From 7ab4b634ca7d5acfc3e9b4f9fbc6f48e5373cc08 Mon Sep 17 00:00:00 2001 From: Pavel Chuchuva Date: Sun, 12 Jan 2020 08:18:02 +1100 Subject: [PATCH 1/7] Add support for sending 'raw' requests on Android request.data can be `Uint8Array` or `ArrayBuffer`. The plugin will send it as is, without any processing. --- src/android/com/silkimen/cordovahttp/CordovaHttpBase.java | 2 ++ www/helpers.js | 6 +++++- www/js-util.js | 2 ++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/android/com/silkimen/cordovahttp/CordovaHttpBase.java b/src/android/com/silkimen/cordovahttp/CordovaHttpBase.java index 5617508..575d57a 100644 --- a/src/android/com/silkimen/cordovahttp/CordovaHttpBase.java +++ b/src/android/com/silkimen/cordovahttp/CordovaHttpBase.java @@ -159,6 +159,8 @@ abstract class CordovaHttpBase implements Runnable { request.send(this.data.toString()); } else if ("utf8".equals(this.serializer)) { request.send(((JSONObject) this.data).getString("text")); + } else if ("raw".equals(this.serializer)) { + request.send(Base64.decode((String)this.data, Base64.DEFAULT)); } else if ("urlencoded".equals(this.serializer)) { request.form(JsonUtils.getObjectMap((JSONObject) this.data)); } else if ("multipart".equals(this.serializer)) { diff --git a/www/helpers.js b/www/helpers.js index 4b4ed28..ff6a88f 100644 --- a/www/helpers.js +++ b/www/helpers.js @@ -1,5 +1,5 @@ module.exports = function init(global, jsUtil, cookieHandler, messages, base64, errorCodes, dependencyValidator, ponyfills) { - var validSerializers = ['urlencoded', 'json', 'utf8', 'multipart']; + var validSerializers = ['urlencoded', 'json', 'utf8', 'raw', 'multipart']; var validCertModes = ['default', 'nocheck', 'pinned', 'legacy']; var validClientAuthModes = ['none', 'systemstore', 'buffer']; var validHttpMethods = ['get', 'put', 'post', 'patch', 'head', 'delete', 'upload', 'download']; @@ -365,6 +365,8 @@ module.exports = function init(global, jsUtil, cookieHandler, messages, base64, return ['Object']; case 'json': return ['Array', 'Object']; + case 'raw': + return ['Uint8Array', 'ArrayBuffer']; default: return []; } @@ -400,6 +402,8 @@ module.exports = function init(global, jsUtil, cookieHandler, messages, base64, switch (dataSerializer) { case 'utf8': return cb({ text: data }); + case 'raw': + return cb(currentDataType === 'Uint8Array' ? data.buffer : data); case 'multipart': return processFormData(data, cb); default: diff --git a/www/js-util.js b/www/js-util.js index e379dba..593dcc0 100644 --- a/www/js-util.js +++ b/www/js-util.js @@ -6,6 +6,8 @@ module.exports = { return 'Array'; case '[object Blob]': return 'Blob'; + case '[object Uint8Array]': + return 'Uint8Array'; case '[object ArrayBuffer]': return 'ArrayBuffer'; case '[object Boolean]': From 937010bd4ecda455758ebca64a2b270ee44a3d4e Mon Sep 17 00:00:00 2001 From: Pavel Chuchuva Date: Tue, 14 Jan 2020 16:09:47 +1100 Subject: [PATCH 2/7] Add support for sending 'raw' requests on iOS too --- plugin.xml | 2 ++ src/ios/BinaryRequestSerializer.h | 8 +++++ src/ios/BinaryRequestSerializer.m | 49 +++++++++++++++++++++++++++++++ src/ios/CordovaHttpPlugin.m | 3 ++ 4 files changed, 62 insertions(+) create mode 100644 src/ios/BinaryRequestSerializer.h create mode 100644 src/ios/BinaryRequestSerializer.m diff --git a/plugin.xml b/plugin.xml index 78d43a9..1bf0f73 100644 --- a/plugin.xml +++ b/plugin.xml @@ -31,6 +31,7 @@ + @@ -43,6 +44,7 @@ + diff --git a/src/ios/BinaryRequestSerializer.h b/src/ios/BinaryRequestSerializer.h new file mode 100644 index 0000000..b9930a8 --- /dev/null +++ b/src/ios/BinaryRequestSerializer.h @@ -0,0 +1,8 @@ +#import +#import "AFURLRequestSerialization.h" + +@interface BinaryRequestSerializer : AFHTTPRequestSerializer + ++ (instancetype)serializer; + +@end diff --git a/src/ios/BinaryRequestSerializer.m b/src/ios/BinaryRequestSerializer.m new file mode 100644 index 0000000..16da68f --- /dev/null +++ b/src/ios/BinaryRequestSerializer.m @@ -0,0 +1,49 @@ +#import "BinaryRequestSerializer.h" + +@implementation BinaryRequestSerializer + ++ (instancetype)serializer +{ + BinaryRequestSerializer *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) { + [mutableRequest setHTTPBody: parameters]; + } + + return mutableRequest; +} + +#pragma mark - NSSecureCoding + +- (instancetype)initWithCoder:(NSCoder *)decoder { + self = [super initWithCoder:decoder]; + if (!self) { + return nil; + } + + return self; +} + +@end diff --git a/src/ios/CordovaHttpPlugin.m b/src/ios/CordovaHttpPlugin.m index f77f0d9..df31126 100644 --- a/src/ios/CordovaHttpPlugin.m +++ b/src/ios/CordovaHttpPlugin.m @@ -1,5 +1,6 @@ #import "CordovaHttpPlugin.h" #import "CDVFile.h" +#import "BinaryRequestSerializer.h" #import "BinaryResponseSerializer.h" #import "TextResponseSerializer.h" #import "TextRequestSerializer.h" @@ -31,6 +32,8 @@ manager.requestSerializer = [AFJSONRequestSerializer serializer]; } else if ([serializerName isEqualToString:@"utf8"]) { manager.requestSerializer = [TextRequestSerializer serializer]; + } else if ([serializerName isEqualToString:@"raw"]) { + manager.requestSerializer = [BinaryRequestSerializer serializer]; } else { manager.requestSerializer = [AFHTTPRequestSerializer serializer]; } From 7fd7ab223c6897094b1259d81ea9cbc59132cd32 Mon Sep 17 00:00:00 2001 From: Pavel Chuchuva Date: Sat, 18 Jan 2020 08:23:43 +1100 Subject: [PATCH 3/7] Add tests for 'raw' serializer --- test/e2e-specs.js | 29 ++++++++++++++++++++++++++++- test/js-specs.js | 9 +++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/test/e2e-specs.js b/test/e2e-specs.js index 83f753d..937c2e3 100644 --- a/test/e2e-specs.js +++ b/test/e2e-specs.js @@ -843,7 +843,34 @@ const tests = [ const b64Logo = rawLogo.toString('base64'); JSON.parse(result.data.data).files.CordovaLogo.should.be.equal('data:image/png;base64,' + b64Logo); } - } + }, + { + description: 'should send raw byte array correctly (POST)', + expected: 'resolved: {"isArrayBuffer:true,"data":[1,2,3,4,5,6,7],"byteLength":7}', + func: function (resolve, reject) { + var url = 'http://httpbin.org/anything'; + var options = { + method: 'post', + serializer: 'raw', + data: new Uint8Array([1,2,3,4,5,6,7]), + responseType: 'arraybuffer' + }; + var success = function (response) { + resolve({ + isArrayBuffer: response.data.constructor === ArrayBuffer, + data: new Uint8Array(response.data), + byteLength: response.data.byteLength + }); + }; + cordova.plugin.http.sendRequest(url, options, success, reject); + }, + validationFunc: function (driver, result) { + result.type.should.be.equal('resolved'); + result.data.isArrayBuffer.should.be.equal(true); + result.data.should.be.eql(new Uint8Array([1,2,3,4,5,6,7])); + result.data.byteLength.should.be.equal(7); + } + }, // TODO: not ready yet // { diff --git a/test/js-specs.js b/test/js-specs.js index 8a2698c..2507dd6 100644 --- a/test/js-specs.js +++ b/test/js-specs.js @@ -589,6 +589,15 @@ describe('Common helpers', function () { cb(); }); }); + + it('processes data correctly when serializer "raw" is configured', (cb) => { + const byteArray = new Uint8Array([1,2,3]); + helpers.processData(byteArray, 'raw', (data) => { + data.should.be.a('ArrayBuffer'); + data.should.be.equal(byteArray.buffer); + cb(); + }) + }); }); }); From b74ad73742a05d98d6cd22a6010367ab9942a9de Mon Sep 17 00:00:00 2001 From: Pavel Chuchuva Date: Sat, 18 Jan 2020 08:44:58 +1100 Subject: [PATCH 4/7] Add documentation for 'raw' serializer --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c90d745..c2d7c85 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,7 @@ You can choose one of these: * `json`: send data as JSON encoded content in body (content type "application/json") * `utf8`: send data as plain UTF8 encoded string in body (content type "plain/text") * `multipart`: send FormData objects as multipart content in body (content type "multipart/form-data") +* `raw`: send data as is, without any processing. Data should be `Uint8Array` or `ArrayBuffer`. This defaults to `urlencoded`. You can also override the default content type headers by specifying your own headers (see [setHeader](#setHeader)). From 99c7f5d3315612500103b3e5573ff50a39c4be7c Mon Sep 17 00:00:00 2001 From: Sefa Ilkimen Date: Sun, 26 Jan 2020 17:50:18 +0100 Subject: [PATCH 5/7] add default content type for `raw` serializer --- src/android/com/silkimen/cordovahttp/CordovaHttpBase.java | 2 ++ src/ios/BinaryRequestSerializer.m | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/src/android/com/silkimen/cordovahttp/CordovaHttpBase.java b/src/android/com/silkimen/cordovahttp/CordovaHttpBase.java index 575d57a..a86c46f 100644 --- a/src/android/com/silkimen/cordovahttp/CordovaHttpBase.java +++ b/src/android/com/silkimen/cordovahttp/CordovaHttpBase.java @@ -143,6 +143,8 @@ abstract class CordovaHttpBase implements Runnable { request.contentType("application/json", "UTF-8"); } else if ("utf8".equals(this.serializer)) { request.contentType("text/plain", "UTF-8"); + } else if ("raw".equals(this.serializer)) { + request.contentType("application/octet-stream"); } else if ("urlencoded".equals(this.serializer)) { // intentionally left blank, because content type is set in HttpRequest.form() } else if ("multipart".equals(this.serializer)) { diff --git a/src/ios/BinaryRequestSerializer.m b/src/ios/BinaryRequestSerializer.m index 16da68f..ada4df8 100644 --- a/src/ios/BinaryRequestSerializer.m +++ b/src/ios/BinaryRequestSerializer.m @@ -29,6 +29,10 @@ }]; if (parameters) { + if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) { + [mutableRequest setValue:@"application/octet-stream" forHTTPHeaderField:@"Content-Type"]; + } + [mutableRequest setHTTPBody: parameters]; } From faffe0e0784c97971bffdb44754327d57de0070c Mon Sep 17 00:00:00 2001 From: Sefa Ilkimen Date: Sun, 26 Jan 2020 17:50:40 +0100 Subject: [PATCH 6/7] fix e2e tests for `raw` serializer --- test/e2e-specs.js | 40 +++++++++++++++++++--------------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/test/e2e-specs.js b/test/e2e-specs.js index 937c2e3..02312a2 100644 --- a/test/e2e-specs.js +++ b/test/e2e-specs.js @@ -34,6 +34,7 @@ const helpers = { setUtf8StringSerializer: function (resolve) { resolve(cordova.plugin.http.setDataSerializer('utf8')); }, setUrlEncodedSerializer: function (resolve) { resolve(cordova.plugin.http.setDataSerializer('urlencoded')); }, setMultipartSerializer: function (resolve) { resolve(cordova.plugin.http.setDataSerializer('multipart')); }, + setRawSerializer: function(resolve) { resolve(cordova.plugin.http.setDataSerializer('raw')); }, disableFollowingRedirect: function (resolve) { resolve(cordova.plugin.http.setFollowRedirect(false)); }, enableFollowingRedirect: function(resolve) { resolve(cordova.plugin.http.setFollowRedirect(true)); }, getWithXhr: function (done, url, type) { @@ -845,30 +846,27 @@ const tests = [ } }, { - description: 'should send raw byte array correctly (POST)', - expected: 'resolved: {"isArrayBuffer:true,"data":[1,2,3,4,5,6,7],"byteLength":7}', + description: 'should send raw byte array correctly (POST) #291', + expected: 'resolved: {"status":200,"data:application/octet-stream;base64,iVBORw0KGgoAAAANSUhEUg ...', + before: helpers.setRawSerializer, func: function (resolve, reject) { - var url = 'http://httpbin.org/anything'; - var options = { - method: 'post', - serializer: 'raw', - data: new Uint8Array([1,2,3,4,5,6,7]), - responseType: 'arraybuffer' - }; - var success = function (response) { - resolve({ - isArrayBuffer: response.data.constructor === ArrayBuffer, - data: new Uint8Array(response.data), - byteLength: response.data.byteLength - }); - }; - cordova.plugin.http.sendRequest(url, options, success, reject); + helpers.getWithXhr(function(buffer) { + cordova.plugin.http.post('http://httpbin.org/anything', buffer, {}, resolve, reject); + }, './res/cordova_logo.png', 'arraybuffer'); }, validationFunc: function (driver, result) { - result.type.should.be.equal('resolved'); - result.data.isArrayBuffer.should.be.equal(true); - result.data.should.be.eql(new Uint8Array([1,2,3,4,5,6,7])); - result.data.byteLength.should.be.equal(7); + helpers.checkResult(result, 'resolved'); + result.data.status.should.be.equal(200); + + // httpbin.org encodes posted binaries in base64 and echoes them back + // therefore we need to check for base64 string with mime type prefix + const fs = require('fs'); + const rawLogo = fs.readFileSync('./test/e2e-app-template/www/res/cordova_logo.png'); + const b64Logo = rawLogo.toString('base64'); + const parsed = JSON.parse(result.data.data); + + parsed.headers['Content-Type'].should.be.equal('application/octet-stream'); + parsed.data.should.be.equal('data:application/octet-stream;base64,' + b64Logo); } }, From 33fea67603832390d07fba71f6413a2964feb73c Mon Sep 17 00:00:00 2001 From: Sefa Ilkimen Date: Sun, 26 Jan 2020 17:55:04 +0100 Subject: [PATCH 7/7] update readme and changelog --- CHANGELOG.md | 4 ++++ README.md | 20 +++++++++++++++----- package.json | 4 ++-- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 492604c..f4ef441 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 2.4.0 + +- Feature #291: add support for sending 'raw' requests (thanks to jachstet-sea and chuchuva) + ## 2.3.1 - Fixed #275: getAllCookies() is broken because of a typo (thanks ath0mas) diff --git a/README.md b/README.md index c2d7c85..246709a 100644 --- a/README.md +++ b/README.md @@ -91,11 +91,21 @@ cordova.plugin.http.setDataSerializer('urlencoded'); ``` You can choose one of these: -* `urlencoded`: send data as url encoded content in body (content type "application/x-www-form-urlencoded") -* `json`: send data as JSON encoded content in body (content type "application/json") -* `utf8`: send data as plain UTF8 encoded string in body (content type "plain/text") -* `multipart`: send FormData objects as multipart content in body (content type "multipart/form-data") -* `raw`: send data as is, without any processing. Data should be `Uint8Array` or `ArrayBuffer`. +* `urlencoded`: send data as url encoded content in body + * default content type "application/x-www-form-urlencoded" + * data must be an dictionary style `Object` +* `json`: send data as JSON encoded content in body + * default content type "application/json" + * data must be an `Array` or an dictionary style `Object` +* `utf8`: send data as plain UTF8 encoded string in body + * default content type "plain/text" + * data must be a `String` +* `multipart`: send FormData objects as multipart content in body + * default content type "multipart/form-data" + * data must be an `FormData` instance +* `raw`: send data as is, without any processing + * default content type "application/octet-stream" + * data must be an `Uint8Array` or an `ArrayBuffer` This defaults to `urlencoded`. You can also override the default content type headers by specifying your own headers (see [setHeader](#setHeader)). diff --git a/package.json b/package.json index 3e9bec9..12de35a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cordova-plugin-advanced-http", - "version": "2.3.1", + "version": "2.4.0", "description": "Cordova / Phonegap plugin for communicating with HTTP servers using SSL pinning", "scripts": { "updatecert": "node ./scripts/update-e2e-server-cert.js && node ./scripts/update-e2e-client-cert.js", @@ -50,7 +50,7 @@ "pvsaikrishna", "cvillerm", "hideov", - "Mobisys" + "silkimen" ], "license": "MIT", "bugs": {