Skip to content

Commit

Permalink
[TIMOB-16271] Get the correct content type from response
Browse files Browse the repository at this point in the history
  • Loading branch information
pec1985 committed Feb 3, 2014
1 parent 521cb30 commit 9da0094
Show file tree
Hide file tree
Showing 7 changed files with 208 additions and 62 deletions.
7 changes: 7 additions & 0 deletions iphone/Classes/TiHTTPClient/TiHTTPHelper.h
Expand Up @@ -13,5 +13,12 @@
@interface TiHTTPHelper : NSObject

+(NSString *)base64encode:(NSData *)plainText;
+(int)caselessCompareFirstString:(const char *)firstString secondString:(const char *)secondString size:(int)size;
+(BOOL)extractEncodingFromData:(NSData *)inputData result:(NSStringEncoding*)result;
+(NSString *)contentTypeForImageData:(NSData *)data;
+(NSString*)fileMIMEType:(NSString*)file;
+(NSString*)encodeURL:(NSString *)string;
+(void)parseMimeType:(NSString **)mimeType andResponseEncoding:(NSStringEncoding *)stringEncoding fromContentType:(NSString *)contentType;
+(NSStringEncoding)parseStringEncodingFromHeaders:(NSDictionary*)headers;

@end
172 changes: 172 additions & 0 deletions iphone/Classes/TiHTTPClient/TiHTTPHelper.m
Expand Up @@ -8,6 +8,7 @@
*/

#import "TiHTTPClient.h"
#import <MobileCoreServices/MobileCoreServices.h>

@implementation TiHTTPHelper

Expand Down Expand Up @@ -50,4 +51,175 @@ +(NSString *)base64encode:(NSData *)plainText
return result;
}


+(int)caselessCompareFirstString:(const char *)firstString secondString:(const char *)secondString size:(int)size
{
int index = 0;
while(index < size)
{
char firstChar = tolower(firstString[index]);
char secondChar = secondString[index]; //Second string is always lowercase.
index++;
if(firstChar!=secondChar)return index; //Yes, this is one after the failure.
}
return 0;
}


#define TRYENCODING( encodingName, nameSize, returnValue, value ) \
if((remainingSize > nameSize)&&([self caselessCompareFirstString:data secondString:encodingName size:nameSize] == 0)) {*value = returnValue; return YES;}

+(BOOL)extractEncodingFromData:(NSData *)inputData result:(NSStringEncoding*)result
{

int remainingSize = [inputData length];
int unsearchableSize;
if(remainingSize > 2008) unsearchableSize = remainingSize - 2000;
else unsearchableSize = 8; // So that there's no chance of overrunning the buffer with 'charset='
const char * data = [inputData bytes];

// XML provides its own encoding format as part of the definition,
// we need to use this if it looks like a XML document
int prefix = [self caselessCompareFirstString:data secondString:"<?xml" size:5];
if (prefix==0)
{
char *enc = strstr(data, "encoding=");
if (enc)
{
enc += 10;
data = enc;

TRYENCODING("windows-1252",12,NSWindowsCP1252StringEncoding,result);
TRYENCODING("iso-8859-1",10,NSISOLatin1StringEncoding,result);
TRYENCODING("utf-8",5,NSUTF8StringEncoding,result);
TRYENCODING("shift-jis",9,NSShiftJISStringEncoding,result);
TRYENCODING("shift_jis",9,NSShiftJISStringEncoding,result);
TRYENCODING("x-euc",5,NSJapaneseEUCStringEncoding,result);
TRYENCODING("euc-jp",6,NSJapaneseEUCStringEncoding,result);
TRYENCODING("windows-1250",12,NSWindowsCP1251StringEncoding,result);
TRYENCODING("windows-1251",12,NSWindowsCP1252StringEncoding,result);
TRYENCODING("windows-1253",12,NSWindowsCP1253StringEncoding,result);
TRYENCODING("windows-1254",12,NSWindowsCP1254StringEncoding,result);
TRYENCODING("windows-1255",12,CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingWindowsHebrew),result);
return NO;
}
}

