From ed4c1f81773469d2e003a882d2f6452a2043ecc2 Mon Sep 17 00:00:00 2001 From: Eddy Verbruggen Date: Mon, 13 Mar 2017 12:18:39 +0100 Subject: [PATCH 01/12] Added .js files so we can install from GitHub --- .gitignore | 5 +- README.md | 3 + demo/tsconfig.json | 16 +++-- https.android.js | 172 +++++++++++++++++++++++++++++++++++++++++++++ https.ios.js | 140 ++++++++++++++++++++++++++++++++++++ 5 files changed, 326 insertions(+), 10 deletions(-) create mode 100644 https.android.js create mode 100644 https.ios.js diff --git a/.gitignore b/.gitignore index 8cb8969..22418d0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,7 @@ -*.js *.js.map *.log -!scripts/*.js +demo/hooks/**/*.js demo/app/*.js -!demo/karma.conf.js -!demo/app/tests/*.js demo/*.d.ts demo/lib demo/platforms diff --git a/README.md b/README.md index 7bd16c5..bed3f8a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,7 @@ # NativeScript-HTTPS + +> Forked by Eddy Verbruggen to be able to install from GitHub (include .js files), just in casenpm isbehind and we desperately need the latest changes. + ### The definitive way to hit HTTP based APIs in Nativescript. Easily integrate the most reliable native networking libraries with the latest and greatest HTTPS security features. #### A drop-in replacement for the [default http module](https://docs.nativescript.org/cookbook/http#get-response-status-code). diff --git a/demo/tsconfig.json b/demo/tsconfig.json index 0f17014..c1cc152 100644 --- a/demo/tsconfig.json +++ b/demo/tsconfig.json @@ -5,12 +5,13 @@ "declaration": false, "removeComments": true, "noLib": false, - "skipLibCheck": true, - "skipDefaultLibCheck": true, + "skipLibCheck": true, + "skipDefaultLibCheck": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, - "lib": ["es2016"], - "sourceMap": true, + "lib": [ + "es2016" + ], "pretty": true, "allowUnreachableCode": false, "allowUnusedLabels": false, @@ -20,8 +21,11 @@ "noImplicitReturns": true, "noImplicitUseStrict": false, "noFallthroughCasesInSwitch": true, - "typeRoots": ["./node_modules/@types", "./node_modules"], - "types": [] + "typeRoots": [ + "./node_modules/@types", + "./node_modules" + ], + "types": [] }, "exclude": [ "node_modules", diff --git a/https.android.js b/https.android.js new file mode 100644 index 0000000..6620e2a --- /dev/null +++ b/https.android.js @@ -0,0 +1,172 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var types_1 = require("utils/types"); +var peer = { + enabled: false, + allowInvalidCertificates: false, + validatesDomainName: true, +}; +function enableSSLPinning(options) { + if (!peer.host && !peer.certificate) { + var certificate = void 0; + var inputStream = void 0; + try { + var file = new java.io.File(options.certificate); + inputStream = new java.io.FileInputStream(file); + var x509Certificate = java.security.cert.CertificateFactory.getInstance('X509').generateCertificate(inputStream); + peer.x509Certificate = x509Certificate; + certificate = okhttp3.CertificatePinner.pin(x509Certificate); + inputStream.close(); + } + catch (error) { + try { + if (inputStream) { + inputStream.close(); + } + } + catch (e) { } + console.error('nativescript-https > enableSSLPinning error', error); + return; + } + peer.host = options.host; + peer.certificate = certificate; + if (options.allowInvalidCertificates == true) { + peer.allowInvalidCertificates = true; + } + if (options.validatesDomainName == false) { + peer.validatesDomainName = false; + } + } + peer.enabled = true; + getClient(true); + console.log('nativescript-https > Enabled SSL pinning'); +} +exports.enableSSLPinning = enableSSLPinning; +function disableSSLPinning() { + peer.enabled = false; + getClient(true); + console.log('nativescript-https > Disabled SSL pinning'); +} +exports.disableSSLPinning = disableSSLPinning; +console.info('nativescript-https > Disabled SSL pinning by default'); +var Client; +function getClient(reload) { + if (reload === void 0) { reload = false; } + if (Client && reload == false) { + return Client; + } + var client = new okhttp3.OkHttpClient.Builder(); + if (peer.enabled == true) { + if (peer.host || peer.certificate) { + var spec = okhttp3.ConnectionSpec.MODERN_TLS; + client.connectionSpecs(java.util.Collections.singletonList(spec)); + var pinner = new okhttp3.CertificatePinner.Builder(); + pinner.add(peer.host, [peer.certificate]); + client.certificatePinner(pinner.build()); + if (peer.allowInvalidCertificates == false) { + try { + var x509Certificate = peer.x509Certificate; + var keyStore = java.security.KeyStore.getInstance(java.security.KeyStore.getDefaultType()); + keyStore.load(null, null); + keyStore.setCertificateEntry('CA', x509Certificate); + var keyManagerFactory = javax.net.ssl.KeyManagerFactory.getInstance('X509'); + keyManagerFactory.init(keyStore, null); + var keyManagers = keyManagerFactory.getKeyManagers(); + var trustManagerFactory = javax.net.ssl.TrustManagerFactory.getInstance(javax.net.ssl.TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init(keyStore); + var sslContext = javax.net.ssl.SSLContext.getInstance('TLS'); + sslContext.init(keyManagers, trustManagerFactory.getTrustManagers(), new java.security.SecureRandom()); + client.sslSocketFactory(sslContext.getSocketFactory()); + } + catch (error) { + console.error('nativescript-https > client.allowInvalidCertificates error', error); + } + } + if (peer.validatesDomainName == true) { + try { + client.hostnameVerifier(new javax.net.ssl.HostnameVerifier({ + verify: function (hostname, session) { + var pp = session.getPeerPrincipal().getName(); + var hv = javax.net.ssl.HttpsURLConnection.getDefaultHostnameVerifier(); + return (hv.verify(peer.host, session) && + peer.host == hostname && + peer.host == session.getPeerHost() && + pp.indexOf(peer.host) != -1); + }, + })); + } + catch (error) { + console.error('nativescript-https > client.validatesDomainName error', error); + } + } + } + else { + console.warn('nativescript-https > Undefined host or certificate. SSL pinning NOT working!!!'); + } + } + Client = client.build(); + return Client; +} +function request(opts) { + return new Promise(function (resolve, reject) { + try { + var client = getClient(); + var request_1 = new okhttp3.Request.Builder(); + request_1.url(opts.url); + if (opts.headers) { + Object.keys(opts.headers).forEach(function (key) { + request_1.addHeader(key, opts.headers[key]); + }); + } + var methods = { + 'GET': 'get', + 'HEAD': 'head', + 'DELETE': 'delete', + 'POST': 'post', + 'PUT': 'put', + 'PATCH': 'patch', + }; + if ((['GET', 'HEAD'].indexOf(opts.method) != -1) + || + (opts.method == 'DELETE' && !types_1.isDefined(opts.body))) { + request_1[methods[opts.method]](); + } + else { + var type = opts.headers['Content-Type'] || 'application/json'; + var body = opts.body || {}; + try { + body = JSON.stringify(body); + } + catch (e) { } + request_1[methods[opts.method]](okhttp3.RequestBody.create(okhttp3.MediaType.parse(type), body)); + } + client.newCall(request_1.build()).enqueue(new okhttp3.Callback({ + onResponse: function (task, response) { + var content = response.body().string(); + try { + content = JSON.parse(content); + } + catch (e) { } + var statusCode = response.code(); + var headers = {}; + var heads = response.headers(); + var i, len = heads.size(); + for (i = 0; i < len; i++) { + var key = heads.name(i); + var value = heads.value(i); + headers[key] = value; + } + resolve({ content: content, statusCode: statusCode, headers: headers }); + }, + onFailure: function (task, error) { + reject(error); + }, + })); + } + catch (error) { + reject(error); + } + }); +} +exports.request = request; +//# sourceMappingURL=https.android.js.map \ No newline at end of file diff --git a/https.ios.js b/https.ios.js new file mode 100644 index 0000000..139eafa --- /dev/null +++ b/https.ios.js @@ -0,0 +1,140 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var types_1 = require("utils/types"); +var policies = { + def: AFSecurityPolicy.defaultPolicy(), + secured: false, +}; +policies.def.allowInvalidCertificates = true; +policies.def.validatesDomainName = false; +function enableSSLPinning(options) { + if (!policies.secure) { + policies.secure = AFSecurityPolicy.policyWithPinningMode(1); + var allowInvalidCertificates = (types_1.isDefined(options.allowInvalidCertificates)) ? options.allowInvalidCertificates : false; + policies.secure.allowInvalidCertificates = allowInvalidCertificates; + var validatesDomainName = (types_1.isDefined(options.validatesDomainName)) ? options.validatesDomainName : true; + policies.secure.validatesDomainName = validatesDomainName; + var data = NSData.dataWithContentsOfFile(options.certificate); + policies.secure.pinnedCertificates = NSSet.setWithObject(data); + } + policies.secured = true; + console.log('nativescript-https > Enabled SSL pinning'); +} +exports.enableSSLPinning = enableSSLPinning; +function disableSSLPinning() { + policies.secured = false; + console.log('nativescript-https > Disabled SSL pinning'); +} +exports.disableSSLPinning = disableSSLPinning; +console.info('nativescript-https > Disabled SSL pinning by default'); +function AFSuccess(resolve, task, data) { + var content; + if (data && data.class) { + if (data.enumerateKeysAndObjectsUsingBlock || data.class().name == 'NSArray') { + var serial = NSJSONSerialization.dataWithJSONObjectOptionsError(data, 1); + content = NSString.alloc().initWithDataEncoding(serial, NSUTF8StringEncoding).toString(); + } + else if (data.class().name == 'NSData') { + content = NSString.alloc().initWithDataEncoding(data, NSASCIIStringEncoding).toString(); + } + else { + content = data; + } + try { + content = JSON.parse(content); + } + catch (e) { } + } + else { + content = data; + } + resolve({ task: task, content: content }); +} +function AFFailure(resolve, reject, task, error) { + var data = error.userInfo.valueForKey(AFNetworkingOperationFailingURLResponseDataErrorKey); + var body = NSString.alloc().initWithDataEncoding(data, NSUTF8StringEncoding).toString(); + try { + body = JSON.parse(body); + } + catch (e) { } + var content = { + body: body, + description: error.description, + reason: error.localizedDescription, + url: error.userInfo.objectForKey('NSErrorFailingURLKey').description + }; + if (policies.secured == true) { + content.description = 'nativescript-https > Invalid SSL certificate! ' + content.description; + } + var reason = error.localizedDescription; + resolve({ task: task, content: content, reason: reason }); +} +function request(opts) { + return new Promise(function (resolve, reject) { + try { + var manager_1 = AFHTTPSessionManager.manager(); + if (opts.headers && opts.headers['Content-Type'] == 'application/json') { + manager_1.requestSerializer = AFJSONRequestSerializer.serializer(); + manager_1.responseSerializer = AFJSONResponseSerializer.serializerWithReadingOptions(4); + } + else { + manager_1.requestSerializer = AFHTTPRequestSerializer.serializer(); + manager_1.responseSerializer = AFHTTPResponseSerializer.serializer(); + } + manager_1.requestSerializer.allowsCellularAccess = true; + manager_1.securityPolicy = (policies.secured == true) ? policies.secure : policies.def; + manager_1.requestSerializer.timeoutInterval = 10; + var heads_1 = opts.headers; + if (heads_1) { + Object.keys(heads_1).forEach(function (key) { + manager_1.requestSerializer.setValueForHTTPHeaderField(heads_1[key], key); + }); + } + var dict_1 = null; + if (opts.body) { + var cont_1 = opts.body; + if (types_1.isObject(cont_1)) { + dict_1 = NSMutableDictionary.new(); + Object.keys(cont_1).forEach(function (key) { + dict_1.setValueForKey(cont_1[key], key); + }); + } + } + var methods = { + 'GET': 'GETParametersSuccessFailure', + 'POST': 'POSTParametersSuccessFailure', + 'PUT': 'PUTParametersSuccessFailure', + 'DELETE': 'DELETEParametersSuccessFailure', + 'PATCH': 'PATCHParametersSuccessFailure', + 'HEAD': 'HEADParametersSuccessFailure', + }; + manager_1[methods[opts.method]](opts.url, dict_1, function success(task, data) { + AFSuccess(resolve, task, data); + }, function failure(task, error) { + AFFailure(resolve, reject, task, error); + }); + } + catch (error) { + reject(error); + } + }).then(function (AFResponse) { + var sendi = { + content: AFResponse.content, + headers: {}, + }; + var response = AFResponse.task.response; + if (!types_1.isNullOrUndefined(response)) { + sendi.statusCode = response.statusCode; + var dict = response.allHeaderFields; + dict.enumerateKeysAndObjectsUsingBlock(function (k, v) { + sendi.headers[k] = v; + }); + } + if (AFResponse.reason) { + sendi.reason = AFResponse.reason; + } + return Promise.resolve(sendi); + }); +} +exports.request = request; +//# sourceMappingURL=https.ios.js.map \ No newline at end of file From b030b88b722c61996e3c52162e8a38dc2e2a98ad Mon Sep 17 00:00:00 2001 From: Eddy Verbruggen Date: Thu, 23 Mar 2017 14:41:39 +0100 Subject: [PATCH 02/12] Prevent crashes on Android with larger responses #1 --- https.android.js | 2 ++ https.android.ts | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/https.android.js b/https.android.js index 6620e2a..29f108b 100644 --- a/https.android.js +++ b/https.android.js @@ -107,6 +107,7 @@ function getClient(reload) { Client = client.build(); return Client; } +var strictModeThreadPolicyPermitAll = new android.os.StrictMode.ThreadPolicy.Builder().permitAll().build(); function request(opts) { return new Promise(function (resolve, reject) { try { @@ -140,6 +141,7 @@ function request(opts) { catch (e) { } request_1[methods[opts.method]](okhttp3.RequestBody.create(okhttp3.MediaType.parse(type), body)); } + android.os.StrictMode.setThreadPolicy(strictModeThreadPolicyPermitAll); client.newCall(request_1.build()).enqueue(new okhttp3.Callback({ onResponse: function (task, response) { var content = response.body().string(); diff --git a/https.android.ts b/https.android.ts index 4d015a9..f893bcb 100644 --- a/https.android.ts +++ b/https.android.ts @@ -163,6 +163,11 @@ function getClient(reload: boolean = false): okhttp3.OkHttpClient { return Client } +// We have to allow networking on the main thread because larger responses will crash the app with an NetworkOnMainThreadException. +// Note that it would be better to offload it to an AsyncTask but that has to run natively to work properly. +// No time for that now, and actually it only concerns the '.string()' call of response.body().string() below. +const strictModeThreadPolicyPermitAll = new android.os.StrictMode.ThreadPolicy.Builder().permitAll().build() + export function request(opts: Https.HttpsRequestOptions): Promise { return new Promise(function(resolve, reject) { try { @@ -205,6 +210,9 @@ export function request(opts: Https.HttpsRequestOptions): Promise Date: Thu, 31 Jan 2019 22:15:15 +0100 Subject: [PATCH 03/12] - bumped AFNetworking to fix a few issues - added support for large responses on Android - added typings (and scripts to generate those) --- README.md | 2 - demo/app/App_Resources/iOS/build.xcconfig | 2 + demo/app/assets/httpbin.org.cer | Bin 1378 -> 1379 bytes demo/app/main-page.ts | 24 +- demo/app/main-page.xml | 8 +- demo/package.json | 3 + demo/tsconfig.json | 2 - src/https.android.ts | 41 +- src/https.common.ts | 13 +- src/https.ios.ts | 9 +- src/package.json | 24 +- src/platforms/android/include.gradle | 7 - .../android/typings/android-declarations.d.ts | 1 + src/platforms/android/typings/okhttp3.d.ts | 2620 +++++++++++++++++ src/platforms/ios/Podfile | 4 +- .../ios/typings/objc!AFNetworking.d.ts | 820 ++++++ src/references.d.ts | 4 + 17 files changed, 3515 insertions(+), 69 deletions(-) create mode 100644 src/platforms/android/typings/android-declarations.d.ts create mode 100644 src/platforms/android/typings/okhttp3.d.ts create mode 100644 src/platforms/ios/typings/objc!AFNetworking.d.ts diff --git a/README.md b/README.md index c41c1f1..303a0c2 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,5 @@ # NativeScript-HTTPS -> Forked by Eddy Verbruggen to be able to install from GitHub (include .js files), just in casenpm isbehind and we desperately need the latest changes. - ### The definitive way to hit HTTP based APIs in Nativescript. Easily integrate the most reliable native networking libraries with the latest and greatest HTTPS security features. #### A drop-in replacement for the [default http module](https://docs.nativescript.org/cookbook/http#get-response-status-code). diff --git a/demo/app/App_Resources/iOS/build.xcconfig b/demo/app/App_Resources/iOS/build.xcconfig index 0562055..f2c79fb 100644 --- a/demo/app/App_Resources/iOS/build.xcconfig +++ b/demo/app/App_Resources/iOS/build.xcconfig @@ -3,3 +3,5 @@ // CODE_SIGN_IDENTITY = iPhone Distribution ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + +DEVELOPMENT_TEAM = 8Q5F6M3TNS diff --git a/demo/app/assets/httpbin.org.cer b/demo/app/assets/httpbin.org.cer index e003b593221ea98360bad1f4612c35827bed4a28..ec44c38c2f5825eb227d7dfc49617b061be38348 100644 GIT binary patch delta 915 zcmV;E18n@_3gZePFoFeNFoFa}paTK{0s;~P#PUKPwUO-G_IUlkF$*#DX^|md8aXgA zFgP+ZF*YzWS{Ds5IWROZI5IOaHZU`hn>v5bNdl&WE^$(WSpZEw-^9O@Co)F3xOYT3 zL3B;I8{B0N0cN0N$wCoXr-Mu3F5kh&j7;^ZB0XIaEZMl;L|^7omKNqZs?bL>8?FMK zB!brDu27ADl%G&74O3~Lo--yLXIN@8O_!|i-#HA);l*XXdIy8rH~1~MT^Upxv+{qt z4m~>K68Wg*ff|6P6E5Q6gf_e13ba3dtR|u1y&v_?MOqfK1RFmHfUT z1@BgbhFOx@$gIyE>Lpi9uKi|Vo!ywNlE@C*+0Og_^JZ@{-oD`R>ht`z^0P+aaw?o6 zsRg4QsJ(kqKP@e_hl0&UZ*E_fPnr)X{Q?6400E&_xuTS;)l7;@7ULg~l%5{6o@>u=iHdxR0wDnN52J0~D>*gmBlbgN zsu2#(nzg1)_~uFJe_my7Nni9%FbxI?Duzgg_YDC73k3iJf&l>l9VoN{>1WG@nan^C zd(O+_nL}2Aig!?4-5u6Y?79&g#;~B#6dnssI!#nMBfBzYt0Z-Jk-`FN4{~9F3 zQ{zhTJd+<`?g_hO?ZGs^cg)<-O-zeWJy(8asX>|(e79_9n-`{t0lW1G*vqdsu#QvK z2pi+O?Qr{vI;s%k7+4N-YA`ZeTceko-qE&Nf08MFqYS81UMfmRWN$_~QwZ&NHx4_s pGAXS>(I#M89g=wSu8n(wOCx|w)L2v%Xez?gh?hsjO-rmTHtuj}nTr4b delta 914 zcmV;D18w}{3gQYOFoFeMFoFa|paTK{0s;~PTVf3ow461w10kip1)V^<5|JTb8aOdA zFgY?aH8C(bS{Ds5IWRIXH!?FdF)%rin>v5|JuF!EFFVzqtFk5C<1vUHOpayzY7SR`td#~5Dr2d{2|$MljfmCAT53Tt&$rf zv1AZ`lP79Oy_Q@71Rf`CitekFC)}W+b$lRn@Au<;nHv!YtocA#?Kz&PVlC8tX&--m z4!Ui3P2|H(kv`*Lav@{|61QkvG|$2XP+&P|)aLGCSOn3oxeI?f5(&b%)yKiA``Nhc z_8l_frnCILeMSA0mRl`C9Fil zSaEKL@;Ufn9^~So3-UgeP~pr(!6pyL0|Em900EzD+U1s0oHi}1Ofzs^aO$N0Pg^G0B=)vtTFI08Q7Ts zq)`?BcNH8F*#f};DF%(Q2%0z96951KX8Rf9MgRZ=0{})aL;@gIljW#7Sv?p1iNqPZ zJCe@Cn!tBvdX=fYJj74$j+8$FAXu@tl}gU889NjVFGg+;$3b4-e`3z)+(nI-UK+L` z9y$Pa04Y3CmQ=_&WxA?R{8$Inw{O2#dMR?;q{9sK zrM9}+r!-ZZcrKIC`WhhdTg0*kl zEe<%*q@@)ZZ+UDlHe`8@i_rXgN`>F?qve8O`7vnLj^!nv3UBkM+>HoHMOi-bTyo(% z58%lcK_7-it+DRSuX$&sJwNjZvZ`z+r~B2+pB!9`4|iQ2f9C zjEh-z@hZ=yc>mspy^ubJNKfUz^GdpAnd$sLBEj|~-TV2Ep951v8+ z4sav)yCI}b#prIntrMGL{^CxFriIVxa$<38nxCS0=i1a>6|at8j6$Sf-#WY)=lUZO oXC|(Whqx$y90vgeTc+$M4t!1=@RoeEtC!MDY0~tPq+*uibtEX5Z2$lO diff --git a/demo/app/main-page.ts b/demo/app/main-page.ts index b1c20fd..cbfd532 100644 --- a/demo/app/main-page.ts +++ b/demo/app/main-page.ts @@ -1,20 +1,32 @@ - import * as Observable from 'tns-core-modules/data/observable' import * as Page from 'tns-core-modules/ui/page' import * as fs from 'tns-core-modules/file-system' import * as dialogs from 'tns-core-modules/ui/dialogs' import * as Https from 'nativescript-https' - - export function onNavigatingTo(args: Page.NavigatedData) { let page = args.object as Page.Page page.bindingContext = Observable.fromObject({ enabled: false }) } -function getRequest(url: string) { +function getRequest(url: string, allowLargeResponse = false) { + Https.request({ + url, + method: 'GET', + allowLargeResponse + }).then(function(response) { + console.log('Https.request response', response) + }).catch(function(error) { + console.error('Https.request error', error) + dialogs.alert(error) + }) +} + +function postRequest(url: string, body: any) { Https.request({ - url, method: 'GET', + url, + method: 'POST', + body }).then(function(response) { console.log('Https.request response', response) }).catch(function(error) { @@ -23,7 +35,9 @@ function getRequest(url: string) { }) } +export function postHttpbin() { postRequest('https://httpbin.org/post', {"foo": "bar", "baz": undefined, "plaz": null}) } export function getHttpbin() { getRequest('https://httpbin.org/get') } +export function getHttpbinLargeResponse() { getRequest('https://httpbin.org/bytes/100000', true) } export function getMockbin() { getRequest('https://mockbin.com/request') } export function enableSSLPinning(args: Observable.EventData) { diff --git a/demo/app/main-page.xml b/demo/app/main-page.xml index 879c258..df02365 100644 --- a/demo/app/main-page.xml +++ b/demo/app/main-page.xml @@ -5,10 +5,14 @@