-
Notifications
You must be signed in to change notification settings - Fork 0
/
AFXMLRPCClient.m
359 lines (296 loc) · 14.9 KB
/
AFXMLRPCClient.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
//
// AFXMLRPCClient.m
// WordPressApiExample
//
// Created by Jorge Bernal on 12/13/11.
// Copyright (c) 2011 Automattic. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "AFXMLRPCClient.h"
#import "AFHTTPRequestOperation.h"
#import "AFAuthenticationAlertView.h"
#import "XMLRPCEncoder.h"
#import "XMLRPCResponse.h"
#ifndef WPFLog
#define WPFLog(...) NSLog(__VA_ARGS__)
#endif
static NSUInteger const kAFXMLRPCClientDefaultMaxConcurrentOperationCount = 4;
@implementation AFXMLRPCRequest
@synthesize method, parameters;
- (void)dealloc {
self.method = nil;
self.parameters = nil;
[super dealloc];
}
@end
@implementation AFXMLRPCRequestOperation
@synthesize XMLRPCRequest, success, failure;
- (void)dealloc {
self.XMLRPCRequest = nil;
self.success = nil;
self.failure = nil;
[super dealloc];
}
@end
@interface AFXMLRPCClient ()
@property (readwrite, nonatomic, retain) NSURL *xmlrpcEndpoint;
@property (readwrite, nonatomic, retain) NSMutableDictionary *defaultHeaders;
@property (readwrite, nonatomic, retain) NSOperationQueue *operationQueue;
@end
@implementation AFXMLRPCClient {
NSURL *_xmlrpcEndpoint;
NSMutableDictionary *_defaultHeaders;
NSOperationQueue *_operationQueue;
}
@synthesize xmlrpcEndpoint = _xmlrpcEndpoint;
@synthesize defaultHeaders = _defaultHeaders;
@synthesize operationQueue = _operationQueue;
#pragma mark - Creating and Initializing XML-RPC Clients
+ (AFXMLRPCClient *)clientWithXMLRPCEndpoint:(NSURL *)xmlrpcEndpoint {
return [[[self alloc] initWithXMLRPCEndpoint:xmlrpcEndpoint] autorelease];
}
- (id)initWithXMLRPCEndpoint:(NSURL *)xmlrpcEndpoint {
self = [super init];
if (!self) {
return nil;
}
self.xmlrpcEndpoint = xmlrpcEndpoint;
self.defaultHeaders = [NSMutableDictionary dictionary];
// Accept-Encoding HTTP Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3
[self setDefaultHeader:@"Accept-Encoding" value:@"gzip"];
[self setDefaultHeader:@"Content-Type" value:@"text/xml"];
NSString *applicationUserAgent = [[NSUserDefaults standardUserDefaults] objectForKey:@"UserAgent"];
if (applicationUserAgent) {
[self setDefaultHeader:@"User-Agent" value:applicationUserAgent];
} else {
[self setDefaultHeader:@"User-Agent" value:[NSString stringWithFormat:@"%@/%@ (%@, %@ %@, %@, Scale/%f)", [[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString *)kCFBundleIdentifierKey], [[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString *)kCFBundleVersionKey], @"unknown", [[UIDevice currentDevice] systemName], [[UIDevice currentDevice] systemVersion], [[UIDevice currentDevice] model], ([[UIScreen mainScreen] respondsToSelector:@selector(scale)] ? [[UIScreen mainScreen] scale] : 1.0)]];
}
self.operationQueue = [[[NSOperationQueue alloc] init] autorelease];
[self.operationQueue setMaxConcurrentOperationCount:kAFXMLRPCClientDefaultMaxConcurrentOperationCount];
return self;
}
- (void)dealloc {
[_xmlrpcEndpoint release];
[_defaultHeaders release];
[_operationQueue release];
[super dealloc];
}
#pragma mark - Managing HTTP Header Values
- (NSString *)defaultValueForHeader:(NSString *)header {
return [self.defaultHeaders valueForKey:header];
}
- (void)setDefaultHeader:(NSString *)header value:(NSString *)value {
[self.defaultHeaders setValue:value forKey:header];
}
- (void)setAuthorizationHeaderWithUsername:(NSString *)username password:(NSString *)password {
// TODO: not implemented yet
// NSString *basicAuthCredentials = [NSString stringWithFormat:@"%@:%@", username, password];
// [self setDefaultHeader:@"Authorization" value:[NSString stringWithFormat:@"Basic %@", AFBase64EncodedStringFromString(basicAuthCredentials)]];
}
- (void)setAuthorizationHeaderWithToken:(NSString *)token {
[self setDefaultHeader:@"Authorization" value:[NSString stringWithFormat:@"Token token=\"%@\"", token]];
}
- (void)clearAuthorizationHeader {
[self.defaultHeaders removeObjectForKey:@"Authorization"];
}
#pragma mark - Creating Request Objects
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
parameters:(NSArray *)parameters {
NSMutableURLRequest *request = [[[NSMutableURLRequest alloc] initWithURL:self.xmlrpcEndpoint] autorelease];
[request setHTTPMethod:@"POST"];
[request setAllHTTPHeaderFields:self.defaultHeaders];
XMLRPCEncoder *encoder = [[XMLRPCEncoder alloc] init];
[encoder setMethod:method withParameters:parameters];
NSData *body = [[encoder encode] dataUsingEncoding:NSUTF8StringEncoding];
[encoder release];
[request setHTTPBody:body];
return request;
}
- (NSMutableURLRequest *)streamingRequestWithMethod:(NSString *)method
parameters:(NSArray *)parameters {
NSMutableURLRequest *request = [[[NSMutableURLRequest alloc] initWithURL:self.xmlrpcEndpoint] autorelease];
[request setHTTPMethod:@"POST"];
[request setAllHTTPHeaderFields:self.defaultHeaders];
XMLRPCEncoder *encoder = [[XMLRPCEncoder alloc] init];
[encoder setMethod:method withParameters:parameters];
[request setHTTPBodyStream:[encoder encodedStream]];
[request setValue:[[encoder encodedLength] stringValue] forHTTPHeaderField:@"Content-Length"];
[encoder release];
return request;
}
- (AFXMLRPCRequest *)XMLRPCRequestWithMethod:(NSString *)method
parameters:(NSArray *)parameters {
AFXMLRPCRequest *request = [[[AFXMLRPCRequest alloc] init] autorelease];
request.method = method;
request.parameters = parameters;
return request;
}
#pragma mark - Creating HTTP Operations
- (AFHTTPRequestOperation *)HTTPRequestOperationWithRequest:(NSURLRequest *)request
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure {
AFHTTPRequestOperation *operation = [[[AFHTTPRequestOperation alloc] initWithRequest:request] autorelease];
BOOL extra_debug_on = getenv("WPDebugXMLRPC") ? YES : NO;
#ifndef DEBUG
NSNumber *extra_debug = [[NSUserDefaults standardUserDefaults] objectForKey:@"extra_debug"];
if ([extra_debug boolValue]) extra_debug_on = YES;
#endif
void (^xmlrpcSuccess)(AFHTTPRequestOperation *, id) = ^(AFHTTPRequestOperation *operation, id responseObject) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
XMLRPCResponse *response = [[XMLRPCResponse alloc] initWithData:responseObject];
NSError *err = nil;
if ( extra_debug_on == YES ) {
WPFLog(@"[XML-RPC] < %@", [response body]);
}
if ([response isFault]) {
NSDictionary *usrInfo = [NSDictionary dictionaryWithObjectsAndKeys:[response faultString], NSLocalizedDescriptionKey, nil];
err = [NSError errorWithDomain:@"XMLRPC" code:[[response faultCode] intValue] userInfo:usrInfo];
}
if ([response object] == nil) {
NSDictionary *usrInfo = [NSDictionary dictionaryWithObjectsAndKeys:NSLocalizedString(@"Blog returned invalid data.", @""), NSLocalizedDescriptionKey, nil];
err = [NSError errorWithDomain:@"XMLRPC" code:kNoXMLPrefix userInfo:usrInfo];
// Log the whole response for "invalid data"
if ( extra_debug_on == YES ) {
WPFLog(@"Blog returned invalid data (URL: %@)\n%@", request.URL.absoluteString, operation.responseString);
}
}
id object = [[response object] copy];
[response release];
dispatch_async(dispatch_get_main_queue(), ^(void) {
if (err) {
if (failure) {
failure(operation, err);
}
} else {
if (success) {
success(operation, object);
}
}
[object release];
});
});
};
void (^xmlrpcFailure)(AFHTTPRequestOperation *, NSError *) = ^(AFHTTPRequestOperation *operation, NSError *error) {
if ( extra_debug_on == YES ) {
WPFLog(@"[XML-RPC] ! %@", [error localizedDescription]);
}
if (failure) {
failure(operation, [WPError errorWithResponse:operation.response error:error]);
}
};
[operation setCompletionBlockWithSuccess:xmlrpcSuccess failure:xmlrpcFailure];
[operation setAuthenticationChallengeBlock:^(NSURLConnection *connection, NSURLAuthenticationChallenge *challenge) {
NSURLCredential *credential = [[NSURLCredentialStorage sharedCredentialStorage] defaultCredentialForProtectionSpace:[challenge protectionSpace]];
if ([challenge previousFailureCount] == 0 && credential) {
[[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
} else {
dispatch_async(dispatch_get_main_queue(), ^(void) {
AFAuthenticationAlertView *alert = [[AFAuthenticationAlertView alloc] initWithChallenge:challenge];
[alert show];
[alert release];
});
}
}];
if ( extra_debug_on == YES ) {
NSString *requestString = [[[NSString alloc] initWithData:[request HTTPBody] encoding:NSUTF8StringEncoding] autorelease];
if (getenv("WPDebugXMLRPC")) {
WPFLog(@"[XML-RPC] > %@", requestString);
} else {
NSError *error = NULL;
NSRegularExpression *method = [NSRegularExpression regularExpressionWithPattern:@"<methodName>(.*)</methodName>" options:NSRegularExpressionCaseInsensitive error:&error];
NSArray *matches = [method matchesInString:requestString options:0 range:NSMakeRange(0, [requestString length])];
NSString *methodName = nil;
if (matches) {
NSRange methodRange = [[matches objectAtIndex:0] rangeAtIndex:1];
if(methodRange.location != NSNotFound)
methodName = [requestString substringWithRange:methodRange];
}
WPFLog(@"[XML-RPC] > %@", methodName);
}
}
return operation;
}
- (AFXMLRPCRequestOperation *)XMLRPCRequestOperationWithRequest:(AFXMLRPCRequest *)request
success:(AFXMLRPCRequestOperationSuccessBlock)success
failure:(AFXMLRPCRequestOperationFailureBlock)failure {
AFXMLRPCRequestOperation *operation = [[[AFXMLRPCRequestOperation alloc] init] autorelease];
operation.XMLRPCRequest = request;
operation.success = success;
operation.failure = failure;
return operation;
}
- (AFHTTPRequestOperation *)combinedHTTPRequestOperationWithOperations:(NSArray *)operations success:(AFXMLRPCRequestOperationSuccessBlock)success failure:(AFXMLRPCRequestOperationFailureBlock)failure {
NSMutableArray *parameters = [NSMutableArray array];
for (AFXMLRPCRequestOperation *operation in operations) {
NSDictionary *param = [NSDictionary dictionaryWithObjectsAndKeys:
operation.XMLRPCRequest.method, @"methodName",
operation.XMLRPCRequest.parameters, @"params",
nil];
[parameters addObject:param];
}
NSURLRequest *request = [self requestWithMethod:@"system.multicall" parameters:parameters];
AFXMLRPCRequestOperationSuccessBlock _success = ^(AFHTTPRequestOperation *multicallOperation, id responseObject) {
NSArray *responses = (NSArray *)responseObject;
for (int i = 0; i < [responses count]; i++) {
AFXMLRPCRequestOperation *operation = [operations objectAtIndex:i];
id object = [responses objectAtIndex:i];
NSError *error = nil;
if ([object isKindOfClass:[NSDictionary class]] && [object objectForKey:@"faultCode"] && [object objectForKey:@"faultString"]) {
NSDictionary *usrInfo = [NSDictionary dictionaryWithObjectsAndKeys:[object objectForKey:@"faultString"], NSLocalizedDescriptionKey, nil];
error = [NSError errorWithDomain:@"XMLRPC" code:[[object objectForKey:@"faultCode"] intValue] userInfo:usrInfo];
} else if ([object isKindOfClass:[NSArray class]] && [object count] == 1) {
object = [object objectAtIndex:0];
}
if (error) {
if (operation.failure) {
operation.failure(operation, error);
}
} else {
if (operation.success) {
operation.success(operation, object);
}
}
}
if (success) {
success(multicallOperation, responseObject);
}
};
AFXMLRPCRequestOperationFailureBlock _failure = ^(AFHTTPRequestOperation *multicallOperation, NSError *error) {
for (AFXMLRPCRequestOperation *operation in operations) {
if (operation.failure) {
operation.failure(operation, error);
}
}
if (failure) {
failure(multicallOperation, error);
}
};
AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithRequest:request
success:_success
failure:_failure];
return operation;
}
#pragma mark - Managing Enqueued HTTP Operations
- (void)enqueueHTTPRequestOperation:(AFHTTPRequestOperation *)operation {
[self.operationQueue addOperation:operation];
}
- (void)enqueueXMLRPCRequestOperation:(AFXMLRPCRequestOperation *)operation {
NSURLRequest *request = [self requestWithMethod:operation.XMLRPCRequest.method parameters:operation.XMLRPCRequest.parameters];
AFHTTPRequestOperation *op = [self HTTPRequestOperationWithRequest:request success:operation.success failure:operation.failure];
[self enqueueHTTPRequestOperation:op];
}
- (void)cancelAllHTTPOperations {
for (AFHTTPRequestOperation *operation in [self.operationQueue operations]) {
[operation cancel];
}
}
#pragma mark - Making XML-RPC Requests
- (void)callMethod:(NSString *)method
parameters:(NSArray *)parameters
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure {
NSURLRequest *request = [self requestWithMethod:method parameters:parameters];
AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithRequest:request success:success failure:failure];
[self enqueueHTTPRequestOperation:operation];
}
@end