while(remainingSize > unsearchableSize)
{
int compareOffset = [self caselessCompareFirstString:data secondString:"charset=" size:8];
if (compareOffset != 0)
{
data += compareOffset;
remainingSize -= compareOffset;
continue;
}
data += 8;
remainingSize -= 8;

TRYENCODING("windows-1252",12,NSWindowsCP1252StringEncoding,result);
TRYENCODING("iso-8859-1",10,NSISOLatin1StringEncoding,result);
TRYENCODING("utf-8",5,NSUTF8StringEncoding,result);
TRYENCODING("shift-jis",9,NSShiftJISStringEncoding,result);
TRYENCODING("shift_jis",9,NSShiftJISStringEncoding,result);
TRYENCODING("x-euc",5,NSJapaneseEUCStringEncoding,result);
TRYENCODING("euc-jp",6,NSJapaneseEUCStringEncoding,result);
TRYENCODING("windows-1250",12,NSWindowsCP1251StringEncoding,result);
TRYENCODING("windows-1251",12,NSWindowsCP1252StringEncoding,result);
TRYENCODING("windows-1253",12,NSWindowsCP1253StringEncoding,result);
TRYENCODING("windows-1254",12,NSWindowsCP1254StringEncoding,result);
TRYENCODING("windows-1255",12,CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingWindowsHebrew),result);
}
return NO;
}

// Taken from http://stackoverflow.com/questions/4147311/finding-image-type-from-nsdata-or-uiimage
+ (NSString *)contentTypeForImageData:(NSData *)data {
uint8_t c;
[data getBytes:&c length:1];

switch (c) {
case 0xFF:
return @"image/jpeg";
case 0x89:
return @"image/png";
case 0x47:
return @"image/gif";
case 0x49:
case 0x4D:
return @"image/tiff";
}
return @"application/octet-stream";
}

// Taken from http://stackoverflow.com/questions/2439020/wheres-the-iphone-mime-type-database
+ (NSString*)fileMIMEType:(NSString*) file
{
CFStringRef UTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)[file pathExtension], NULL);
CFStringRef MIMEType = UTTypeCopyPreferredTagWithClass (UTI, kUTTagClassMIMEType);
CFRelease(UTI);
if (!MIMEType) {
return @"application/octet-stream";
}
return (__bridge NSString *)MIMEType;
}

// Taken from http://stackoverflow.com/questions/8088473/url-encode-an-nsstring
+ (NSString*)encodeURL:(NSString *)string
{
NSMutableString *output = [NSMutableString string];
const unsigned char *source = (const unsigned char *)[string UTF8String];
int sourceLen = strlen((const char *)source);
for (int i = 0; i < sourceLen; ++i) {
const unsigned char thisChar = source[i];
if (thisChar == ' '){
[output appendString:@"+"];
} else if (thisChar == '.' || thisChar == '-' || thisChar == '_' || thisChar == '~' ||
(thisChar >= 'a' && thisChar <= 'z') ||
(thisChar >= 'A' && thisChar <= 'Z') ||
(thisChar >= '0' && thisChar <= '9')) {
[output appendFormat:@"%c", thisChar];
} else {
[output appendFormat:@"%%%02X", thisChar];
}
}
return output;
}

+ (void)parseMimeType:(NSString **)mimeType andResponseEncoding:(NSStringEncoding *)stringEncoding fromContentType:(NSString *)contentType
{
if (!contentType) {
return;
}
NSScanner *charsetScanner = [NSScanner scannerWithString: contentType];
if (![charsetScanner scanUpToString:@";" intoString:mimeType] || [charsetScanner scanLocation] == [contentType length]) {
*mimeType = [contentType stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
return;
}
*mimeType = [*mimeType stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
NSString *charsetSeparator = @"charset=";
NSString *IANAEncoding = nil;

if ([charsetScanner scanUpToString: charsetSeparator intoString: NULL] && [charsetScanner scanLocation] < [contentType length]) {
[charsetScanner setScanLocation: [charsetScanner scanLocation] + [charsetSeparator length]];
[charsetScanner scanUpToString: @";" intoString: &IANAEncoding];
}

if (IANAEncoding) {
CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)IANAEncoding);
if (cfEncoding != kCFStringEncodingInvalidId) {
*stringEncoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding);
}
}
}

