// // SinaWeiboRequest.m // sinaweibo_ios_sdk // Based on OAuth 2.0 // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. // // Copyright 2011 Sina. All rights reserved. // #import "SinaWeiboRequest.h" #import "SinaWeiboConstants.h" #import "JSONKit.h" #import "SinaWeibo.h" #define kSinaWeiboRequestTimeOutInterval 180.0 #define kSinaWeiboRequestStringBoundary @"293iosfksdfkiowjksdf31jsiuwq003s02dsaffafass3qw" @interface NSString (SinaWeiboEncode) - (NSString *)URLEncodedString; @end @implementation NSString (SinaWeiboEncode) - (NSString *)URLEncodedStringWithCFStringEncoding:(CFStringEncoding)encoding { return [(NSString *) CFURLCreateStringByAddingPercentEscapes(NULL, (CFStringRef)[[self mutableCopy] autorelease], NULL, CFSTR("=,!$&'()*+;@?\n\"<>#\t :/"), encoding) autorelease]; } - (NSString *)URLEncodedString { return [self URLEncodedStringWithCFStringEncoding:kCFStringEncodingUTF8]; } @end @interface SinaWeibo (SinaWeiboRequest) - (void)requestDidFinish:(SinaWeiboRequest *)request; - (void)requestDidFailWithInvalidToken:(NSError *)error; @end @interface SinaWeiboRequest (Private) - (void)appendUTF8Body:(NSMutableData *)body dataString:(NSString *)dataString; - (NSMutableData *)postBodyHasRawData:(BOOL*)hasRawData; - (void)handleResponseData:(NSData *)data; - (id)parseJSONData:(NSData *)data error:(NSError **)error; - (id)errorWithCode:(NSInteger)code userInfo:(NSDictionary *)userInfo; - (void)failedWithError:(NSError *)error; @end @implementation SinaWeiboRequest @synthesize sinaweibo; @synthesize url; @synthesize httpMethod; @synthesize params; @synthesize delegate; #pragma mark - SinaWeiboRequest Life Circle - (void)dealloc { sinaweibo = nil; [url release], url = nil; [httpMethod release], httpMethod = nil; [params release], params = nil; [responseData release]; responseData = nil; [connection cancel]; [connection release], connection = nil; [super dealloc]; } #pragma mark - SinaWeiboRequest Private Methods - (void)appendUTF8Body:(NSMutableData *)body dataString:(NSString *)dataString { [body appendData:[dataString dataUsingEncoding:NSUTF8StringEncoding]]; } - (NSMutableData *)postBodyHasRawData:(BOOL*)hasRawData { NSString *bodyPrefixString = [NSString stringWithFormat:@"--%@\r\n", kSinaWeiboRequestStringBoundary]; NSString *bodySuffixString = [NSString stringWithFormat:@"\r\n--%@--\r\n", kSinaWeiboRequestStringBoundary]; NSMutableDictionary *dataDictionary = [NSMutableDictionary dictionary]; NSMutableData *body = [NSMutableData data]; [self appendUTF8Body:body dataString:bodyPrefixString]; for (id key in [params keyEnumerator]) { if (([[params valueForKey:key] isKindOfClass:[UIImage class]]) || ([[params valueForKey:key] isKindOfClass:[NSData class]])) { [dataDictionary setObject:[params valueForKey:key] forKey:key]; continue; } [self appendUTF8Body:body dataString:[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n%@\r\n", key, [params valueForKey:key]]]; [self appendUTF8Body:body dataString:bodyPrefixString]; } if ([dataDictionary count] > 0) { *hasRawData = YES; for (id key in dataDictionary) { NSObject *dataParam = [dataDictionary valueForKey:key]; if ([dataParam isKindOfClass:[UIImage class]]) { NSData* imageData = UIImagePNGRepresentation((UIImage *)dataParam); [self appendUTF8Body:body dataString:[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"file\"\r\n", key]]; [self appendUTF8Body:body dataString:[NSString stringWithString:@"Content-Type: image/png\r\nContent-Transfer-Encoding: binary\r\n\r\n"]]; [body appendData:imageData]; } else if ([dataParam isKindOfClass:[NSData class]]) { [self appendUTF8Body:body dataString:[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"file\"\r\n", key]]; [self appendUTF8Body:body dataString:[NSString stringWithString:@"Content-Type: content/unknown\r\nContent-Transfer-Encoding: binary\r\n\r\n"]]; [body appendData:(NSData*)dataParam]; } [self appendUTF8Body:body dataString:bodySuffixString]; } } return body; } - (void)handleResponseData:(NSData *)data { if ([delegate respondsToSelector:@selector(request:didReceiveRawData:)]) { [delegate request:self didReceiveRawData:data]; } NSError *error = nil; id result = [self parseJSONData:data error:&error]; if (error) { [self failedWithError:error]; } else { NSInteger error_code = 0; if([result isKindOfClass:[NSDictionary class]]) { [[result objectForKey:@"error_code"] intValue]; } if (error_code != 0) { NSString *error_description = [result objectForKey:@"error"]; NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys: result, @"error", error_description, NSLocalizedDescriptionKey, nil]; NSError *error = [NSError errorWithDomain:kSinaWeiboSDKErrorDomain code:[[result objectForKey:@"error_code"] intValue] userInfo:userInfo]; if (error_code == 21314 //Token已经被使用 || error_code == 21315 //Token已经过期 || error_code == 21316 //Token不合法 || error_code == 21317 //Token不合法 || error_code == 21327 //token过期 || error_code == 21332) //access_token 无效 { [sinaweibo requestDidFailWithInvalidToken:error]; } else { [self failedWithError:error]; } } else { if ([delegate respondsToSelector:@selector(request:didFinishLoadingWithResult:)]) { [delegate request:self didFinishLoadingWithResult:(result == nil ? data : result)]; } } } } - (id)parseJSONData:(NSData *)data error:(NSError **)error { NSError *parseError = nil; id result =[data objectFromJSONDataWithParseOptions:JKParseOptionStrict error:&parseError]; if (parseError && (error != nil)) { NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys: parseError, @"error", @"Data parse error", NSLocalizedDescriptionKey, nil]; *error = [self errorWithCode:kSinaWeiboSDKErrorCodeParseError userInfo:userInfo]; } return result; } - (id)errorWithCode:(NSInteger)code userInfo:(NSDictionary *)userInfo { return [NSError errorWithDomain:kSinaWeiboSDKErrorDomain code:code userInfo:userInfo]; } - (void)failedWithError:(NSError *)error { if ([delegate respondsToSelector:@selector(request:didFailWithError:)]) { [delegate request:self didFailWithError:error]; } } #pragma mark - SinaWeiboRequest Public Methods + (NSString *)getParamValueFromUrl:(NSString*)url paramName:(NSString *)paramName { if (![paramName hasSuffix:@"="]) { paramName = [NSString stringWithFormat:@"%@=", paramName]; } NSString * str = nil; NSRange start = [url rangeOfString:paramName]; if (start.location != NSNotFound) { // confirm that the parameter is not a partial name match unichar c = '?'; if (start.location != 0) { c = [url characterAtIndex:start.location - 1]; } if (c == '?' || c == '&' || c == '#') { NSRange end = [[url substringFromIndex:start.location+start.length] rangeOfString:@"&"]; NSUInteger offset = start.location+start.length; str = end.location == NSNotFound ? [url substringFromIndex:offset] : [url substringWithRange:NSMakeRange(offset, end.location)]; str = [str stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; } } return str; } + (NSString *)serializeURL:(NSString *)baseURL params:(NSDictionary *)params httpMethod:(NSString *)httpMethod { NSURL* parsedURL = [NSURL URLWithString:baseURL]; NSString* queryPrefix = parsedURL.query ? @"&" : @"?"; NSMutableArray* pairs = [NSMutableArray array]; for (NSString* key in [params keyEnumerator]) { if (([[params objectForKey:key] isKindOfClass:[UIImage class]]) ||([[params objectForKey:key] isKindOfClass:[NSData class]])) { if ([httpMethod isEqualToString:@"GET"]) { NSLog(@"can not use GET to upload a file"); } continue; } NSString* escaped_value = (NSString *)CFURLCreateStringByAddingPercentEscapes( NULL, /* allocator */ (CFStringRef)[params objectForKey:key], NULL, /* charactersToLeaveUnescaped */ (CFStringRef)@"!*'();:@&=+$,/?%#[]", kCFStringEncodingUTF8); [pairs addObject:[NSString stringWithFormat:@"%@=%@", key, escaped_value]]; [escaped_value release]; } NSString* query = [pairs componentsJoinedByString:@"&"]; return [NSString stringWithFormat:@"%@%@%@", baseURL, queryPrefix, query]; } + (SinaWeiboRequest *)requestWithURL:(NSString *)url httpMethod:(NSString *)httpMethod params:(NSDictionary *)params delegate:(id)delegate { SinaWeiboRequest *request = [[[SinaWeiboRequest alloc] init] autorelease]; request.url = url; request.httpMethod = httpMethod; request.params = params; request.delegate = delegate; return request; } + (SinaWeiboRequest *)requestWithAccessToken:(NSString *)accessToken url:(NSString *)url httpMethod:(NSString *)httpMethod params:(NSDictionary *)params delegate:(id)delegate { // add the access token field NSMutableDictionary *mutableParams = [NSMutableDictionary dictionaryWithDictionary:params]; [mutableParams setObject:accessToken forKey:@"access_token"]; return [SinaWeiboRequest requestWithURL:url httpMethod:httpMethod params:mutableParams delegate:delegate]; } - (void)connect { NSString* urlString = [[self class] serializeURL:url params:params httpMethod:httpMethod]; NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlString] cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:kSinaWeiboRequestTimeOutInterval]; [request setHTTPMethod:self.httpMethod]; if ([self.httpMethod isEqualToString: @"POST"]) { BOOL hasRawData = NO; [request setHTTPBody:[self postBodyHasRawData:&hasRawData]]; if (hasRawData) { NSString* contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", kSinaWeiboRequestStringBoundary]; [request setValue:contentType forHTTPHeaderField:@"Content-Type"]; } } connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES]; } - (void)disconnect { [responseData release]; responseData = nil; [connection cancel]; [connection release], connection = nil; } #pragma mark - NSURLConnection Delegate Methods - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { responseData = [[NSMutableData alloc] init]; if ([delegate respondsToSelector:@selector(request:didReceiveResponse:)]) { [delegate request:self didReceiveResponse:response]; } } - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { [responseData appendData:data]; } - (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse*)cachedResponse { return nil; } - (void)connectionDidFinishLoading:(NSURLConnection *)theConnection { [self handleResponseData:responseData]; [responseData release]; responseData = nil; [connection cancel]; [connection release]; connection = nil; [sinaweibo requestDidFinish:self]; } - (void)connection:(NSURLConnection *)theConnection didFailWithError:(NSError *)error { [self failedWithError:error]; [responseData release]; responseData = nil; [connection cancel]; [connection release]; connection = nil; [sinaweibo requestDidFinish:self]; } @end