diff --git a/CHANGELOG.md b/CHANGELOG.md index 25832d9..ea3973d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 1.10.1 + +- Fixed #71: does not encode query string in URL correctly on Android + ## 1.10.0 - Feature #34: add new serializer "utf8" sending utf-8 encoded plain text (thanks robertocapuano) diff --git a/src/ios/CordovaHttpPlugin.m b/src/ios/CordovaHttpPlugin.m index 6f34c8b..bff6a3f 100644 --- a/src/ios/CordovaHttpPlugin.m +++ b/src/ios/CordovaHttpPlugin.m @@ -55,7 +55,7 @@ - (void)handleSuccess:(NSMutableDictionary*)dictionary withResponse:(NSHTTPURLResponse*)response andData:(id)data { if (response != nil) { [dictionary setValue:response.URL.absoluteString forKey:@"url"]; - [dictionary setObject:[NSNumber numberWithInt:response.statusCode] forKey:@"status"]; + [dictionary setObject:[NSNumber numberWithInt:(int)response.statusCode] forKey:@"status"]; [dictionary setObject:[self copyHeaderFields:response.allHeaderFields] forKey:@"headers"]; } @@ -67,9 +67,9 @@ - (void)handleError:(NSMutableDictionary*)dictionary withResponse:(NSHTTPURLResponse*)response error:(NSError*)error { if (response != nil) { [dictionary setValue:response.URL.absoluteString forKey:@"url"]; - [dictionary setObject:[NSNumber numberWithInt:response.statusCode] forKey:@"status"]; + [dictionary setObject:[NSNumber numberWithInt:(int)response.statusCode] forKey:@"status"]; [dictionary setObject:[self copyHeaderFields:response.allHeaderFields] forKey:@"headers"]; - [dictionary setObject:[[NSString alloc] initWithData:(NSData *)error.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] encoding:NSUTF8StringEncoding] forKey:@"error"]; + [dictionary setObject:error.userInfo[AFNetworkingOperationFailingURLResponseBodyKey] forKey:@"error"]; } else { [dictionary setObject:[self getStatusCode:error] forKey:@"status"]; [dictionary setObject:[error localizedDescription] forKey:@"error"]; diff --git a/src/ios/TextResponseSerializer.h b/src/ios/TextResponseSerializer.h index 5e2841e..7bf87db 100644 --- a/src/ios/TextResponseSerializer.h +++ b/src/ios/TextResponseSerializer.h @@ -5,4 +5,6 @@ + (instancetype)serializer; -@end \ No newline at end of file +FOUNDATION_EXPORT NSString * const AFNetworkingOperationFailingURLResponseBodyKey; + +@end diff --git a/src/ios/TextResponseSerializer.m b/src/ios/TextResponseSerializer.m index c9ef1d8..b5bf149 100644 --- a/src/ios/TextResponseSerializer.m +++ b/src/ios/TextResponseSerializer.m @@ -1,32 +1,115 @@ #import "TextResponseSerializer.h" -static BOOL AFErrorOrUnderlyingErrorHasCodeInDomain(NSError *error, NSInteger code, NSString *domain) { - if ([error.domain isEqualToString:domain] && error.code == code) { - return YES; - } else if (error.userInfo[NSUnderlyingErrorKey]) { - return AFErrorOrUnderlyingErrorHasCodeInDomain(error.userInfo[NSUnderlyingErrorKey], code, domain); - } +NSString * const AFNetworkingOperationFailingURLResponseBodyKey = @"com.alamofire.serialization.response.error.body"; +NSStringEncoding const SupportedEncodings[6] = { NSUTF8StringEncoding, NSWindowsCP1252StringEncoding, NSISOLatin1StringEncoding, NSISOLatin2StringEncoding, NSASCIIStringEncoding, NSUnicodeStringEncoding }; - return NO; +static NSError * AFErrorWithUnderlyingError(NSError *error, NSError *underlyingError) { + if (!error) { + return underlyingError; + } + + if (!underlyingError || error.userInfo[NSUnderlyingErrorKey]) { + return error; + } + + NSMutableDictionary *mutableUserInfo = [error.userInfo mutableCopy]; + mutableUserInfo[NSUnderlyingErrorKey] = underlyingError; + + return [[NSError alloc] initWithDomain:error.domain code:error.code userInfo:mutableUserInfo]; +} + +static BOOL AFErrorOrUnderlyingErrorHasCodeInDomain(NSError *error, NSInteger code, NSString *domain) { + if ([error.domain isEqualToString:domain] && error.code == code) { + return YES; + } else if (error.userInfo[NSUnderlyingErrorKey]) { + return AFErrorOrUnderlyingErrorHasCodeInDomain(error.userInfo[NSUnderlyingErrorKey], code, domain); + } + + return NO; } @implementation TextResponseSerializer + (instancetype)serializer { - TextResponseSerializer *serializer = [[self alloc] init]; - return serializer; + TextResponseSerializer *serializer = [[self alloc] init]; + return serializer; } - (instancetype)init { - self = [super init]; + self = [super init]; - if (!self) { - return nil; + if (!self) { + return nil; + } + + self.acceptableContentTypes = nil; + + return self; +} + +- (NSString*)decodeResponseData:(NSData*)rawResponseData withEncoding:(CFStringEncoding)cfEncoding { + NSStringEncoding nsEncoding; + NSString* decoded = nil; + + if (cfEncoding != kCFStringEncodingInvalidId) { + nsEncoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding); + } + + for (int i = 0; i < sizeof(SupportedEncodings) / sizeof(NSStringEncoding) && !decoded; ++i) { + if (cfEncoding == kCFStringEncodingInvalidId || nsEncoding == SupportedEncodings[i]) { + decoded = [[NSString alloc] initWithData:rawResponseData encoding:SupportedEncodings[i]]; } + } - self.acceptableContentTypes = nil; + if (!decoded) { + decoded = @"Could not decode response data due to invalid or unknown charset encoding"; + } - return self; + return decoded; +} + +- (CFStringEncoding) getEncoding:(NSURLResponse *)response { + CFStringEncoding encoding = kCFStringEncodingInvalidId; + + if (response.textEncodingName) { + encoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName); + } + + return encoding; +} + +#pragma mark - + +- (BOOL)validateResponse:(NSHTTPURLResponse *)response + data:(NSData *)data + error:(NSError * __autoreleasing *)error +{ + BOOL responseIsValid = YES; + NSError *validationError = nil; + + if (response && [response isKindOfClass:[NSHTTPURLResponse class]]) { + if (self.acceptableStatusCodes && ![self.acceptableStatusCodes containsIndex:(NSUInteger)response.statusCode] && [response URL]) { + NSMutableDictionary *mutableUserInfo = [@{ + NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedStringFromTable(@"Request failed: %@ (%ld)", @"AFNetworking", nil), [NSHTTPURLResponse localizedStringForStatusCode:response.statusCode], (long)response.statusCode], + NSURLErrorFailingURLErrorKey:[response URL], + AFNetworkingOperationFailingURLResponseErrorKey: response, + } mutableCopy]; + + if (data) { + mutableUserInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] = data; + mutableUserInfo[AFNetworkingOperationFailingURLResponseBodyKey] = [self decodeResponseData:data withEncoding:[self getEncoding:response]]; + } + + validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorBadServerResponse userInfo:mutableUserInfo], validationError); + responseIsValid = NO; + } + } + + if (error && !responseIsValid) { + *error = validationError; + } + + return responseIsValid; } #pragma mark - AFURLResponseSerialization @@ -35,25 +118,13 @@ static BOOL AFErrorOrUnderlyingErrorHasCodeInDomain(NSError *error, NSInteger co data:(NSData *)data error:(NSError *__autoreleasing *)error { - if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) { - if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) { - return nil; - } + if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) { + if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) { + return nil; } + } - // Workaround for behavior of Rails to return a single space for `head :ok` (a workaround for a bug in Safari), which is not interpreted as valid input by NSJSONSerialization. - // See https://github.com/rails/rails/issues/1742 - NSStringEncoding stringEncoding = self.stringEncoding; - if (response.textEncodingName) { - CFStringEncoding encoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName); - if (encoding != kCFStringEncodingInvalidId) { - stringEncoding = CFStringConvertEncodingToNSStringEncoding(encoding); - } - } - - NSString *responseString = [[NSString alloc] initWithData:data encoding:stringEncoding]; - - return responseString; + return [self decodeResponseData:data withEncoding:[self getEncoding:response]]; } @end diff --git a/test/app-test-definitions.js b/test/app-test-definitions.js index 265acb9..d1191a2 100644 --- a/test/app-test-definitions.js +++ b/test/app-test-definitions.js @@ -402,9 +402,18 @@ const tests = [ }, validationFunc: function(driver, result) { result.type.should.be.equal('resolved'); - console.log(JSON.parse(result.data.data).args); JSON.parse(result.data.data).args['query param'].should.eql('and value with spaces'); } + },{ + description: 'should decode latin1 (iso-8859-1) encoded body correctly (GET) #72', + expected: 'resolved: {"status": 200, "data": " ...', + func: function(resolve, reject) { + cordova.plugin.http.get('http://www.columbia.edu/kermit/latin1.html', {}, {}, resolve, reject); + }, + validationFunc: function(driver, result) { + result.type.should.be.equal('resolved'); + result.data.data.should.include('[¡] 161 10/01 241 A1 INVERTED EXCLAMATION MARK\n[¢] 162 10/02 242 A2 CENT SIGN'); + } } ];