+(NSStringEncoding)parseStringEncodingFromHeaders:(NSDictionary*)headers
{
// Handle response text encoding
NSStringEncoding charset = 0;
NSString *mimeType = nil;
[self parseMimeType:&mimeType andResponseEncoding:&charset fromContentType:[headers valueForKey:@"Content-Type"]];
return charset;
}

@end
63 changes: 4 additions & 59 deletions iphone/Classes/TiHTTPClient/TiHTTPPostForm.m
Expand Up @@ -10,8 +10,6 @@
#import "TiHTTPClient.h"
#import <MobileCoreServices/MobileCoreServices.h>

#define FORM_SPACE @"\r\n";

@implementation TiHTTPPostForm


Expand All @@ -26,59 +24,6 @@ - (void)dealloc
[super dealloc];
}

// Taken from http://stackoverflow.com/questions/4147311/finding-image-type-from-nsdata-or-uiimage
- (NSString *)contentTypeForImageData:(NSData *)data {
uint8_t c;
[data getBytes:&c length:1];

switch (c) {
case 0xFF:
return @"image/jpeg";
case 0x89:
return @"image/png";
case 0x47:
return @"image/gif";
case 0x49:
case 0x4D:
return @"image/tiff";
}
return @"application/octet-stream";
}

// Taken from http://stackoverflow.com/questions/2439020/wheres-the-iphone-mime-type-database
- (NSString*)fileMIMEType:(NSString*) file
{
CFStringRef UTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)[file pathExtension], NULL);
CFStringRef MIMEType = UTTypeCopyPreferredTagWithClass (UTI, kUTTagClassMIMEType);
CFRelease(UTI);
if (!MIMEType) {
return @"application/octet-stream";
}
return (__bridge NSString *)MIMEType;
}

// Taken from http://stackoverflow.com/questions/8088473/url-encode-an-nsstring
- (NSString*)encodeURL:(NSString *)string
{
NSMutableString *output = [NSMutableString string];
const unsigned char *source = (const unsigned char *)[string UTF8String];
int sourceLen = strlen((const char *)source);
for (int i = 0; i < sourceLen; ++i) {
const unsigned char thisChar = source[i];
if (thisChar == ' '){
[output appendString:@"+"];
} else if (thisChar == '.' || thisChar == '-' || thisChar == '_' || thisChar == '~' ||
(thisChar >= 'a' && thisChar <= 'z') ||
(thisChar >= 'A' && thisChar <= 'Z') ||
(thisChar >= '0' && thisChar <= '9')) {
[output appendFormat:@"%c", thisChar];
} else {
[output appendFormat:@"%%%02X", thisChar];
}
}
return output;
}


-(void)appendStringData:(NSString*)str
{
Expand All @@ -104,8 +49,8 @@ -(void)buildStringPostData
}
NSString *key = [allKeys objectAtIndex:i];
[self appendStringData:[NSString stringWithFormat:@"%@=%@%@",
[self encodeURL:key],
[self encodeURL: [[self requestFormDictionay] valueForKey:key]],
[TiHTTPHelper encodeURL:key],
[TiHTTPHelper encodeURL: [[self requestFormDictionay] valueForKey:key]],
(last ? @"" : @"&")
]
];
Expand Down Expand Up @@ -255,7 +200,7 @@ -(void)addFormFile:(NSString*)path;

-(void)addFormFile:(NSString*)path fieldName:(NSString*)name;
{
[self addFormFile:path fieldName:name contentType:[self fileMIMEType:path]];
[self addFormFile:path fieldName:name contentType:[TiHTTPHelper fileMIMEType:path]];
}

