From 5d5a859cfcaff298f7e5141e3abe9fee8bea2f7f Mon Sep 17 00:00:00 2001 From: Roberto Capuano Date: Tue, 12 Dec 2017 16:42:03 +0100 Subject: [PATCH 1/5] added 'raw' serializer --- .../synconset/cordovahttp/CordovaHttp.java | 6 +- .../AFNetworking/AFURLRequestSerialization.h | 13 +++++ .../AFNetworking/AFURLRequestSerialization.m | 56 +++++++++++++++++++ src/ios/CordovaHttpPlugin.m | 6 +- www/advanced-http.js | 2 +- 5 files changed, 80 insertions(+), 3 deletions(-) diff --git a/src/android/com/synconset/cordovahttp/CordovaHttp.java b/src/android/com/synconset/cordovahttp/CordovaHttp.java index 0dfd3fb..20c0a72 100644 --- a/src/android/com/synconset/cordovahttp/CordovaHttp.java +++ b/src/android/com/synconset/cordovahttp/CordovaHttp.java @@ -144,7 +144,11 @@ abstract class CordovaHttp { if (new String("json").equals(this.getSerializerName())) { request.contentType(request.CONTENT_TYPE_JSON, request.CHARSET_UTF8); request.send(this.getParamsObject().toString()); - } else { + } else if (new String("raw").equals(this.getSerializerName())) { + // request.contentType(request.CONTENT_TYPE_JSON, request.CHARSET_UTF8); + request.send(this.getParamsObject().toString()); + } else + { request.form(this.getParamsMap()); } diff --git a/src/ios/AFNetworking/AFURLRequestSerialization.h b/src/ios/AFNetworking/AFURLRequestSerialization.h index 694696b..8548a3e 100644 --- a/src/ios/AFNetworking/AFURLRequestSerialization.h +++ b/src/ios/AFNetworking/AFURLRequestSerialization.h @@ -375,6 +375,19 @@ forHTTPHeaderField:(NSString *)field; @end + +#pragma mark - + +/** + `AFRAWRequestSerializer` is a subclass of `AFHTTPRequestSerializer` + */ +@interface AFRAWRequestSerializer : AFHTTPRequestSerializer + + ++ (instancetype)serializer; + +@end + #pragma mark - /** diff --git a/src/ios/AFNetworking/AFURLRequestSerialization.m b/src/ios/AFNetworking/AFURLRequestSerialization.m index a47e2e6..036b80f 100644 --- a/src/ios/AFNetworking/AFURLRequestSerialization.m +++ b/src/ios/AFNetworking/AFURLRequestSerialization.m @@ -1195,6 +1195,62 @@ typedef enum { @end + +#pragma mark - + +@implementation AFRAWRequestSerializer + ++ (instancetype)serializer +{ + AFRAWRequestSerializer *serializer = [[self alloc] init]; + return serializer; +} + +#pragma mark - AFURLRequestSerialization + +- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request + withParameters:(id)parameters + error:(NSError *__autoreleasing *)error +{ + NSParameterAssert(request); + + if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) { + return [super requestBySerializingRequest:request withParameters:parameters error:error]; + } + + NSMutableURLRequest *mutableRequest = [request mutableCopy]; + + [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) { + if (![request valueForHTTPHeaderField:field]) { + [mutableRequest setValue:value forHTTPHeaderField:field]; + } + }]; + + if (parameters) { +// if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) { +// [mutableRequest setValue:@"application/xml" forHTTPHeaderField:@"Content-Type"]; +// } + + [mutableRequest setHTTPBody: [parameters dataUsingEncoding:NSUTF8StringEncoding]]; + } + + return mutableRequest; +} + +#pragma mark - NSSecureCoding + +- (instancetype)initWithCoder:(NSCoder *)decoder { + self = [super initWithCoder:decoder]; + if (!self) { + return nil; + } + + return self; +} + +@end + + #pragma mark - @implementation AFJSONRequestSerializer diff --git a/src/ios/CordovaHttpPlugin.m b/src/ios/CordovaHttpPlugin.m index 6f64cd9..8fafed0 100644 --- a/src/ios/CordovaHttpPlugin.m +++ b/src/ios/CordovaHttpPlugin.m @@ -28,7 +28,11 @@ - (void)setRequestSerializer:(NSString*)serializerName forManager:(AFHTTPSessionManager*)manager { if ([serializerName isEqualToString:@"json"]) { manager.requestSerializer = [AFJSONRequestSerializer serializer]; - } else { + } else + if ([serializerName isEqualToString:@"raw"]) { + manager.requestSerializer = [AFRAWRequestSerializer serializer]; + } else + { manager.requestSerializer = [AFHTTPRequestSerializer serializer]; } } diff --git a/www/advanced-http.js b/www/advanced-http.js index 7cb0c6f..cacce92 100644 --- a/www/advanced-http.js +++ b/www/advanced-http.js @@ -33,7 +33,7 @@ */ var pluginId = module.id.slice(0, module.id.indexOf('.')); -var validSerializers = ['urlencoded', 'json']; +var validSerializers = ['urlencoded', 'json', 'raw' ]; var exec = require('cordova/exec'); var angularIntegration = require(pluginId +'.angular-integration'); From c452b29433da89a0a5148f3f9712dd3a407cdd8e Mon Sep 17 00:00:00 2001 From: Sefa Ilkimen Date: Wed, 7 Feb 2018 16:31:51 +0100 Subject: [PATCH 2/5] WIP commit for iOS: - failure handler gets called if an exception occurs in native code - rename "raw" serializer to "utf8" - extract serializer into separate file --- plugin.xml | 4 +- .../synconset/cordovahttp/CordovaHttp.java | 3 +- .../AFNetworking/AFURLRequestSerialization.h | 13 - .../AFNetworking/AFURLRequestSerialization.m | 56 --- src/ios/CordovaHttpPlugin.m | 365 ++++++++++-------- src/ios/TextRequestSerializer.h | 8 + src/ios/TextRequestSerializer.m | 53 +++ test/app-test-definitions.js | 12 + www/advanced-http.js | 9 +- 9 files changed, 289 insertions(+), 234 deletions(-) create mode 100644 src/ios/TextRequestSerializer.h create mode 100644 src/ios/TextRequestSerializer.m diff --git a/plugin.xml b/plugin.xml index c8ef95f..9a976ef 100644 --- a/plugin.xml +++ b/plugin.xml @@ -24,6 +24,7 @@ + @@ -33,6 +34,7 @@ + @@ -64,4 +66,4 @@ - \ No newline at end of file + diff --git a/src/android/com/synconset/cordovahttp/CordovaHttp.java b/src/android/com/synconset/cordovahttp/CordovaHttp.java index 20c0a72..d11e72e 100644 --- a/src/android/com/synconset/cordovahttp/CordovaHttp.java +++ b/src/android/com/synconset/cordovahttp/CordovaHttp.java @@ -144,8 +144,7 @@ abstract class CordovaHttp { if (new String("json").equals(this.getSerializerName())) { request.contentType(request.CONTENT_TYPE_JSON, request.CHARSET_UTF8); request.send(this.getParamsObject().toString()); - } else if (new String("raw").equals(this.getSerializerName())) { - // request.contentType(request.CONTENT_TYPE_JSON, request.CHARSET_UTF8); + } else if (new String("utf8").equals(this.getSerializerName())) { request.send(this.getParamsObject().toString()); } else { diff --git a/src/ios/AFNetworking/AFURLRequestSerialization.h b/src/ios/AFNetworking/AFURLRequestSerialization.h index 8548a3e..694696b 100644 --- a/src/ios/AFNetworking/AFURLRequestSerialization.h +++ b/src/ios/AFNetworking/AFURLRequestSerialization.h @@ -375,19 +375,6 @@ forHTTPHeaderField:(NSString *)field; @end - -#pragma mark - - -/** - `AFRAWRequestSerializer` is a subclass of `AFHTTPRequestSerializer` - */ -@interface AFRAWRequestSerializer : AFHTTPRequestSerializer - - -+ (instancetype)serializer; - -@end - #pragma mark - /** diff --git a/src/ios/AFNetworking/AFURLRequestSerialization.m b/src/ios/AFNetworking/AFURLRequestSerialization.m index 036b80f..a47e2e6 100644 --- a/src/ios/AFNetworking/AFURLRequestSerialization.m +++ b/src/ios/AFNetworking/AFURLRequestSerialization.m @@ -1195,62 +1195,6 @@ typedef enum { @end - -#pragma mark - - -@implementation AFRAWRequestSerializer - -+ (instancetype)serializer -{ - AFRAWRequestSerializer *serializer = [[self alloc] init]; - return serializer; -} - -#pragma mark - AFURLRequestSerialization - -- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request - withParameters:(id)parameters - error:(NSError *__autoreleasing *)error -{ - NSParameterAssert(request); - - if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) { - return [super requestBySerializingRequest:request withParameters:parameters error:error]; - } - - NSMutableURLRequest *mutableRequest = [request mutableCopy]; - - [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) { - if (![request valueForHTTPHeaderField:field]) { - [mutableRequest setValue:value forHTTPHeaderField:field]; - } - }]; - - if (parameters) { -// if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) { -// [mutableRequest setValue:@"application/xml" forHTTPHeaderField:@"Content-Type"]; -// } - - [mutableRequest setHTTPBody: [parameters dataUsingEncoding:NSUTF8StringEncoding]]; - } - - return mutableRequest; -} - -#pragma mark - NSSecureCoding - -- (instancetype)initWithCoder:(NSCoder *)decoder { - self = [super initWithCoder:decoder]; - if (!self) { - return nil; - } - - return self; -} - -@end - - #pragma mark - @implementation AFJSONRequestSerializer diff --git a/src/ios/CordovaHttpPlugin.m b/src/ios/CordovaHttpPlugin.m index 8fafed0..6f34c8b 100644 --- a/src/ios/CordovaHttpPlugin.m +++ b/src/ios/CordovaHttpPlugin.m @@ -1,6 +1,7 @@ #import "CordovaHttpPlugin.h" #import "CDVFile.h" #import "TextResponseSerializer.h" +#import "TextRequestSerializer.h" #import "AFHTTPSessionManager.h" @interface CordovaHttpPlugin() @@ -28,11 +29,9 @@ - (void)setRequestSerializer:(NSString*)serializerName forManager:(AFHTTPSessionManager*)manager { if ([serializerName isEqualToString:@"json"]) { manager.requestSerializer = [AFJSONRequestSerializer serializer]; - } else - if ([serializerName isEqualToString:@"raw"]) { - manager.requestSerializer = [AFRAWRequestSerializer serializer]; - } else - { + } else if ([serializerName isEqualToString:@"utf8"]) { + manager.requestSerializer = [TextRequestSerializer serializer]; + } else { manager.requestSerializer = [AFHTTPRequestSerializer serializer]; } } @@ -77,6 +76,17 @@ } } +- (void)handleException:(NSException*)exception withCommand:(CDVInvokedUrlCommand*)command { + CordovaHttpPlugin* __weak weakSelf = self; + + NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; + [dictionary setValue:exception.userInfo forKey:@"error"]; + [dictionary setObject:[NSNumber numberWithInt:-1] forKey:@"status"]; + + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary]; + [weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; +} + - (NSNumber*)getStatusCode:(NSError*) error { switch ([error code]) { case -1001: @@ -163,19 +173,25 @@ CordovaHttpPlugin* __weak weakSelf = self; manager.responseSerializer = [TextResponseSerializer serializer]; - [manager POST:url parameters:parameters progress:nil success:^(NSURLSessionTask *task, id responseObject) { - NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; - [self handleSuccess:dictionary withResponse:(NSHTTPURLResponse*)task.response andData:responseObject]; - CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary]; - [weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; - } failure:^(NSURLSessionTask *task, NSError *error) { - NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; - [self handleError:dictionary withResponse:(NSHTTPURLResponse*)task.response error:error]; + @try { + [manager POST:url parameters:parameters progress:nil success:^(NSURLSessionTask *task, id responseObject) { + NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; + [self handleSuccess:dictionary withResponse:(NSHTTPURLResponse*)task.response andData:responseObject]; - CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary]; - [weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; - }]; + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary]; + [weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + } failure:^(NSURLSessionTask *task, NSError *error) { + NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; + [self handleError:dictionary withResponse:(NSHTTPURLResponse*)task.response error:error]; + + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary]; + [weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + }]; + } + @catch (NSException *exception) { + [self handleException:exception withCommand:command]; + } } - (void)get:(CDVInvokedUrlCommand*)command { @@ -194,21 +210,26 @@ [self setRedirect: manager]; CordovaHttpPlugin* __weak weakSelf = self; - manager.responseSerializer = [TextResponseSerializer serializer]; - [manager GET:url parameters:parameters progress:nil success:^(NSURLSessionTask *task, id responseObject) { - NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; - [self handleSuccess:dictionary withResponse:(NSHTTPURLResponse*)task.response andData:responseObject]; - CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary]; - [weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; - } failure:^(NSURLSessionTask *task, NSError *error) { - NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; - [self handleError:dictionary withResponse:(NSHTTPURLResponse*)task.response error:error]; + @try { + [manager GET:url parameters:parameters progress:nil success:^(NSURLSessionTask *task, id responseObject) { + NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; + [self handleSuccess:dictionary withResponse:(NSHTTPURLResponse*)task.response andData:responseObject]; - CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary]; - [weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; - }]; + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary]; + [weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + } failure:^(NSURLSessionTask *task, NSError *error) { + NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; + [self handleError:dictionary withResponse:(NSHTTPURLResponse*)task.response error:error]; + + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary]; + [weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + }]; + } + @catch (NSException *exception) { + [self handleException:exception withCommand:command]; + } } - (void)put:(CDVInvokedUrlCommand*)command { @@ -228,19 +249,25 @@ CordovaHttpPlugin* __weak weakSelf = self; manager.responseSerializer = [TextResponseSerializer serializer]; - [manager PUT:url parameters:parameters success:^(NSURLSessionTask *task, id responseObject) { - NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; - [self handleSuccess:dictionary withResponse:(NSHTTPURLResponse*)task.response andData:responseObject]; - CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary]; - [weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; - } failure:^(NSURLSessionTask *task, NSError *error) { - NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; - [self handleError:dictionary withResponse:(NSHTTPURLResponse*)task.response error:error]; + @try { + [manager PUT:url parameters:parameters success:^(NSURLSessionTask *task, id responseObject) { + NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; + [self handleSuccess:dictionary withResponse:(NSHTTPURLResponse*)task.response andData:responseObject]; - CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary]; - [weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; - }]; + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary]; + [weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + } failure:^(NSURLSessionTask *task, NSError *error) { + NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; + [self handleError:dictionary withResponse:(NSHTTPURLResponse*)task.response error:error]; + + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary]; + [weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + }]; + } + @catch (NSException *exception) { + [self handleException:exception withCommand:command]; + } } - (void)patch:(CDVInvokedUrlCommand*)command { @@ -260,19 +287,25 @@ CordovaHttpPlugin* __weak weakSelf = self; manager.responseSerializer = [TextResponseSerializer serializer]; - [manager PATCH:url parameters:parameters success:^(NSURLSessionTask *task, id responseObject) { - NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; - [self handleSuccess:dictionary withResponse:(NSHTTPURLResponse*)task.response andData:responseObject]; - CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary]; - [weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; - } failure:^(NSURLSessionTask *task, NSError *error) { - NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; - [self handleError:dictionary withResponse:(NSHTTPURLResponse*)task.response error:error]; + @try { + [manager PATCH:url parameters:parameters success:^(NSURLSessionTask *task, id responseObject) { + NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; + [self handleSuccess:dictionary withResponse:(NSHTTPURLResponse*)task.response andData:responseObject]; - CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary]; - [weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; - }]; + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary]; + [weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + } failure:^(NSURLSessionTask *task, NSError *error) { + NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; + [self handleError:dictionary withResponse:(NSHTTPURLResponse*)task.response error:error]; + + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary]; + [weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + }]; + } + @catch (NSException *exception) { + [self handleException:exception withCommand:command]; + } } - (void)delete:(CDVInvokedUrlCommand*)command { @@ -290,21 +323,26 @@ [self setRedirect: manager]; CordovaHttpPlugin* __weak weakSelf = self; - manager.responseSerializer = [TextResponseSerializer serializer]; - [manager DELETE:url parameters:parameters success:^(NSURLSessionTask *task, id responseObject) { - NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; - [self handleSuccess:dictionary withResponse:(NSHTTPURLResponse*)task.response andData:responseObject]; - CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary]; - [weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; - } failure:^(NSURLSessionTask *task, NSError *error) { - NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; - [self handleError:dictionary withResponse:(NSHTTPURLResponse*)task.response error:error]; + @try { + [manager DELETE:url parameters:parameters success:^(NSURLSessionTask *task, id responseObject) { + NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; + [self handleSuccess:dictionary withResponse:(NSHTTPURLResponse*)task.response andData:responseObject]; - CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary]; - [weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; - }]; + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary]; + [weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + } failure:^(NSURLSessionTask *task, NSError *error) { + NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; + [self handleError:dictionary withResponse:(NSHTTPURLResponse*)task.response error:error]; + + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary]; + [weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + }]; + } + @catch (NSException *exception) { + [self handleException:exception withCommand:command]; + } } - (void)head:(CDVInvokedUrlCommand*)command { @@ -320,22 +358,27 @@ [self setRedirect: manager]; CordovaHttpPlugin* __weak weakSelf = self; - manager.responseSerializer = [AFHTTPResponseSerializer serializer]; - [manager HEAD:url parameters:parameters success:^(NSURLSessionTask *task) { - NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; - // no 'body' for HEAD request, omitting 'data' - [self handleSuccess:dictionary withResponse:(NSHTTPURLResponse*)task.response andData:nil]; - CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary]; - [weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; - } failure:^(NSURLSessionTask *task, NSError *error) { - NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; - [self handleError:dictionary withResponse:(NSHTTPURLResponse*)task.response error:error]; + @try { + [manager HEAD:url parameters:parameters success:^(NSURLSessionTask *task) { + NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; + // no 'body' for HEAD request, omitting 'data' + [self handleSuccess:dictionary withResponse:(NSHTTPURLResponse*)task.response andData:nil]; - CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary]; - [weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; - }]; + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary]; + [weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + } failure:^(NSURLSessionTask *task, NSError *error) { + NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; + [self handleError:dictionary withResponse:(NSHTTPURLResponse*)task.response error:error]; + + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary]; + [weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + }]; + } + @catch (NSException *exception) { + [self handleException:exception withCommand:command]; + } } - (void)uploadFile:(CDVInvokedUrlCommand*)command { @@ -357,30 +400,36 @@ CordovaHttpPlugin* __weak weakSelf = self; manager.responseSerializer = [TextResponseSerializer serializer]; - [manager POST:url parameters:parameters constructingBodyWithBlock:^(id formData) { - NSError *error; - [formData appendPartWithFileURL:fileURL name:name error:&error]; - if (error) { + + @try { + [manager POST:url parameters:parameters constructingBodyWithBlock:^(id formData) { + NSError *error; + [formData appendPartWithFileURL:fileURL name:name error:&error]; + if (error) { + NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; + [dictionary setObject:[NSNumber numberWithInt:500] forKey:@"status"]; + [dictionary setObject:@"Could not add file to post body." forKey:@"error"]; + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary]; + [weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + return; + } + } progress:nil success:^(NSURLSessionTask *task, id responseObject) { NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; - [dictionary setObject:[NSNumber numberWithInt:500] forKey:@"status"]; - [dictionary setObject:@"Could not add file to post body." forKey:@"error"]; + [self handleSuccess:dictionary withResponse:(NSHTTPURLResponse*)task.response andData:responseObject]; + + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary]; + [weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + } failure:^(NSURLSessionTask *task, NSError *error) { + NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; + [self handleError:dictionary withResponse:(NSHTTPURLResponse*)task.response error:error]; + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary]; [weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; - return; - } - } progress:nil success:^(NSURLSessionTask *task, id responseObject) { - NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; - [self handleSuccess:dictionary withResponse:(NSHTTPURLResponse*)task.response andData:responseObject]; - - CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary]; - [weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; - } failure:^(NSURLSessionTask *task, NSError *error) { - NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; - [self handleError:dictionary withResponse:(NSHTTPURLResponse*)task.response error:error]; - - CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary]; - [weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; - }]; + }]; + } + @catch (NSException *exception) { + [self handleException:exception withCommand:command]; + } } @@ -404,70 +453,76 @@ CordovaHttpPlugin* __weak weakSelf = self; manager.responseSerializer = [AFHTTPResponseSerializer serializer]; - [manager GET:url parameters:parameters progress:nil success:^(NSURLSessionTask *task, id responseObject) { - /* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - * Modified by Andrew Stephan for Sync OnSet - * - */ - // Download response is okay; begin streaming output to file - NSString* parentPath = [filePath stringByDeletingLastPathComponent]; - // create parent directories if needed - NSError *error; - if ([[NSFileManager defaultManager] createDirectoryAtPath:parentPath withIntermediateDirectories:YES attributes:nil error:&error] == NO) { - NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; - [dictionary setObject:[NSNumber numberWithInt:500] forKey:@"status"]; - if (error) { - [dictionary setObject:[NSString stringWithFormat:@"Could not create path to save downloaded file: %@", [error localizedDescription]] forKey:@"error"]; - } else { - [dictionary setObject:@"Could not create path to save downloaded file" forKey:@"error"]; + @try { + [manager GET:url parameters:parameters progress:nil success:^(NSURLSessionTask *task, id responseObject) { + /* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * Modified by Andrew Stephan for Sync OnSet + * + */ + // Download response is okay; begin streaming output to file + NSString* parentPath = [filePath stringByDeletingLastPathComponent]; + + // create parent directories if needed + NSError *error; + if ([[NSFileManager defaultManager] createDirectoryAtPath:parentPath withIntermediateDirectories:YES attributes:nil error:&error] == NO) { + NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; + [dictionary setObject:[NSNumber numberWithInt:500] forKey:@"status"]; + if (error) { + [dictionary setObject:[NSString stringWithFormat:@"Could not create path to save downloaded file: %@", [error localizedDescription]] forKey:@"error"]; + } else { + [dictionary setObject:@"Could not create path to save downloaded file" forKey:@"error"]; + } + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary]; + [weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + return; } - CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary]; - [weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; - return; - } - NSData *data = (NSData *)responseObject; - if (![data writeToFile:filePath atomically:YES]) { + NSData *data = (NSData *)responseObject; + if (![data writeToFile:filePath atomically:YES]) { + NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; + [dictionary setObject:[NSNumber numberWithInt:500] forKey:@"status"]; + [dictionary setObject:@"Could not write the data to the given filePath." forKey:@"error"]; + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary]; + [weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + return; + } + + id filePlugin = [self.commandDelegate getCommandInstance:@"File"]; NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; - [dictionary setObject:[NSNumber numberWithInt:500] forKey:@"status"]; - [dictionary setObject:@"Could not write the data to the given filePath." forKey:@"error"]; + [self handleSuccess:dictionary withResponse:(NSHTTPURLResponse*)task.response andData:nil]; + [dictionary setObject:[filePlugin getDirectoryEntry:filePath isDirectory:NO] forKey:@"file"]; + + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary]; + [weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + } failure:^(NSURLSessionTask *task, NSError *error) { + NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; + [self handleError:dictionary withResponse:(NSHTTPURLResponse*)task.response error:error]; + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary]; [weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; - return; - } - - id filePlugin = [self.commandDelegate getCommandInstance:@"File"]; - NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; - [self handleSuccess:dictionary withResponse:(NSHTTPURLResponse*)task.response andData:nil]; - [dictionary setObject:[filePlugin getDirectoryEntry:filePath isDirectory:NO] forKey:@"file"]; - - CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary]; - [weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; - } failure:^(NSURLSessionTask *task, NSError *error) { - NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; - [self handleError:dictionary withResponse:(NSHTTPURLResponse*)task.response error:error]; - - CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary]; - [weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; - }]; + }]; + } + @catch (NSException *exception) { + [self handleException:exception withCommand:command]; + } } @end diff --git a/src/ios/TextRequestSerializer.h b/src/ios/TextRequestSerializer.h new file mode 100644 index 0000000..56ddad4 --- /dev/null +++ b/src/ios/TextRequestSerializer.h @@ -0,0 +1,8 @@ +#import +#import "AFURLRequestSerialization.h" + +@interface TextRequestSerializer : AFHTTPRequestSerializer + ++ (instancetype)serializer; + +@end diff --git a/src/ios/TextRequestSerializer.m b/src/ios/TextRequestSerializer.m new file mode 100644 index 0000000..31c1c2e --- /dev/null +++ b/src/ios/TextRequestSerializer.m @@ -0,0 +1,53 @@ +#import "TextRequestSerializer.h" + +@implementation TextRequestSerializer + ++ (instancetype)serializer +{ + TextRequestSerializer *serializer = [[self alloc] init]; + return serializer; +} + +#pragma mark - AFURLRequestSerialization + +- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request + withParameters:(id)parameters + error:(NSError *__autoreleasing *)error +{ + NSParameterAssert(request); + + if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) { + return [super requestBySerializingRequest:request withParameters:parameters error:error]; + } + + NSMutableURLRequest *mutableRequest = [request mutableCopy]; + + [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) { + if (![request valueForHTTPHeaderField:field]) { + [mutableRequest setValue:value forHTTPHeaderField:field]; + } + }]; + + if (parameters) { + if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) { + [mutableRequest setValue:@"text/plain; charset=utf-8" forHTTPHeaderField:@"Content-Type"]; + } + + [mutableRequest setHTTPBody: [[parameters valueForKey:@"text"] dataUsingEncoding:NSUTF8StringEncoding]]; + } + + return mutableRequest; +} + +#pragma mark - NSSecureCoding + +- (instancetype)initWithCoder:(NSCoder *)decoder { + self = [super initWithCoder:decoder]; + if (!self) { + return nil; + } + + return self; +} + +@end diff --git a/test/app-test-definitions.js b/test/app-test-definitions.js index 1c687a3..36f5c43 100644 --- a/test/app-test-definitions.js +++ b/test/app-test-definitions.js @@ -8,6 +8,7 @@ const hooks = { const helpers = { acceptAllCerts: function(done) { cordova.plugin.http.acceptAllCerts(true, done, done); }, setJsonSerializer: function(done) { done(cordova.plugin.http.setDataSerializer('json')); }, + setUtf8StringSerializer: function(done) { done(cordova.plugin.http.setDataSerializer('utf8')); }, setUrlEncodedSerializer: function(done) { done(cordova.plugin.http.setDataSerializer('urlencoded')); }, getWithXhr: function(done, url) { var xhr = new XMLHttpRequest(); @@ -382,6 +383,17 @@ const tests = [ cookies.myCookie.should.be.equal('myValue'); cookies.mySecondCookie.should.be.equal('mySecondValue'); } + },{ + description: 'should send UTF-8 encoded raw string correctly (POST)', + expected: 'resolved: {"status": 200, "data": "{\\"data\\": \\"this is a test string\\"...', + before: helpers.setUtf8StringSerializer, + func: function(resolve, reject) { + cordova.plugin.http.post('http://httpbin.org/anything', { text: 'this is a test string' }, {}, resolve, reject); + }, + validationFunc: function(driver, result) { + result.type.should.be.equal('resolved'); + JSON.parse(result.data.data).data.should.eql('this is a test string'); + } } ]; diff --git a/www/advanced-http.js b/www/advanced-http.js index 5119032..16ab2be 100644 --- a/www/advanced-http.js +++ b/www/advanced-http.js @@ -20,12 +20,7 @@ * under the License. * * Modified by Andrew Stephan for Sync OnSet - * Modified by Sefa Ilkimen: - * - added configurable params serializer - * - added put and delete methods - * - using cordova www module pattern - * - some minor improvements - * + * Modified by Sefa Ilkimen */ /* @@ -33,7 +28,7 @@ */ var pluginId = module.id.slice(0, module.id.indexOf('.')); -var validSerializers = ['urlencoded', 'json', 'raw' ]; +var validSerializers = ['urlencoded', 'json', 'utf8' ]; var exec = require('cordova/exec'); var angularIntegration = require(pluginId +'.angular-integration'); From ebef9a2944937e1f166f388543ef29ef610d1bea Mon Sep 17 00:00:00 2001 From: Sefa Ilkimen Date: Wed, 7 Feb 2018 16:57:48 +0100 Subject: [PATCH 3/5] Android: fix interface for "utf8" serializer --- src/android/com/synconset/cordovahttp/CordovaHttp.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/android/com/synconset/cordovahttp/CordovaHttp.java b/src/android/com/synconset/cordovahttp/CordovaHttp.java index d11e72e..0c008c2 100644 --- a/src/android/com/synconset/cordovahttp/CordovaHttp.java +++ b/src/android/com/synconset/cordovahttp/CordovaHttp.java @@ -145,7 +145,8 @@ abstract class CordovaHttp { request.contentType(request.CONTENT_TYPE_JSON, request.CHARSET_UTF8); request.send(this.getParamsObject().toString()); } else if (new String("utf8").equals(this.getSerializerName())) { - request.send(this.getParamsObject().toString()); + request.contentType("text/plain", request.CHARSET_UTF8); + request.send(this.getParamsMap().get("text").toString()); } else { request.form(this.getParamsMap()); From 25b4a283eae8368a5cb0d4359c75841ccf86fec0 Mon Sep 17 00:00:00 2001 From: Sefa Ilkimen Date: Wed, 7 Feb 2018 18:24:39 +0100 Subject: [PATCH 4/5] - add data type validation for data param - support strings as data when "utf8" serializer is configured --- test/app-test-definitions.js | 2 +- www/advanced-http.js | 63 +++++++++++++++++++++++++++++++++--- 2 files changed, 59 insertions(+), 6 deletions(-) diff --git a/test/app-test-definitions.js b/test/app-test-definitions.js index 36f5c43..0fbc3fe 100644 --- a/test/app-test-definitions.js +++ b/test/app-test-definitions.js @@ -388,7 +388,7 @@ const tests = [ expected: 'resolved: {"status": 200, "data": "{\\"data\\": \\"this is a test string\\"...', before: helpers.setUtf8StringSerializer, func: function(resolve, reject) { - cordova.plugin.http.post('http://httpbin.org/anything', { text: 'this is a test string' }, {}, resolve, reject); + cordova.plugin.http.post('http://httpbin.org/anything', 'this is a test string', {}, resolve, reject); }, validationFunc: function(driver, result) { result.type.should.be.equal('resolved'); diff --git a/www/advanced-http.js b/www/advanced-http.js index 16ab2be..d9c8f3c 100644 --- a/www/advanced-http.js +++ b/www/advanced-http.js @@ -38,6 +38,7 @@ var MANDATORY_SUCCESS = 'advanced-http: missing mandatory "onSuccess" callback f var MANDATORY_FAIL = 'advanced-http: missing mandatory "onFail" callback function'; var ADDING_COOKIES_NOT_SUPPORTED = 'advanced-http: "setHeader" does not support adding cookies, please use "setCookie" function instead'; var HEADER_VALUE_MUST_BE_STRING = 'advanced-http: header values must be strings'; +var DATA_TYPE_MISMATCH = 'advanced-http: "data" argument supports only following data types:'; // Thanks Mozilla: https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding#The_.22Unicode_Problem.22 function b64EncodeUnicode(str) { @@ -155,12 +156,64 @@ function getMergedHeaders(url, requestHeaders, predefinedHeaders) { return mergedHeaders; } +function getTypeOf(object) { + // typeof is not working reliably in JS + switch (Object.prototype.toString.call(object)) { + case '[object Array]': + return 'Array'; + case '[object Boolean]': + return 'Boolean'; + case '[object Function]': + return 'Function'; + case '[object Null]': + return 'Null'; + case '[object Number]': + return 'Number'; + case '[object Object]': + return 'Object'; + case '[object String]': + return 'String'; + case '[object Undefined]': + return 'Undefined'; + default: + return 'Unknown'; + } +} + +function getAllowedDataTypes(dataSerializer) { + switch (dataSerializer) { + case 'utf8': + return ['String']; + case 'urlencoded': + return ['Object']; + default: + return ['Array', 'Object']; + } +} + +function getProcessedData(data, dataSerializer) { + data = data || {}; + + var currentDataType = getTypeOf(data); + var allowedDataTypes = getAllowedDataTypes(dataSerializer); + + if (allowedDataTypes.indexOf(currentDataType) === -1) { + throw new Error(DATA_TYPE_MISMATCH + ' ' + allowedDataTypes.join(', ')); + } + + if (dataSerializer === 'utf8') { + data = { text: data }; + } + + return data; +} + function handleMissingCallbacks(successFn, failFn) { - if (Object.prototype.toString.call(successFn) !== '[object Function]') { + if (getTypeOf(successFn) !== 'Function') { throw new Error(MANDATORY_SUCCESS); } - if (Object.prototype.toString.call(failFn) !== '[object Function]') { + if (getTypeOf(failFn) !== 'Function') { throw new Error(MANDATORY_FAIL); } } @@ -232,7 +285,7 @@ var http = { post: function (url, data, headers, success, failure) { handleMissingCallbacks(success, failure); - data = data || {}; + data = getProcessedData(data, this.dataSerializer); headers = getMergedHeaders(url, headers, this.headers); if (!checkHeaders(headers)) { @@ -262,7 +315,7 @@ var http = { put: function (url, data, headers, success, failure) { handleMissingCallbacks(success, failure); - data = data || {}; + data = getProcessedData(data, this.dataSerializer); headers = getMergedHeaders(url, headers, this.headers); if (!checkHeaders(headers)) { @@ -278,7 +331,7 @@ var http = { patch: function (url, data, headers, success, failure) { handleMissingCallbacks(success, failure); - data = data || {}; + data = getProcessedData(data, this.dataSerializer); headers = getMergedHeaders(url, headers, this.headers); if (!checkHeaders(headers)) { From d28eedebd095f1e9d86eceef21677d26daf24e95 Mon Sep 17 00:00:00 2001 From: Sefa Ilkimen Date: Thu, 8 Feb 2018 16:44:16 +0100 Subject: [PATCH 5/5] some refactoring: - new module for messages - new module for helper functions --- plugin.xml | 2 + test/js-mocha-specs.js | 20 ++- www/advanced-http.js | 303 ++++++++----------------------------- www/cookie-handler.js | 2 +- www/helpers.js | 195 ++++++++++++++++++++++++ www/local-storage-store.js | 2 +- www/messages.js | 8 + 7 files changed, 282 insertions(+), 250 deletions(-) create mode 100644 www/helpers.js create mode 100644 www/messages.js diff --git a/plugin.xml b/plugin.xml index 9a976ef..34b42bf 100644 --- a/plugin.xml +++ b/plugin.xml @@ -10,9 +10,11 @@ + + diff --git a/test/js-mocha-specs.js b/test/js-mocha-specs.js index d9a7c5b..4ad48a3 100644 --- a/test/js-mocha-specs.js +++ b/test/js-mocha-specs.js @@ -3,12 +3,18 @@ const mock = require('mock-require'); const path = require('path'); const should = chai.should(); + +const HELPERS_ID = path.resolve(__dirname, '..', 'www', 'helpers'); const PLUGIN_ID = path.resolve(__dirname, '..', 'www', 'advanced-http'); describe('Advanced HTTP www interface', function() { let http = {}; + let helpers = {}; + const noop = () => { /* intentionally doing nothing */ }; + const loadHttp = () => { + mock(`${PLUGIN_ID}.helpers`, mock.reRequire('../www/helpers')); http = mock.reRequire('../www/advanced-http'); }; @@ -19,8 +25,12 @@ describe('Advanced HTTP www interface', function() { global.btoa = decoded => new Buffer(decoded).toString('base64'); mock('cordova/exec', noop); - mock(`${PLUGIN_ID}.angular-integration`, { registerService: noop }); mock(`${PLUGIN_ID}.cookie-handler`, {}); + mock(`${HELPERS_ID}.cookie-handler`, {}); + mock(`${PLUGIN_ID}.messages`, require('../www/messages')); + mock(`${HELPERS_ID}.messages`, require('../www/messages')); + mock(`${PLUGIN_ID}.angular-integration`, { registerService: noop }); + loadHttp(); }); @@ -40,7 +50,7 @@ describe('Advanced HTTP www interface', function() { }); it('resolves global headers correctly #24', () => { - mock(`${PLUGIN_ID}.cookie-handler`, { + mock(`${HELPERS_ID}.cookie-handler`, { getCookieString: () => 'fakeCookieString' }); @@ -59,7 +69,7 @@ describe('Advanced HTTP www interface', function() { }); it('resolves host headers correctly (set without port number) #37', () => { - mock(`${PLUGIN_ID}.cookie-handler`, { + mock(`${HELPERS_ID}.cookie-handler`, { getCookieString: () => 'fakeCookieString' }); @@ -78,7 +88,7 @@ describe('Advanced HTTP www interface', function() { }); it('resolves host headers correctly (set with port number) #37', () => { - mock(`${PLUGIN_ID}.cookie-handler`, { + mock(`${HELPERS_ID}.cookie-handler`, { getCookieString: () => 'fakeCookieString' }); @@ -97,7 +107,7 @@ describe('Advanced HTTP www interface', function() { }); it('resolves request headers correctly', () => { - mock(`${PLUGIN_ID}.cookie-handler`, { + mock(`${HELPERS_ID}.cookie-handler`, { getCookieString: () => 'fakeCookieString' }); diff --git a/www/advanced-http.js b/www/advanced-http.js index d9c8f3c..82076e4 100644 --- a/www/advanced-http.js +++ b/www/advanced-http.js @@ -27,196 +27,13 @@ * An HTTP Plugin for PhoneGap. */ -var pluginId = module.id.slice(0, module.id.indexOf('.')); -var validSerializers = ['urlencoded', 'json', 'utf8' ]; +var pluginId = module.id.slice(0, module.id.lastIndexOf('.')); var exec = require('cordova/exec'); var angularIntegration = require(pluginId +'.angular-integration'); var cookieHandler = require(pluginId + '.cookie-handler'); - -var MANDATORY_SUCCESS = 'advanced-http: missing mandatory "onSuccess" callback function'; -var MANDATORY_FAIL = 'advanced-http: missing mandatory "onFail" callback function'; -var ADDING_COOKIES_NOT_SUPPORTED = 'advanced-http: "setHeader" does not support adding cookies, please use "setCookie" function instead'; -var HEADER_VALUE_MUST_BE_STRING = 'advanced-http: header values must be strings'; -var DATA_TYPE_MISMATCH = 'advanced-http: "data" argument supports only following data types:'; - -// Thanks Mozilla: https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding#The_.22Unicode_Problem.22 -function b64EncodeUnicode(str) { - return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function (match, p1) { - return String.fromCharCode('0x' + p1); - })); -} - -function mergeHeaders(globalHeaders, localHeaders) { - var globalKeys = Object.keys(globalHeaders); - var key; - - for (var i = 0; i < globalKeys.length; i++) { - key = globalKeys[i]; - - if (!localHeaders.hasOwnProperty(key)) { - localHeaders[key] = globalHeaders[key]; - } - } - - return localHeaders; -} - -function checkHeaders(headers) { - var keys = Object.keys(headers); - var key; - - for (var i = 0; i < keys.length; i++) { - key = keys[i]; - - if (typeof headers[key] !== 'string') { - return false; - } - } - - return true; -} - -function onInvalidHeader(handler) { - handler({ - status: 0, - error: HEADER_VALUE_MUST_BE_STRING, - headers: {} - }); -} - -function checkSerializer(serializer) { - serializer = serializer || ''; - serializer = serializer.trim().toLowerCase(); - - if (validSerializers.indexOf(serializer) > -1) { - return serializer; - } - - return serializer[0]; -} - -function resolveCookieString(headers) { - var keys = Object.keys(headers || {}); - - for (var i = 0; i < keys.length; ++i) { - if (keys[i].match(/^set-cookie$/i)) { - return headers[keys[i]]; - } - } - - return null; -} - -function createFileEntry(rawEntry) { - var entry = new (require('cordova-plugin-file.FileEntry'))(); - - entry.isDirectory = rawEntry.isDirectory; - entry.isFile = rawEntry.isFile; - entry.name = rawEntry.name; - entry.fullPath = rawEntry.fullPath; - entry.filesystem = new FileSystem(rawEntry.filesystemName || (rawEntry.filesystem == window.PERSISTENT ? 'persistent' : 'temporary')); - entry.nativeURL = rawEntry.nativeURL; - - return entry; -} - -function injectCookieHandler(url, cb) { - return function(response) { - cookieHandler.setCookieFromString(url, resolveCookieString(response.headers)); - cb(response); - } -} - -function injectFileEntryHandler(cb) { - return function(response) { - cb(createFileEntry(response.file)); - } -} - -function getCookieHeader(url) { - return { Cookie: cookieHandler.getCookieString(url) }; -} - -function getMatchingHostHeaders(url, headersList) { - var matches = url.match(/^https?\:\/\/([^\/?#]+)(?:[\/?#]|$)/i); - var domain = matches && matches[1]; - - return headersList[domain] || null; -} - -function getMergedHeaders(url, requestHeaders, predefinedHeaders) { - var globalHeaders = predefinedHeaders['*'] || {}; - var hostHeaders = getMatchingHostHeaders(url, predefinedHeaders) || {}; - var mergedHeaders = mergeHeaders(globalHeaders, hostHeaders); - - mergedHeaders = mergeHeaders(mergedHeaders, requestHeaders); - mergedHeaders = mergeHeaders(mergedHeaders, getCookieHeader(url)); - - return mergedHeaders; -} - -function getTypeOf(object) { - // typeof is not working reliably in JS - switch (Object.prototype.toString.call(object)) { - case '[object Array]': - return 'Array'; - case '[object Boolean]': - return 'Boolean'; - case '[object Function]': - return 'Function'; - case '[object Null]': - return 'Null'; - case '[object Number]': - return 'Number'; - case '[object Object]': - return 'Object'; - case '[object String]': - return 'String'; - case '[object Undefined]': - return 'Undefined'; - default: - return 'Unknown'; - } -} - -function getAllowedDataTypes(dataSerializer) { - switch (dataSerializer) { - case 'utf8': - return ['String']; - case 'urlencoded': - return ['Object']; - default: - return ['Array', 'Object']; - } -} - -function getProcessedData(data, dataSerializer) { - data = data || {}; - - var currentDataType = getTypeOf(data); - var allowedDataTypes = getAllowedDataTypes(dataSerializer); - - if (allowedDataTypes.indexOf(currentDataType) === -1) { - throw new Error(DATA_TYPE_MISMATCH + ' ' + allowedDataTypes.join(', ')); - } - - if (dataSerializer === 'utf8') { - data = { text: data }; - } - - return data; -} - -function handleMissingCallbacks(successFn, failFn) { - if (getTypeOf(successFn) !== 'Function') { - throw new Error(MANDATORY_SUCCESS); - } - - if (getTypeOf(failFn) !== 'Function') { - throw new Error(MANDATORY_FAIL); - } -} +var helpers = require(pluginId + '.helpers'); +var messages = require(pluginId + '.messages'); var http = { headers: {}, @@ -224,10 +41,10 @@ var http = { sslPinning: false, timeoutInSeconds: 60.0, getBasicAuthHeader: function (username, password) { - return {'Authorization': 'Basic ' + b64EncodeUnicode(username + ':' + password)}; + return {'Authorization': 'Basic ' + helpers.b64EncodeUnicode(username + ':' + password)}; }, useBasicAuth: function (username, password) { - this.setHeader('*', 'Authorization', 'Basic ' + b64EncodeUnicode(username + ':' + password)); + this.setHeader('*', 'Authorization', 'Basic ' + helpers.b64EncodeUnicode(username + ':' + password)); }, setHeader: function () { // this one is for being backward compatible @@ -242,18 +59,18 @@ var http = { } if (header.toLowerCase() === 'cookie') { - throw new Error(ADDING_COOKIES_NOT_SUPPORTED); + throw new Error(messages.ADDING_COOKIES_NOT_SUPPORTED); } if (typeof value !== 'string') { - throw new Error(HEADER_VALUE_MUST_BE_STRING); + throw new Error(messages.HEADER_VALUE_MUST_BE_STRING); } this.headers[host] = this.headers[host] || {}; this.headers[host][header] = value; }, setDataSerializer: function (serializer) { - this.dataSerializer = checkSerializer(serializer); + this.dataSerializer = helpers.checkSerializer(serializer); }, setCookie: function (url, cookie, options) { cookieHandler.setCookie(url, cookie, options); @@ -280,127 +97,127 @@ var http = { return exec(success, failure, 'CordovaHttpPlugin', 'disableRedirect', [disable]); }, validateDomainName: function (validate, success, failure) { - failure('advanced-http: "validateDomainName" is no more supported, please see change log for further info'); + failure(messages.DEPRECATED_VDN); }, post: function (url, data, headers, success, failure) { - handleMissingCallbacks(success, failure); + helpers.handleMissingCallbacks(success, failure); - data = getProcessedData(data, this.dataSerializer); - headers = getMergedHeaders(url, headers, this.headers); + data = helpers.getProcessedData(data, this.dataSerializer); + headers = helpers.getMergedHeaders(url, headers, this.headers); - if (!checkHeaders(headers)) { - return onInvalidHeader(failure); + if (!helpers.checkHeaders(headers)) { + return helpers.onInvalidHeader(failure); } - var onSuccess = injectCookieHandler(url, success); - var onFail = injectCookieHandler(url, failure); + var onSuccess = helpers.injectCookieHandler(url, success); + var onFail = helpers.injectCookieHandler(url, failure); return exec(onSuccess, onFail, 'CordovaHttpPlugin', 'post', [url, data, this.dataSerializer, headers, this.timeoutInSeconds]); }, get: function (url, params, headers, success, failure) { - handleMissingCallbacks(success, failure); + helpers.handleMissingCallbacks(success, failure); params = params || {}; - headers = getMergedHeaders(url, headers, this.headers); + headers = helpers.getMergedHeaders(url, headers, this.headers); - if (!checkHeaders(headers)) { - return onInvalidHeader(failure); + if (!helpers.checkHeaders(headers)) { + return helpers.onInvalidHeader(failure); } - var onSuccess = injectCookieHandler(url, success); - var onFail = injectCookieHandler(url, failure); + var onSuccess = helpers.injectCookieHandler(url, success); + var onFail = helpers.injectCookieHandler(url, failure); return exec(onSuccess, onFail, 'CordovaHttpPlugin', 'get', [url, params, headers, this.timeoutInSeconds]); }, put: function (url, data, headers, success, failure) { - handleMissingCallbacks(success, failure); + helpers.handleMissingCallbacks(success, failure); - data = getProcessedData(data, this.dataSerializer); - headers = getMergedHeaders(url, headers, this.headers); + data = helpers.getProcessedData(data, this.dataSerializer); + headers = helpers.getMergedHeaders(url, headers, this.headers); - if (!checkHeaders(headers)) { - return onInvalidHeader(failure); + if (!helpers.checkHeaders(headers)) { + return helpers.onInvalidHeader(failure); } - var onSuccess = injectCookieHandler(url, success); - var onFail = injectCookieHandler(url, failure); + var onSuccess = helpers.injectCookieHandler(url, success); + var onFail = helpers.injectCookieHandler(url, failure); return exec(onSuccess, onFail, 'CordovaHttpPlugin', 'put', [url, data, this.dataSerializer, headers, this.timeoutInSeconds]); }, patch: function (url, data, headers, success, failure) { - handleMissingCallbacks(success, failure); + helpers.handleMissingCallbacks(success, failure); - data = getProcessedData(data, this.dataSerializer); - headers = getMergedHeaders(url, headers, this.headers); + data = helpers.getProcessedData(data, this.dataSerializer); + headers = helpers.getMergedHeaders(url, headers, this.headers); - if (!checkHeaders(headers)) { - return onInvalidHeader(failure); + if (!helpers.checkHeaders(headers)) { + return helpers.onInvalidHeader(failure); } - var onSuccess = injectCookieHandler(url, success); - var onFail = injectCookieHandler(url, failure); + var onSuccess = helpers.injectCookieHandler(url, success); + var onFail = helpers.injectCookieHandler(url, failure); return exec(onSuccess, onFail, 'CordovaHttpPlugin', 'patch', [url, data, this.dataSerializer, headers, this.timeoutInSeconds]); }, delete: function (url, params, headers, success, failure) { - handleMissingCallbacks(success, failure); + helpers.handleMissingCallbacks(success, failure); params = params || {}; - headers = getMergedHeaders(url, headers, this.headers); + headers = helpers.getMergedHeaders(url, headers, this.headers); - if (!checkHeaders(headers)) { - return onInvalidHeader(failure); + if (!helpers.checkHeaders(headers)) { + return helpers.onInvalidHeader(failure); } - var onSuccess = injectCookieHandler(url, success); - var onFail = injectCookieHandler(url, failure); + var onSuccess = helpers.injectCookieHandler(url, success); + var onFail = helpers.injectCookieHandler(url, failure); return exec(onSuccess, onFail, 'CordovaHttpPlugin', 'delete', [url, params, headers, this.timeoutInSeconds]); }, head: function (url, params, headers, success, failure) { - handleMissingCallbacks(success, failure); + helpers.handleMissingCallbacks(success, failure); params = params || {}; - headers = getMergedHeaders(url, headers, this.headers); + headers = helpers.getMergedHeaders(url, headers, this.headers); - if (!checkHeaders(headers)) { - return onInvalidHeader(failure); + if (!helpers.checkHeaders(headers)) { + return helpers.onInvalidHeader(failure); } - var onSuccess = injectCookieHandler(url, success); - var onFail = injectCookieHandler(url, failure); + var onSuccess = helpers.injectCookieHandler(url, success); + var onFail = helpers.injectCookieHandler(url, failure); return exec(onSuccess, onFail, 'CordovaHttpPlugin', 'head', [url, params, headers, this.timeoutInSeconds]); }, uploadFile: function (url, params, headers, filePath, name, success, failure) { - handleMissingCallbacks(success, failure); + helpers.handleMissingCallbacks(success, failure); params = params || {}; - headers = getMergedHeaders(url, headers, this.headers); + headers = helpers.getMergedHeaders(url, headers, this.headers); - if (!checkHeaders(headers)) { - return onInvalidHeader(failure); + if (!helpers.checkHeaders(headers)) { + return helpers.onInvalidHeader(failure); } - var onSuccess = injectCookieHandler(url, success); - var onFail = injectCookieHandler(url, failure); + var onSuccess = helpers.injectCookieHandler(url, success); + var onFail = helpers.injectCookieHandler(url, failure); return exec(onSuccess, onFail, 'CordovaHttpPlugin', 'uploadFile', [url, params, headers, filePath, name, this.timeoutInSeconds]); }, downloadFile: function (url, params, headers, filePath, success, failure) { - handleMissingCallbacks(success, failure); + helpers.handleMissingCallbacks(success, failure); params = params || {}; - headers = getMergedHeaders(url, headers, this.headers); + headers = helpers.getMergedHeaders(url, headers, this.headers); - if (!checkHeaders(headers)) { - return onInvalidHeader(failure); + if (!helpers.checkHeaders(headers)) { + return helpers.onInvalidHeader(failure); } - var onSuccess = injectCookieHandler(url, injectFileEntryHandler(success)); - var onFail = injectCookieHandler(url, failure); + var onSuccess = helpers.injectCookieHandler(url, helpers.injectFileEntryHandler(success)); + var onFail = helpers.injectCookieHandler(url, failure); return exec(onSuccess, onFail, 'CordovaHttpPlugin', 'downloadFile', [url, params, headers, filePath, this.timeoutInSeconds]); } diff --git a/www/cookie-handler.js b/www/cookie-handler.js index 2399f96..0f97780 100644 --- a/www/cookie-handler.js +++ b/www/cookie-handler.js @@ -1,4 +1,4 @@ -var pluginId = module.id.slice(0, module.id.indexOf('.')); +var pluginId = module.id.slice(0, module.id.lastIndexOf('.')); var ToughCookie = require(pluginId + '.tough-cookie'); var WebStorageCookieStore = require(pluginId + '.local-storage-store'); diff --git a/www/helpers.js b/www/helpers.js new file mode 100644 index 0000000..6ecc609 --- /dev/null +++ b/www/helpers.js @@ -0,0 +1,195 @@ +var pluginId = module.id.slice(0, module.id.lastIndexOf('.')); +var cookieHandler = require(pluginId + '.cookie-handler'); +var messages = require(pluginId + '.messages'); + +var validSerializers = ['urlencoded', 'json', 'utf8' ]; + +module.exports = { + b64EncodeUnicode: b64EncodeUnicode, + checkHeaders: checkHeaders, + onInvalidHeader: onInvalidHeader, + checkSerializer: checkSerializer, + injectCookieHandler: injectCookieHandler, + injectFileEntryHandler: injectFileEntryHandler, + getMergedHeaders: getMergedHeaders, + getProcessedData: getProcessedData, + handleMissingCallbacks: handleMissingCallbacks +}; + +// Thanks Mozilla: https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding#The_.22Unicode_Problem.22 +function b64EncodeUnicode(str) { + return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function (match, p1) { + return String.fromCharCode('0x' + p1); + })); +} + +function mergeHeaders(globalHeaders, localHeaders) { + var globalKeys = Object.keys(globalHeaders); + var key; + + for (var i = 0; i < globalKeys.length; i++) { + key = globalKeys[i]; + + if (!localHeaders.hasOwnProperty(key)) { + localHeaders[key] = globalHeaders[key]; + } + } + + return localHeaders; +} + +function checkHeaders(headers) { + var keys = Object.keys(headers); + var key; + + for (var i = 0; i < keys.length; i++) { + key = keys[i]; + + if (typeof headers[key] !== 'string') { + return false; + } + } + + return true; +} + +function onInvalidHeader(handler) { + handler({ + status: 0, + error: messages.HEADER_VALUE_MUST_BE_STRING, + headers: {} + }); +} + +function checkSerializer(serializer) { + serializer = serializer || ''; + serializer = serializer.trim().toLowerCase(); + + if (validSerializers.indexOf(serializer) > -1) { + return serializer; + } + + return serializer[0]; +} + +function resolveCookieString(headers) { + var keys = Object.keys(headers || {}); + + for (var i = 0; i < keys.length; ++i) { + if (keys[i].match(/^set-cookie$/i)) { + return headers[keys[i]]; + } + } + + return null; +} + +function createFileEntry(rawEntry) { + var entry = new (require('cordova-plugin-file.FileEntry'))(); + + entry.isDirectory = rawEntry.isDirectory; + entry.isFile = rawEntry.isFile; + entry.name = rawEntry.name; + entry.fullPath = rawEntry.fullPath; + entry.filesystem = new FileSystem(rawEntry.filesystemName || (rawEntry.filesystem == window.PERSISTENT ? 'persistent' : 'temporary')); + entry.nativeURL = rawEntry.nativeURL; + + return entry; +} + +function injectCookieHandler(url, cb) { + return function(response) { + cookieHandler.setCookieFromString(url, resolveCookieString(response.headers)); + cb(response); + } +} + +function injectFileEntryHandler(cb) { + return function(response) { + cb(createFileEntry(response.file)); + } +} + +function getCookieHeader(url) { + return { Cookie: cookieHandler.getCookieString(url) }; +} + +function getMatchingHostHeaders(url, headersList) { + var matches = url.match(/^https?\:\/\/([^\/?#]+)(?:[\/?#]|$)/i); + var domain = matches && matches[1]; + + return headersList[domain] || null; +} + +function getMergedHeaders(url, requestHeaders, predefinedHeaders) { + var globalHeaders = predefinedHeaders['*'] || {}; + var hostHeaders = getMatchingHostHeaders(url, predefinedHeaders) || {}; + var mergedHeaders = mergeHeaders(globalHeaders, hostHeaders); + + mergedHeaders = mergeHeaders(mergedHeaders, requestHeaders); + mergedHeaders = mergeHeaders(mergedHeaders, getCookieHeader(url)); + + return mergedHeaders; +} + +// typeof is not working reliably in JS +function getTypeOf(object) { + switch (Object.prototype.toString.call(object)) { + case '[object Array]': + return 'Array'; + case '[object Boolean]': + return 'Boolean'; + case '[object Function]': + return 'Function'; + case '[object Null]': + return 'Null'; + case '[object Number]': + return 'Number'; + case '[object Object]': + return 'Object'; + case '[object String]': + return 'String'; + case '[object Undefined]': + return 'Undefined'; + default: + return 'Unknown'; + } +} + +function getAllowedDataTypes(dataSerializer) { + switch (dataSerializer) { + case 'utf8': + return ['String']; + case 'urlencoded': + return ['Object']; + default: + return ['Array', 'Object']; + } +} + +function getProcessedData(data, dataSerializer) { + data = data || {}; + + var currentDataType = getTypeOf(data); + var allowedDataTypes = getAllowedDataTypes(dataSerializer); + + if (allowedDataTypes.indexOf(currentDataType) === -1) { + throw new Error(messages.DATA_TYPE_MISMATCH + ' ' + allowedDataTypes.join(', ')); + } + + if (dataSerializer === 'utf8') { + data = { text: data }; + } + + return data; +} + +function handleMissingCallbacks(successFn, failFn) { + if (getTypeOf(successFn) !== 'Function') { + throw new Error(messages.MANDATORY_SUCCESS); + } + + if (getTypeOf(failFn) !== 'Function') { + throw new Error(messages.MANDATORY_FAIL); + } +} diff --git a/www/local-storage-store.js b/www/local-storage-store.js index 044e7ca..4c5b7b0 100644 --- a/www/local-storage-store.js +++ b/www/local-storage-store.js @@ -30,7 +30,7 @@ 'use strict'; -var pluginId = module.id.slice(0, module.id.indexOf('.')); +var pluginId = module.id.slice(0, module.id.lastIndexOf('.')); var ToughCookie = require(pluginId + '.tough-cookie'); var _ = require(pluginId + '.lodash'); diff --git a/www/messages.js b/www/messages.js new file mode 100644 index 0000000..6708841 --- /dev/null +++ b/www/messages.js @@ -0,0 +1,8 @@ +module.exports = { + ADDING_COOKIES_NOT_SUPPORTED: 'advanced-http: "setHeader" does not support adding cookies, please use "setCookie" function instead', + DATA_TYPE_MISMATCH: 'advanced-http: "data" argument supports only following data types:', + DEPRECATED_VDN: 'advanced-http: "validateDomainName" is no more supported, please see change log for further info', + HEADER_VALUE_MUST_BE_STRING: 'advanced-http: header values must be strings', + MANDATORY_SUCCESS: 'advanced-http: missing mandatory "onSuccess" callback function', + MANDATORY_FAIL: 'advanced-http: missing mandatory "onFail" callback function' +};