-(void)addFormFile:(NSString*)path fieldName:(NSString*)name contentType:(NSString*)contentType
Expand Down Expand Up @@ -290,7 +235,7 @@ -(void)addFormData:(NSData*)data fileName:(NSString*)fileName fieldName:(NSStrin
[self addFormData: data
fileName: fileName
fieldName: fieldName
contentType: [self contentTypeForImageData:data]
contentType: [TiHTTPHelper contentTypeForImageData:data]
];

}
Expand Down
1 change: 1 addition & 0 deletions iphone/Classes/TiHTTPClient/TiHTTPRequest.h
Expand Up @@ -61,5 +61,6 @@ typedef enum {
-(void)send;
-(void)abort;
-(void)addRequestHeader:(NSString*)key value:(NSString*)value;
-(void)setCachePolicy:(NSURLRequestCachePolicy*)cache;

@end
9 changes: 7 additions & 2 deletions iphone/Classes/TiHTTPClient/TiHTTPRequest.m
Expand Up @@ -49,7 +49,7 @@ -(void)initialize
[self setValidatesSecureCertificate: YES];

_request = [[NSMutableURLRequest alloc] init];
[_request setCachePolicy:NSURLRequestReloadIgnoringCacheData];
[_request setCachePolicy:NSURLCacheStorageAllowed];
_response = [[TiHTTPResponse alloc] init];
[_response setReadyState: TiResponseStateUnsent];
}
Expand Down Expand Up @@ -164,7 +164,10 @@ -(void)connection:(NSURLConnection *)connection willSendRequestForAuthentication
}
}
*/

-(void)setCachePolicy:(NSURLRequestCachePolicy*)cache
{
[_request setCachePolicy:cache];
}
-(void)addRequestHeader:(NSString *)key value:(NSString *)value
{
if(_headers == nil) {
Expand Down Expand Up @@ -309,6 +312,8 @@ - (void)connectionDidFinishLoading:(NSURLConnection *)connection
[_response setUploadProgress:1.f];
[_response setReadyState:TiResponseStateDone];
[_response setConnected:NO];
[_response setEncoding:[TiHTTPHelper parseStringEncodingFromHeaders:[_response headers]]];

if([_delegate respondsToSelector:@selector(tiRequest:onReadyStateChage:)]) {
[_delegate tiRequest:self onReadyStateChage:_response];
}
Expand Down
1 change: 1 addition & 0 deletions iphone/Classes/TiHTTPClient/TiHTTPResponse.h
Expand Up @@ -26,6 +26,7 @@ typedef enum {
@property(nonatomic, readonly) NSDictionary *headers;
@property(nonatomic, readonly) NSString *connectionType;
@property(nonatomic, readonly) NSString *location;
@property(nonatomic) NSStringEncoding encoding;
@property(nonatomic, retain) NSError *error;
@property(nonatomic) float downloadProgress;
@property(nonatomic) float uploadProgress;
Expand Down
17 changes: 16 additions & 1 deletion iphone/Classes/TiHTTPClient/TiHTTPResponse.m
Expand Up @@ -83,7 +83,22 @@ -(NSString*)responseString
return [[self error] localizedDescription];
}
if([self responseData] == nil || [[self responseData] length] == 0) return nil;
return [NSString stringWithUTF8String: [[self responseData] bytes]];
NSData *data = [self responseData];
NSString * result = [[[NSString alloc] initWithBytes:[data bytes] length:[data length] encoding:[self encoding]] autorelease];
if (result==nil) {
// encoding failed, probably a bad webserver or content we have to deal
// with in a _special_ way
NSStringEncoding encoding = NSUTF8StringEncoding;
BOOL didExtractEncoding = [TiHTTPHelper extractEncodingFromData:data result:&encoding];
if (didExtractEncoding) {
//If I did extract encoding use that
result = [[[NSString alloc] initWithBytes:[data bytes] length:[data length] encoding:encoding] autorelease];
} else {
result = [[[NSString alloc] initWithBytes:[data bytes] length:[data length] encoding:NSISOLatin1StringEncoding] autorelease];
}

}
return result;
}

-(NSDictionary*)responseDictionary
Expand Down

0 comments on commit 9da0094

Please sign in to comment.