├── .gitignore ├── src ├── www │ └── ios │ │ ├── fetch-bootstrap.js │ │ ├── formdata-polyfill.js │ │ ├── whatwg-fetch-2.0.3.js │ │ └── xhr-polyfill.js └── ios │ ├── CDVWKWebViewFileXhr.h │ └── CDVWKWebViewFileXhr.m ├── tests ├── www │ └── assets │ │ ├── customers.json │ │ └── customers.html ├── README.md ├── plugin.xml └── tests.js ├── package.json ├── LICENSE ├── .github └── ISSUE_TEMPLATE │ └── bug_report.md ├── README.md └── plugin.xml /.gitignore: -------------------------------------------------------------------------------- 1 | /nbproject/private/ -------------------------------------------------------------------------------- /src/www/ios/fetch-bootstrap.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | (function () 4 | { 5 | var _fetch = window.fetch; 6 | window.fetch = undefined; 7 | })(); 8 | -------------------------------------------------------------------------------- /tests/www/assets/customers.json: -------------------------------------------------------------------------------- 1 | { 2 | "comment": "Copyright (c) 2014, 2016, Oracle and/or its affiliates. The Universal Permissive License (UPL), Version 1.0", 3 | "name": "anonymous" 4 | } 5 | -------------------------------------------------------------------------------- /tests/www/assets/customers.html: -------------------------------------------------------------------------------- 1 | 5 |
6 |

Customers Content Area

7 |
8 | To change the content of this section, you will make edits to the customers.html file located in the /js/views folder. 9 |
10 |
11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@globules-io/cordova-plugin-ios-xhr", 3 | "version": "1.2.4", 4 | "description": "Cordova iOS 6+ File XHR Plugin", 5 | "cordova": { 6 | "id": "@globules-io/cordova-plugin-ios-xhr", 7 | "platforms": [ 8 | "ios" 9 | ] 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/globules-io/cordova-plugin-ios-xhr" 14 | }, 15 | "keywords": [ 16 | "cordova", 17 | "wkwebview", 18 | "ecosystem:cordova", 19 | "cordova-ios" 20 | ], 21 | "scripts": { 22 | "test": "npm run jshint", 23 | "jshint": "node node_modules/jshint/bin/jshint www && node node_modules/jshint/bin/jshint src && node node_modules/jshint/bin/jshint tests" 24 | }, 25 | "author": "globules.io", 26 | "license": "MIT", 27 | "devDependencies": { 28 | "jshint": "^2.6.0" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Eric 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: BUG 5 | labels: '' 6 | assignees: globules-io 7 | 8 | --- 9 | **Before you start** 10 | Found a bug? Something you can fix? Please do a PR. 11 | Do not hijack a bug report or comment on closed ones. 12 | Be nice and polite and respect our time! Mistakes happen, 13 | keep you passive aggressive comments for yourself!! 14 | 15 | ALL ISSUES CREATED WITHOUT A PROPER BUG REPORT WILL BE CLOSED 16 | 17 | **Describe the bug** 18 | A clear and concise description of what the bug is. 19 | 20 | **Paste your preferences** 21 | To make sure you've set the plugin 22 | 23 | **Expected behavior** 24 | A clear and concise description of what you expected to happen. 25 | 26 | **Screenshots** 27 | If applicable, add screenshots to help explain your problem. 28 | 29 | **Desktop (please complete the following information):** 30 | - OS: [e.g. iOS] 31 | - Browser [e.g. chrome, safari] 32 | - Version [e.g. 22] 33 | 34 | **Smartphone (please complete the following information):** 35 | - Device: [e.g. iPhone6] 36 | - OS: [e.g. iOS8.1] 37 | - Browser [e.g. stock browser, safari] 38 | - Version [e.g. 22] 39 | 40 | **Additional context** 41 | Add any other context about the problem here. 42 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | # Running Plugin Tests 2 | Our plugin uses the [Cordova Plugin Test Framework](https://github.com/apache/cordova-plugin-test-framework/blob/master/README.md) for its test automation strategy. 3 | 4 | ## Steps to Run 5 | + Clone the repository. The tests project is a sub folder so we need to install the plugins from the local file system. 6 | ``` 7 | git clone http://git-wip-us.apache.org/repos/asf/cordova-plugin-test-framework.git 8 | ``` 9 | + Create a cordova project using the cordova CLI: 10 | ``` 11 | cordova create test oj.test TestFileXhr 12 | cd test 13 | cordova platform add ios 14 | ``` 15 | + Install the test harness: 16 | ``` 17 | cordova plugin add http://git-wip-us.apache.org/repos/asf/cordova-plugin-test-framework.git 18 | ``` 19 | + Change the start page in "`test/config.xml`" with ``. 20 | + Install "`cordova-plugin-wkwebview-file-xhr`" plugin and associated tests: 21 | ``` 22 | cordova plugin add ../cordova-plugin-wkwebview-file-xhr 23 | cordova plugin add ../cordova-plugin-wkwebview-file-xhr/tests 24 | ``` 25 | + Open the Xcode project "`test/platforms/ios/TestFileXhr.xcodeproj`" in the Xcode IDE and run. Alternatively, use the cordova CLI: 26 | ``` 27 | cordova build 28 | cordova run ios 29 | ``` 30 | -------------------------------------------------------------------------------- /tests/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 22 | 23 | 26 | Cordova WKWebView File XHR Plugin Tests 27 | UPL 1.0 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/ios/CDVWKWebViewFileXhr.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016, 2017 Oracle and/or its affiliates. 3 | * 4 | * The Universal Permissive License (UPL), Version 1.0 5 | * 6 | * Subject to the condition set forth below, permission is hereby granted to any person obtaining a copy of this 7 | * software, associated documentation and/or data (collectively the "Software"), free of charge and under any and 8 | * all copyright rights in the Software, and any and all patent rights owned or freely licensable by each 9 | * licensor hereunder covering either (i) the unmodified Software as contributed to or provided by such licensor, 10 | * or (ii) the Larger Works (as defined below), to deal in both 11 | * 12 | * 13 | * (a) the Software, and 14 | * 15 | * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if one is included with the Software 16 | * (each a “Larger Work” to which the Software is contributed by such licensors), 17 | * 18 | * without restriction, including without limitation the rights to copy, create derivative works of, display, 19 | * perform, and distribute the Software and make, use, sell, offer for sale, import, export, have made, and 20 | * have sold the Software and the Larger Work(s), and to sublicense the foregoing rights on either these or other 21 | * terms. 22 | * 23 | * This license is subject to the following condition: 24 | * 25 | * The above copyright notice and either this complete permission notice or at a minimum a reference to the UPL 26 | * must be included in all copies or substantial portions of the Software. 27 | * 28 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 29 | * TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 30 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 31 | * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 32 | * DEALINGS IN THE SOFTWARE. 33 | */ 34 | 35 | #import 36 | #import 37 | #import 38 | 39 | NS_ASSUME_NONNULL_BEGIN 40 | 41 | @interface CDVWKWebViewFileXhr : CDVPlugin 42 | 43 | /* Exec API */ 44 | - (void)readAsText:(CDVInvokedUrlCommand*)command; 45 | - (void)readAsArrayBuffer:(CDVInvokedUrlCommand*)command; 46 | - (void) getConfig:(CDVInvokedUrlCommand*)command; 47 | 48 | @property (nonatomic, assign) BOOL allowsInsecureLoads; 49 | @property (nonatomic, strong) NSString * interceptRemoteRequests; 50 | 51 | @end 52 | 53 | NS_ASSUME_NONNULL_END 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cordova-plugin-ios-xhr 2 | ## cordova-ios 6+ File XHR Plugin 3 | 4 | ### ARCHIVED 5 | This plugin is not needed anymore 6 | 7 | ### About 8 | > Originally, this plugin was a merge of two other plugins, and made compatible for cordova-ios 6+. 9 | 10 | > [cordova-plugin-wkwebview-file-xhr](https://github.com/oracle/cordova-plugin-wkwebview-file-xhr) 11 | 12 | > [cordova-plugin-wkwebviewxhrfix](https://github.com/TheMattRay/cordova-plugin-wkwebviewxhrfix) 13 | 14 | > It's been since remodified and the code from [TheMattRay](https://github.com/TheMattRay) has now been entirely removed. It is kept here as original reference. 15 | > This could be turned into a PR to [cordova-plugin-wkwebview-file-xhr](https://github.com/oracle/cordova-plugin-wkwebview-file-xhr), if time allows it, or if anybody wants to go at it! All credits go to Oracle. 16 | 17 | ### Install 18 | 19 | > Install latest release 20 | 21 | cordova plugin add @globules-io/cordova-plugin-ios-xhr 22 | 23 | > Or install from github master 24 | 25 | cordova plugin add https://github.com/globules-io/cordova-plugin-ios-xhr 26 | 27 | ### Uninstall 28 | 29 | cordova plugin rm @globules-io/cordova-plugin-ios-xhr 30 | 31 | ### Preferences 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | ### Description 40 | 41 | >The default behavior of WKWebView is to raise a cross origin exception when loading files from the main bundle using the file protocol - "file://". This plugin works around this shortcoming by loading files via native code if the web view's current location has "file" protocol and the target URL passed to the open method of the XMLHttpRequest is relative. As a security measure, the plugin verifies that the standardized path of the target URL is within the "www" folder of the application's main bundle or in the /Library path of the application data directory. 42 | 43 | >Since the application's starting page is loaded from the device's file system, all XHR requests to remote endpoints are considered cross origin. For such requests, WKWebView specifies "null" as the value of the Origin header, which will be rejected by endpoints that are configured to disallow requests from the null origin. This plugin works around that issue by handling all remote requests at the native layer where the origin header will be excluded 44 | 45 | >Fixes local file access via XHR with WKWebView 46 | 47 | >CustomUserAgent is only set for XHR requests and does not override cordova's OverrideUserAgent 48 | -------------------------------------------------------------------------------- /plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 21 | 22 | 24 | Cordova WKWebView File XHR Plugin 25 | This plugin resolves XHR Cross-Origin Resource Sharing (CORS) constraints. 26 | MIT 27 | cordova,wkwebview,webview 28 | https://github.com/globules-io/cordova-plugin-ios-xhr.git 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /src/www/ios/formdata-polyfill.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 Oracle and/or its affiliates. 3 | * 4 | * The Universal Permissive License (UPL), Version 1.0 5 | * 6 | * Subject to the condition set forth below, permission is hereby granted to any person obtaining a copy of this 7 | * software, associated documentation and/or data (collectively the "Software"), free of charge and under any and 8 | * all copyright rights in the Software, and any and all patent rights owned or freely licensable by each 9 | * licensor hereunder covering either (i) the unmodified Software as contributed to or provided by such licensor, 10 | * or (ii) the Larger Works (as defined below), to deal in both 11 | * 12 | * 13 | * (a) the Software, and 14 | * 15 | * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if one is included with the Software 16 | * (each a “Larger Work” to which the Software is contributed by such licensors), 17 | * 18 | * without restriction, including without limitation the rights to copy, create derivative works of, display, 19 | * perform, and distribute the Software and make, use, sell, offer for sale, import, export, have made, and 20 | * have sold the Software and the Larger Work(s), and to sublicense the foregoing rights on either these or other 21 | * terms. 22 | * 23 | * This license is subject to the following condition: 24 | * 25 | * The above copyright notice and either this complete permission notice or at a minimum a reference to the UPL 26 | * must be included in all copies or substantial portions of the Software. 27 | * 28 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 29 | * TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 30 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 31 | * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 32 | * DEALINGS IN THE SOFTWARE. 33 | */ 34 | 35 | /* 36 | * Native Safari FormData only implements the append method. This polyfill is used in 37 | * tandem with the xhr-polyfill. 38 | * 39 | * Two additional functions in this FormData implementation: 40 | * 1) __getNative - returns the native Safari FormData object. 41 | * 2) __getRequestParts - returns a promise resolving an object {contentType:string, body:string}. 42 | */ 43 | 'use strict'; 44 | (function () 45 | { 46 | function __FormData(formElement) 47 | { 48 | this._map = new Map(); 49 | 50 | if (!HTMLFormElement.prototype.isPrototypeOf(formElement)) 51 | return; 52 | 53 | var elements = formElement.elements; 54 | for (var i = 0; i < elements.length; i++) 55 | { 56 | var element = elements[i]; 57 | 58 | var name = element.name ? element.name : element.id; 59 | if (!name) 60 | continue; 61 | 62 | if (element.type === 'file') 63 | { 64 | for (var file of element.files) 65 | this.append(name, file); 66 | } 67 | else if (['select-multiple', 'select-one'].indexOf(element.value) > -1) 68 | { 69 | for (var option of element.selectedOptions) 70 | this.append(name, option); 71 | } 72 | else if (['checkbox', 'radio'].indexOf(element.value) > -1 && element.checked) 73 | { 74 | this.append(name, element.value); 75 | } 76 | else 77 | { 78 | this.append(name, element.value); 79 | } 80 | } 81 | } 82 | 83 | if( 'Symbol' in window && typeof Symbol() === 'symbol'){ 84 | Object.defineProperty( __FormData.prototype, Symbol.toStringTag, { 85 | get : function(){ 86 | return 'FormData'; 87 | } 88 | }); 89 | } 90 | 91 | __FormData.prototype.get = function (name) 92 | { 93 | var values = this.getAll(name); 94 | 95 | if (values && values.length > 0) 96 | return values[0]; 97 | else 98 | return undefined; 99 | }; 100 | 101 | __FormData.prototype.getAll = function (name) 102 | { 103 | var map = this._map; 104 | var values = map.get(name); 105 | if (!values) 106 | values = []; 107 | 108 | return values; 109 | }; 110 | 111 | __FormData.prototype.set = function (name, value, filename) 112 | { 113 | var map = this._map; 114 | 115 | if (value && Blob.prototype.isPrototypeOf(value)) 116 | { 117 | if (!filename) 118 | filename = "Blob"; 119 | 120 | Object.defineProperty(value, "name", {value: filename}); 121 | } 122 | 123 | map.set(name, [value]); 124 | }; 125 | 126 | 127 | __FormData.prototype.append = function (name, value, filename) 128 | { 129 | var map = this._map; 130 | var values = map.get(name); 131 | 132 | this.set(name, value, filename); 133 | if (values) 134 | { 135 | values.push(map.get(name)[0]); 136 | map.set(name, values); 137 | } 138 | }; 139 | 140 | __FormData.prototype.delete = function (name) 141 | { 142 | var map = this._map; 143 | map.delete(name); 144 | }; 145 | 146 | __FormData.prototype.has = function (name) 147 | { 148 | var map = this._map; 149 | return map.has(name); 150 | }; 151 | 152 | __FormData.prototype.keys = function () 153 | { 154 | var map = this._map; 155 | return map.keys(); 156 | }; 157 | 158 | __FormData.prototype.values = function () 159 | { 160 | var map = this._map; 161 | var allvalues = []; 162 | 163 | var vit = map.values(); 164 | var entry = vit.next(); 165 | while (entry && !entry.done) 166 | { 167 | var values = entry.value; 168 | for (var i = 0; i < values.length; i++) 169 | allvalues.push(values[i]); 170 | 171 | entry = vit.next(); 172 | } 173 | 174 | return __FormData._makeIterator(allvalues); 175 | }; 176 | 177 | __FormData.prototype.entries = function () 178 | { 179 | var map = this._map; 180 | var allentries = []; 181 | 182 | for (var key of map.keys()) 183 | { 184 | var values = map.get(key); 185 | for (var i = 0; i < values.length; i++) 186 | { 187 | allentries.push([key, values[i]]); 188 | } 189 | } 190 | 191 | return __FormData._makeIterator(allentries); 192 | }; 193 | 194 | __FormData.prototype.forEach = function(callback) 195 | { 196 | var eit = this.entries(); 197 | var entry = eit.next(); 198 | while(!entry.done) 199 | { 200 | callback.call(this, entry.value[1], entry.value[0], this); 201 | entry = eit.next(); 202 | } 203 | }; 204 | 205 | __FormData.prototype.toString = function () 206 | { 207 | return "[object FormData]"; 208 | }; 209 | 210 | __FormData.prototype.__getRequestParts = function () 211 | { 212 | var promise = new Promise(function (resolve) 213 | { 214 | __FormData._getRequestValues(this.entries()).then(function (entries) 215 | { 216 | var parts = __FormData._getMultipartRequest(entries); 217 | 218 | resolve(parts); 219 | }); 220 | }.bind(this)); 221 | 222 | return promise; 223 | }; 224 | 225 | __FormData.prototype.__getNative = function () 226 | { 227 | var fd = new window._FormData(); 228 | var eit = this.entries(); 229 | var entry = eit.next(); 230 | while (!entry.done) 231 | { 232 | if (entry.value[1] && Blob.prototype.isPrototypeOf(entry.value[1])) 233 | fd.append(entry.value[0], entry.value[1], entry.value[1].name); 234 | else 235 | fd.append(entry.value[0], entry.value[1]); 236 | 237 | entry = eit.next(); 238 | } 239 | 240 | return fd; 241 | }; 242 | 243 | __FormData._getMultipartRequest = function (entries) 244 | { 245 | var boundary = "----cordovaPluginWkwebviewFileXhrFormdata" + Math.random().toString(32); 246 | var parts = {"contentType": "multipart/form-data; boundary=" + boundary, body: ""}; 247 | var bodyEntries = []; 248 | 249 | for (var i = 0; i < entries.length; i++) 250 | { 251 | var entry = entries[i]; 252 | for (var data of __FormData._generateMultipartFormData(entry, boundary)) 253 | bodyEntries.push(data); 254 | } 255 | 256 | bodyEntries.push("--" + boundary + "--"); 257 | parts.body = new Blob(bodyEntries, {type: "application/octet-stream"}); 258 | 259 | return parts; 260 | }; 261 | 262 | __FormData._makeIterator = function (array) 263 | { 264 | var i = 0; 265 | return { 266 | next: function () 267 | { 268 | if (i < array.length) 269 | return {done: false, value: array[i++]}; 270 | else 271 | return {done: true, value: undefined}; 272 | } 273 | }; 274 | }; 275 | 276 | __FormData._getRequestValues = function (entriesIterator) 277 | { 278 | var promises = []; 279 | var entry = entriesIterator.next(); 280 | while (!entry.done) 281 | { 282 | promises.push(__FormData._normalizeEntry(entry)); 283 | entry = entriesIterator.next(); 284 | } 285 | 286 | return Promise.all(promises); 287 | }; 288 | 289 | __FormData._normalizeEntry = function (entry) 290 | { 291 | var promise = new Promise(function (resolve) 292 | { 293 | 294 | var name = entry.value[0]; 295 | var value = entry.value[1]; 296 | 297 | if (value && 298 | (Blob.prototype.isPrototypeOf(value) || 299 | File.prototype.isPrototypeOf(value))) 300 | { 301 | 302 | var filename = value.name; 303 | var type = value.type ? value.type : "application/octet-binary;"; 304 | var reader = new FileReader(); 305 | var resolve; 306 | 307 | reader.onload = function () 308 | { 309 | resolve({name: name, value: reader.result, filename: filename, type: type}); 310 | }; 311 | 312 | reader.onerror = function () 313 | { 314 | resolve({name: name, value: reader.error, filename: filename, type: type}); 315 | }; 316 | 317 | reader.readAsArrayBuffer(value); 318 | } 319 | else 320 | { 321 | promise = resolve({name: name, value: value}); 322 | } 323 | }); 324 | 325 | return promise; 326 | }; 327 | 328 | __FormData._generateMultipartFormData = function (entry, boundary) 329 | { 330 | var data = []; 331 | 332 | data.push("--" + boundary + "\r\n"); 333 | if (!entry['filename']) 334 | { 335 | data.push('content-disposition: form-data; name="'); 336 | data.push(entry.name); 337 | data.push('"\r\n'); 338 | data.push('\r\n'); 339 | data.push(entry.value); 340 | data.push("\r\n"); 341 | } 342 | else 343 | { 344 | // Describe it as form data 345 | data.push('content-disposition: form-data; name="'); 346 | data.push(entry.name) 347 | data.push('"; filename="'); 348 | data.push(entry.filename); 349 | data.push('"\r\n'); 350 | data.push('Content-Type: '); 351 | data.push(entry.type); 352 | data.push('\r\n\r\n'); 353 | data.push(entry.value); 354 | data.push('\r\n'); 355 | } 356 | 357 | return data; 358 | }; 359 | 360 | window._FormData = window.FormData; 361 | window.FormData = __FormData; 362 | })(); 363 | -------------------------------------------------------------------------------- /src/www/ios/whatwg-fetch-2.0.3.js: -------------------------------------------------------------------------------- 1 | (function(self) { 2 | 'use strict'; 3 | 4 | if (self.fetch) { 5 | return 6 | } 7 | 8 | var support = { 9 | searchParams: 'URLSearchParams' in self, 10 | iterable: 'Symbol' in self && 'iterator' in Symbol, 11 | blob: 'FileReader' in self && 'Blob' in self && (function() { 12 | try { 13 | new Blob() 14 | return true 15 | } catch(e) { 16 | return false 17 | } 18 | })(), 19 | formData: 'FormData' in self, 20 | arrayBuffer: 'ArrayBuffer' in self 21 | } 22 | 23 | if (support.arrayBuffer) { 24 | var viewClasses = [ 25 | '[object Int8Array]', 26 | '[object Uint8Array]', 27 | '[object Uint8ClampedArray]', 28 | '[object Int16Array]', 29 | '[object Uint16Array]', 30 | '[object Int32Array]', 31 | '[object Uint32Array]', 32 | '[object Float32Array]', 33 | '[object Float64Array]' 34 | ] 35 | 36 | var isDataView = function(obj) { 37 | return obj && DataView.prototype.isPrototypeOf(obj) 38 | } 39 | 40 | var isArrayBufferView = ArrayBuffer.isView || function(obj) { 41 | return obj && viewClasses.indexOf(Object.prototype.toString.call(obj)) > -1 42 | } 43 | } 44 | 45 | function normalizeName(name) { 46 | if (typeof name !== 'string') { 47 | name = String(name) 48 | } 49 | if (/[^a-z0-9\-#$%&'*+.\^_`|~]/i.test(name)) { 50 | throw new TypeError('Invalid character in header field name') 51 | } 52 | return name.toLowerCase() 53 | } 54 | 55 | function normalizeValue(value) { 56 | if (typeof value !== 'string') { 57 | value = String(value) 58 | } 59 | return value 60 | } 61 | 62 | // Build a destructive iterator for the value list 63 | function iteratorFor(items) { 64 | var iterator = { 65 | next: function() { 66 | var value = items.shift() 67 | return {done: value === undefined, value: value} 68 | } 69 | } 70 | 71 | if (support.iterable) { 72 | iterator[Symbol.iterator] = function() { 73 | return iterator 74 | } 75 | } 76 | 77 | return iterator 78 | } 79 | 80 | function Headers(headers) { 81 | this.map = {} 82 | 83 | if (headers instanceof Headers) { 84 | headers.forEach(function(value, name) { 85 | this.append(name, value) 86 | }, this) 87 | } else if (Array.isArray(headers)) { 88 | headers.forEach(function(header) { 89 | this.append(header[0], header[1]) 90 | }, this) 91 | } else if (headers) { 92 | Object.getOwnPropertyNames(headers).forEach(function(name) { 93 | this.append(name, headers[name]) 94 | }, this) 95 | } 96 | } 97 | 98 | Headers.prototype.append = function(name, value) { 99 | name = normalizeName(name) 100 | value = normalizeValue(value) 101 | var oldValue = this.map[name] 102 | this.map[name] = oldValue ? oldValue+','+value : value 103 | } 104 | 105 | Headers.prototype['delete'] = function(name) { 106 | delete this.map[normalizeName(name)] 107 | } 108 | 109 | Headers.prototype.get = function(name) { 110 | name = normalizeName(name) 111 | return this.has(name) ? this.map[name] : null 112 | } 113 | 114 | Headers.prototype.has = function(name) { 115 | return this.map.hasOwnProperty(normalizeName(name)) 116 | } 117 | 118 | Headers.prototype.set = function(name, value) { 119 | this.map[normalizeName(name)] = normalizeValue(value) 120 | } 121 | 122 | Headers.prototype.forEach = function(callback, thisArg) { 123 | for (var name in this.map) { 124 | if (this.map.hasOwnProperty(name)) { 125 | callback.call(thisArg, this.map[name], name, this) 126 | } 127 | } 128 | } 129 | 130 | Headers.prototype.keys = function() { 131 | var items = [] 132 | this.forEach(function(value, name) { items.push(name) }) 133 | return iteratorFor(items) 134 | } 135 | 136 | Headers.prototype.values = function() { 137 | var items = [] 138 | this.forEach(function(value) { items.push(value) }) 139 | return iteratorFor(items) 140 | } 141 | 142 | Headers.prototype.entries = function() { 143 | var items = [] 144 | this.forEach(function(value, name) { items.push([name, value]) }) 145 | return iteratorFor(items) 146 | } 147 | 148 | if (support.iterable) { 149 | Headers.prototype[Symbol.iterator] = Headers.prototype.entries 150 | } 151 | 152 | function consumed(body) { 153 | if (body.bodyUsed) { 154 | return Promise.reject(new TypeError('Already read')) 155 | } 156 | body.bodyUsed = true 157 | } 158 | 159 | function fileReaderReady(reader) { 160 | return new Promise(function(resolve, reject) { 161 | reader.onload = function() { 162 | resolve(reader.result) 163 | } 164 | reader.onerror = function() { 165 | reject(reader.error) 166 | } 167 | }) 168 | } 169 | 170 | function readBlobAsArrayBuffer(blob) { 171 | var reader = new FileReader() 172 | var promise = fileReaderReady(reader) 173 | reader.readAsArrayBuffer(blob) 174 | return promise 175 | } 176 | 177 | function readBlobAsText(blob) { 178 | var reader = new FileReader() 179 | var promise = fileReaderReady(reader) 180 | reader.readAsText(blob) 181 | return promise 182 | } 183 | 184 | function readArrayBufferAsText(buf) { 185 | var view = new Uint8Array(buf) 186 | var chars = new Array(view.length) 187 | 188 | for (var i = 0; i < view.length; i++) { 189 | chars[i] = String.fromCharCode(view[i]) 190 | } 191 | return chars.join('') 192 | } 193 | 194 | function bufferClone(buf) { 195 | if (buf.slice) { 196 | return buf.slice(0) 197 | } else { 198 | var view = new Uint8Array(buf.byteLength) 199 | view.set(new Uint8Array(buf)) 200 | return view.buffer 201 | } 202 | } 203 | 204 | function Body() { 205 | this.bodyUsed = false 206 | 207 | this._initBody = function(body) { 208 | this._bodyInit = body 209 | if (!body) { 210 | this._bodyText = '' 211 | } else if (typeof body === 'string') { 212 | this._bodyText = body 213 | } else if (support.blob && Blob.prototype.isPrototypeOf(body)) { 214 | this._bodyBlob = body 215 | } else if (support.formData && FormData.prototype.isPrototypeOf(body)) { 216 | this._bodyFormData = body 217 | } else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) { 218 | this._bodyText = body.toString() 219 | } else if (support.arrayBuffer && support.blob && isDataView(body)) { 220 | this._bodyArrayBuffer = bufferClone(body.buffer) 221 | // IE 10-11 can't handle a DataView body. 222 | this._bodyInit = new Blob([this._bodyArrayBuffer]) 223 | } else if (support.arrayBuffer && (ArrayBuffer.prototype.isPrototypeOf(body) || isArrayBufferView(body))) { 224 | this._bodyArrayBuffer = bufferClone(body) 225 | } else { 226 | throw new Error('unsupported BodyInit type') 227 | } 228 | 229 | if (!this.headers.get('content-type')) { 230 | if (typeof body === 'string') { 231 | this.headers.set('content-type', 'text/plain;charset=UTF-8') 232 | } else if (this._bodyBlob && this._bodyBlob.type) { 233 | this.headers.set('content-type', this._bodyBlob.type) 234 | } else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) { 235 | this.headers.set('content-type', 'application/x-www-form-urlencoded;charset=UTF-8') 236 | } 237 | } 238 | } 239 | 240 | if (support.blob) { 241 | this.blob = function() { 242 | var rejected = consumed(this) 243 | if (rejected) { 244 | return rejected 245 | } 246 | 247 | if (this._bodyBlob) { 248 | return Promise.resolve(this._bodyBlob) 249 | } else if (this._bodyArrayBuffer) { 250 | return Promise.resolve(new Blob([this._bodyArrayBuffer])) 251 | } else if (this._bodyFormData) { 252 | throw new Error('could not read FormData body as blob') 253 | } else { 254 | return Promise.resolve(new Blob([this._bodyText])) 255 | } 256 | } 257 | 258 | this.arrayBuffer = function() { 259 | if (this._bodyArrayBuffer) { 260 | return consumed(this) || Promise.resolve(this._bodyArrayBuffer) 261 | } else { 262 | return this.blob().then(readBlobAsArrayBuffer) 263 | } 264 | } 265 | } 266 | 267 | this.text = function() { 268 | var rejected = consumed(this) 269 | if (rejected) { 270 | return rejected 271 | } 272 | 273 | if (this._bodyBlob) { 274 | return readBlobAsText(this._bodyBlob) 275 | } else if (this._bodyArrayBuffer) { 276 | return Promise.resolve(readArrayBufferAsText(this._bodyArrayBuffer)) 277 | } else if (this._bodyFormData) { 278 | throw new Error('could not read FormData body as text') 279 | } else { 280 | return Promise.resolve(this._bodyText) 281 | } 282 | } 283 | 284 | if (support.formData) { 285 | this.formData = function() { 286 | return this.text().then(decode) 287 | } 288 | } 289 | 290 | this.json = function() { 291 | return this.text().then(JSON.parse) 292 | } 293 | 294 | return this 295 | } 296 | 297 | // HTTP methods whose capitalization should be normalized 298 | var methods = ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT'] 299 | 300 | function normalizeMethod(method) { 301 | var upcased = method.toUpperCase() 302 | return (methods.indexOf(upcased) > -1) ? upcased : method 303 | } 304 | 305 | function Request(input, options) { 306 | options = options || {} 307 | var body = options.body 308 | 309 | if (input instanceof Request) { 310 | if (input.bodyUsed) { 311 | throw new TypeError('Already read') 312 | } 313 | this.url = input.url 314 | this.credentials = input.credentials 315 | if (!options.headers) { 316 | this.headers = new Headers(input.headers) 317 | } 318 | this.method = input.method 319 | this.mode = input.mode 320 | if (!body && input._bodyInit != null) { 321 | body = input._bodyInit 322 | input.bodyUsed = true 323 | } 324 | } else { 325 | this.url = String(input) 326 | } 327 | 328 | this.credentials = options.credentials || this.credentials || 'omit' 329 | if (options.headers || !this.headers) { 330 | this.headers = new Headers(options.headers) 331 | } 332 | this.method = normalizeMethod(options.method || this.method || 'GET') 333 | this.mode = options.mode || this.mode || null 334 | this.referrer = null 335 | 336 | if ((this.method === 'GET' || this.method === 'HEAD') && body) { 337 | throw new TypeError('Body not allowed for GET or HEAD requests') 338 | } 339 | this._initBody(body) 340 | } 341 | 342 | Request.prototype.clone = function() { 343 | return new Request(this, { body: this._bodyInit }) 344 | } 345 | 346 | function decode(body) { 347 | var form = new FormData() 348 | body.trim().split('&').forEach(function(bytes) { 349 | if (bytes) { 350 | var split = bytes.split('=') 351 | var name = split.shift().replace(/\+/g, ' ') 352 | var value = split.join('=').replace(/\+/g, ' ') 353 | form.append(decodeURIComponent(name), decodeURIComponent(value)) 354 | } 355 | }) 356 | return form 357 | } 358 | 359 | function parseHeaders(rawHeaders) { 360 | var headers = new Headers() 361 | rawHeaders.split(/\r?\n/).forEach(function(line) { 362 | var parts = line.split(':') 363 | var key = parts.shift().trim() 364 | if (key) { 365 | var value = parts.join(':').trim() 366 | headers.append(key, value) 367 | } 368 | }) 369 | return headers 370 | } 371 | 372 | Body.call(Request.prototype) 373 | 374 | function Response(bodyInit, options) { 375 | if (!options) { 376 | options = {} 377 | } 378 | 379 | this.type = 'default' 380 | this.status = 'status' in options ? options.status : 200 381 | this.ok = this.status >= 200 && this.status < 300 382 | this.statusText = 'statusText' in options ? options.statusText : 'OK' 383 | this.headers = new Headers(options.headers) 384 | this.url = options.url || '' 385 | this._initBody(bodyInit) 386 | } 387 | 388 | Body.call(Response.prototype) 389 | 390 | Response.prototype.clone = function() { 391 | return new Response(this._bodyInit, { 392 | status: this.status, 393 | statusText: this.statusText, 394 | headers: new Headers(this.headers), 395 | url: this.url 396 | }) 397 | } 398 | 399 | Response.error = function() { 400 | var response = new Response(null, {status: 0, statusText: ''}) 401 | response.type = 'error' 402 | return response 403 | } 404 | 405 | var redirectStatuses = [301, 302, 303, 307, 308] 406 | 407 | Response.redirect = function(url, status) { 408 | if (redirectStatuses.indexOf(status) === -1) { 409 | throw new RangeError('Invalid status code') 410 | } 411 | 412 | return new Response(null, {status: status, headers: {location: url}}) 413 | } 414 | 415 | self.Headers = Headers 416 | self.Request = Request 417 | self.Response = Response 418 | 419 | self.fetch = function(input, init) { 420 | return new Promise(function(resolve, reject) { 421 | var request = new Request(input, init) 422 | var xhr = new XMLHttpRequest() 423 | 424 | xhr.onload = function() { 425 | var options = { 426 | status: xhr.status, 427 | statusText: xhr.statusText, 428 | headers: parseHeaders(xhr.getAllResponseHeaders() || '') 429 | } 430 | options.url = 'responseURL' in xhr ? xhr.responseURL : options.headers.get('X-Request-URL') 431 | var body = 'response' in xhr ? xhr.response : xhr.responseText 432 | resolve(new Response(body, options)) 433 | } 434 | 435 | xhr.onerror = function() { 436 | reject(new TypeError('Network request failed')) 437 | } 438 | 439 | xhr.ontimeout = function() { 440 | reject(new TypeError('Network request failed')) 441 | } 442 | 443 | xhr.open(request.method, request.url, true) 444 | 445 | if (request.credentials === 'include') { 446 | xhr.withCredentials = true 447 | } 448 | 449 | if ('responseType' in xhr && support.blob) { 450 | xhr.responseType = 'blob' 451 | } 452 | 453 | request.headers.forEach(function(value, name) { 454 | xhr.setRequestHeader(name, value) 455 | }) 456 | 457 | xhr.send(typeof request._bodyInit === 'undefined' ? null : request._bodyInit) 458 | }) 459 | } 460 | self.fetch.polyfill = true 461 | })(typeof self !== 'undefined' ? self : this); 462 | -------------------------------------------------------------------------------- /src/ios/CDVWKWebViewFileXhr.m: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016, 2017 Oracle and/or its affiliates. 3 | * 4 | * The Universal Permissive License (UPL), Version 1.0 5 | * 6 | * Subject to the condition set forth below, permission is hereby granted to any person obtaining a copy of this 7 | * software, associated documentation and/or data (collectively the "Software"), free of charge and under any and 8 | * all copyright rights in the Software, and any and all patent rights owned or freely licensable by each 9 | * licensor hereunder covering either (i) the unmodified Software as contributed to or provided by such licensor, 10 | * or (ii) the Larger Works (as defined below), to deal in both 11 | * 12 | * 13 | * (a) the Software, and 14 | * 15 | * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if one is included with the Software 16 | * (each a “Larger Work” to which the Software is contributed by such licensors), 17 | * 18 | * without restriction, including without limitation the rights to copy, create derivative works of, display, 19 | * perform, and distribute the Software and make, use, sell, offer for sale, import, export, have made, and 20 | * have sold the Software and the Larger Work(s), and to sublicense the foregoing rights on either these or other 21 | * terms. 22 | * 23 | * This license is subject to the following condition: 24 | * 25 | * The above copyright notice and either this complete permission notice or at a minimum a reference to the UPL 26 | * must be included in all copies or substantial portions of the Software. 27 | * 28 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 29 | * TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 30 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 31 | * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 32 | * DEALINGS IN THE SOFTWARE. 33 | */ 34 | 35 | #import "CDVWKWebViewFileXhr.h" 36 | #import 37 | 38 | NS_ASSUME_NONNULL_BEGIN 39 | 40 | 41 | @implementation NSDictionary(CDVWKWebViewFileXhr) 42 | 43 | /** 44 | Convenience method to return a NSString from a dictionary 45 | 46 | @param key The key to return 47 | @return The value as a String, or nil 48 | */ 49 | - (nullable NSString *) cdvwkStringForKey:(NSString *)key 50 | { 51 | NSObject* val = self[key]; 52 | if (val == nil) 53 | return nil; 54 | if ([val isKindOfClass:NSString.class]) 55 | return (NSString *)val; 56 | return [NSString stringWithFormat:@"%@",val]; 57 | } 58 | @end 59 | 60 | @interface CDVWKWebViewFileXhr() 61 | 62 | @property (nonatomic, retain) NSURLSession *urlSession; 63 | @property (nonatomic, retain) NSString *nativeXHRLogging; 64 | 65 | @end 66 | 67 | 68 | 69 | @implementation CDVWKWebViewFileXhr 70 | 71 | -(void) pluginInitialize { 72 | [super pluginInitialize]; 73 | 74 | if (![self.webView isKindOfClass:WKWebView.class]) 75 | return; 76 | 77 | WKWebView *wkWebView = (WKWebView *) self.webView; 78 | 79 | // allowFileAccessFromFileURLs allowUniversalAccessFromFileURLs 80 | NSString *value = [self.commandDelegate.settings cdvwkStringForKey:@"allowfileaccessfromfileurls"]; 81 | 82 | BOOL allowFileAccessFromFileURLs = NO; 83 | 84 | if (value != nil && [value isEqualToString:@"true"]){ 85 | allowFileAccessFromFileURLs = YES; 86 | NSLog(@"WARNING: File access allowed due to preference allowFileAccessFromFileURLs=true"); 87 | } 88 | [wkWebView.configuration.preferences setValue:@(allowFileAccessFromFileURLs) forKey:@"allowFileAccessFromFileURLs"]; 89 | 90 | BOOL allowUniversalAccessFromFileURLs = NO; 91 | 92 | value = [self.commandDelegate.settings cdvwkStringForKey:@"allowuniversalaccessfromfileurls"]; 93 | if (value != nil && [value isEqualToString:@"true"]){ 94 | allowUniversalAccessFromFileURLs = YES; 95 | NSLog(@"WARNING: Universal access allowed due to preference allowUniversalAccessFromFileURLs=true"); 96 | } 97 | [wkWebView.configuration setValue:@(allowUniversalAccessFromFileURLs) forKey:@"allowUniversalAccessFromFileURLs"]; 98 | 99 | // note: settings translates all preferences to lower case 100 | value = [self.commandDelegate.settings cdvwkStringForKey:@"allowuntrustedcerts"]; 101 | if (value != nil && [value isEqualToString:@"true"]) { 102 | _allowsInsecureLoads = YES; 103 | NSLog(@"WARNING: NativeXHR is allowing untrusted certificates due to preference AllowUntrustedCerts=true"); 104 | } 105 | 106 | value = [self.commandDelegate.settings cdvwkStringForKey:@"interceptremoterequests"]; 107 | if (value != nil && 108 | ([value compare:@"all" options:NSCaseInsensitiveSearch] == NSOrderedSame || 109 | [value compare:@"secureOnly" options:NSCaseInsensitiveSearch] == NSOrderedSame || 110 | [value compare:@"none" options:NSCaseInsensitiveSearch] == NSOrderedSame)) { 111 | _interceptRemoteRequests = value; 112 | } else { 113 | _interceptRemoteRequests = @"secureOnly"; 114 | } 115 | 116 | value = [self.commandDelegate.settings cdvwkStringForKey:@"nativexhrlogging"]; 117 | if (value != nil && 118 | ([value compare:@"none" options:NSCaseInsensitiveSearch] == NSOrderedSame || 119 | [value compare:@"full" options:NSCaseInsensitiveSearch] == NSOrderedSame)) { 120 | _nativeXHRLogging = value; 121 | } else { 122 | _nativeXHRLogging = @"none"; 123 | } 124 | 125 | 126 | if ([_interceptRemoteRequests compare:@"all" options:NSCaseInsensitiveSearch] == NSOrderedSame || 127 | [_interceptRemoteRequests compare:@"secureOnly" options:NSCaseInsensitiveSearch] == NSOrderedSame) { 128 | 129 | NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration]; 130 | [sessionConfiguration setRequestCachePolicy:NSURLRequestReloadIgnoringCacheData]; 131 | self.urlSession = [NSURLSession sessionWithConfiguration:sessionConfiguration delegate:self delegateQueue:nil]; // FortityFalsePositive 132 | [wkWebView.configuration.userContentController addScriptMessageHandler:self name:@"nativeXHR"]; 133 | 134 | } 135 | } 136 | 137 | - (void) dispose { 138 | 139 | if ([self.webView isKindOfClass:WKWebView.class]) { 140 | 141 | WKWebView *wkWebView = (WKWebView *) self.webView; 142 | [wkWebView.configuration.userContentController removeScriptMessageHandlerForName:@"nativeXHR"]; 143 | } 144 | [super dispose]; 145 | 146 | } 147 | /*! 148 | * @param uri target relative file from the XMLHttpRequest polyfill 149 | * @return URL relative to the main bundle's www folder if no file:// prefix is provided. Otherwise the file url is used as is to support /Library paths 150 | */ 151 | -(NSURL*)getWebContentResourceURL: (NSString*) uri 152 | { 153 | NSURL *targetURL = nil; 154 | 155 | if ([uri hasPrefix: @"file://"] || [uri hasPrefix: @"FILE://"]) 156 | { 157 | targetURL = [NSURL URLWithString:uri]; 158 | } 159 | else 160 | { 161 | //relative to webview 162 | NSURL *baseURL = [[(WKWebView *)self.webView URL] URLByDeletingLastPathComponent]; 163 | NSString *wwwuri = uri; 164 | targetURL = [NSURL URLWithString:wwwuri relativeToURL:baseURL]; 165 | } 166 | 167 | return targetURL; 168 | } 169 | 170 | /*! 171 | * @discussion Verifying the standardized path of the target URL is under the www 172 | * folder of the main bundle or under the application /Library folder 173 | * 174 | * @param targetURL target file under either the www folder of the main bundle or under the application /Library folder 175 | * @return true if the targetURL is within the www folder in the main bundle or under the application /Library folder 176 | */ 177 | -(BOOL)isWebContentResourceSecure: (NSURL*) targetURL 178 | { 179 | NSURL *baseURL = [NSURL URLWithString:@"www" relativeToURL:[[NSBundle mainBundle] resourceURL]]; 180 | NSString *basePath = [baseURL absoluteString]; 181 | NSString *targetPath = [[targetURL standardizedURL] absoluteString]; 182 | 183 | return [targetPath hasPrefix:basePath] || 184 | [targetPath hasPrefix:[[NSURL fileURLWithPath: 185 | [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0]] absoluteString]]|| 186 | [targetPath hasPrefix:[[NSURL fileURLWithPath: 187 | [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]] absoluteString]]; 188 | } 189 | 190 | /*! 191 | * @discussion Cordova API command impl that reads and return file data as a javascript string 192 | * @param command NSArray* arguments[0] - NSString* uri 193 | */ 194 | - (void)readAsText:(CDVInvokedUrlCommand*)command 195 | { 196 | NSString *uri = [command.arguments.firstObject isKindOfClass: NSString.class] ? command.arguments.firstObject : nil; 197 | if (uri.length == 0) { 198 | // this catches nil value or empty string 199 | [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:404] callbackId:command.callbackId]; 200 | return; 201 | } 202 | 203 | NSURL *targetURL = [self getWebContentResourceURL:uri]; 204 | 205 | if (![self isWebContentResourceSecure:targetURL]) { 206 | 207 | CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ILLEGAL_ACCESS_EXCEPTION messageAsInt:404]; 208 | [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; 209 | 210 | } else { 211 | 212 | __weak CDVWKWebViewFileXhr* weakSelf = self; 213 | [self.commandDelegate runInBackground:^ { 214 | 215 | NSData* data = [[NSData alloc] initWithContentsOfURL:targetURL]; 216 | CDVPluginResult* result = nil; 217 | 218 | if (data != nil) { 219 | NSString* str = [[NSString alloc] initWithBytesNoCopy:(void*)[data bytes] length:[data length] encoding:NSUTF8StringEncoding freeWhenDone:NO]; 220 | 221 | // Check that UTF8 conversion did not fail. 222 | if (str != nil) { 223 | result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:str]; 224 | result.associatedObject = data; 225 | } 226 | } 227 | 228 | if (result == nil) { 229 | result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:404]; 230 | } 231 | 232 | [weakSelf.commandDelegate sendPluginResult:result callbackId:command.callbackId]; 233 | }]; 234 | } 235 | } 236 | 237 | /*! 238 | * @discussion Cordova API command impl that reads and return file data as a javascript arraybuffer 239 | * @param command NSArray* arguments[0] - NSString* uri 240 | */ 241 | - (void)readAsArrayBuffer:(CDVInvokedUrlCommand*)command 242 | { 243 | NSString *uri = [command.arguments.firstObject isKindOfClass: NSString.class] ? command.arguments.firstObject : nil; 244 | if (uri.length == 0) { 245 | // this catches nil value or empty string 246 | [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:404] callbackId:command.callbackId]; 247 | return; 248 | } 249 | NSURL *targetURL = [self getWebContentResourceURL:uri]; 250 | 251 | if (![self isWebContentResourceSecure:targetURL]) { 252 | 253 | CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ILLEGAL_ACCESS_EXCEPTION messageAsInt:404]; 254 | [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; 255 | 256 | } else { 257 | 258 | __weak CDVWKWebViewFileXhr* weakSelf = self; 259 | [self.commandDelegate runInBackground:^ { 260 | 261 | NSData* data = [[NSData alloc] initWithContentsOfURL:targetURL]; 262 | 263 | CDVPluginResult* result = nil; 264 | if (data != nil) { 265 | result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArrayBuffer:data]; 266 | } else { 267 | result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:404]; 268 | } 269 | 270 | [weakSelf.commandDelegate sendPluginResult:result callbackId:command.callbackId]; 271 | }]; 272 | } 273 | } 274 | 275 | - (void)getConfig:(CDVInvokedUrlCommand*)command { 276 | 277 | NSDictionary *dict = @{ 278 | @"InterceptRemoteRequests" : _interceptRemoteRequests, 279 | @"NativeXHRLogging" : _nativeXHRLogging 280 | }; 281 | 282 | [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dict] callbackId:command.callbackId]; 283 | } 284 | 285 | 286 | - (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge 287 | completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler { 288 | 289 | if (_allowsInsecureLoads) { 290 | SecTrustRef serverTrust = challenge.protectionSpace.serverTrust; 291 | if (serverTrust) { 292 | CFDataRef exceptions = SecTrustCopyExceptions (serverTrust); 293 | SecTrustSetExceptions (serverTrust, exceptions); 294 | CFRelease (exceptions); 295 | completionHandler (NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:serverTrust]); // FortityFalsePositive 296 | 297 | return; 298 | } 299 | } 300 | completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil); 301 | } 302 | 303 | 304 | // MARK: - WKScriptMessageHandler 305 | 306 | /** 307 | * Performs an asynchronous network load of the requested resource using native networking routines. 308 | * 309 | * The request is described in the body parameter as a dictionary. The supported keys are outlined below 310 | * 311 | * Name | Kind | Required | Description | Allowed Values 312 | * --- | --- | --- | --- | --- 313 | * id | string | true | A unique id used for callbacks | any valid non-empty string 314 | * callback | string | true | a javascript function that will be invoked on callbacks | any valid javascript function 315 | * url | string | true | The URL to load | A valid URL 316 | * method | string | false | The http method | Valid HTTP method. Defaults to GET 317 | * headers | object {string, string} | false | Additional request headers | 318 | * body | string (base 64 encoded) | false | The http request body | 319 | * timeout | number | false | Request timeout (seconds) | any positive value 320 | * 321 | * The callback function takes two arguments. The first argument is the unique identifier supplied in the request 322 | * object. The second argument is a java object defining the HTTP result. 323 | */ 324 | - (void) performNativeXHR:(NSDictionary *) body inWebView:(WKWebView *) webView { 325 | 326 | NSString *requestId = [body cdvwkStringForKey:@"id"]; 327 | NSString *callbackFunction = [body cdvwkStringForKey:@"callback"]; 328 | NSString *urlString = [body cdvwkStringForKey:@"url"]; 329 | NSString *method = [body cdvwkStringForKey:@"method"]; 330 | 331 | __weak WKWebView* weakWebView = webView; 332 | 333 | void(^sendResult)(NSDictionary *) = ^void(NSDictionary *result) { 334 | dispatch_async(dispatch_get_main_queue(), ^{ 335 | 336 | NSError *jsonError; 337 | NSData* json = [NSJSONSerialization dataWithJSONObject:result options:0 error:&jsonError]; 338 | 339 | if (jsonError != nil) { 340 | NSLog(@"NativeXHR: Failed to encode response to json: %@", jsonError.localizedDescription); // FortityFalsePositive 341 | 342 | NSString *script = [NSString stringWithFormat:@"try { %@('%@', {'error' : 'json serialization failed'}) } catch (e) { }", callbackFunction, requestId]; 343 | [weakWebView evaluateJavaScript:script completionHandler:nil]; 344 | return; 345 | } 346 | 347 | NSString *script = [NSString stringWithFormat:@"try { %@('%@', %@) } catch (e) { }", callbackFunction, requestId, [[NSString alloc] initWithData:json encoding:NSUTF8StringEncoding]]; 348 | [weakWebView evaluateJavaScript:script completionHandler:nil]; 349 | 350 | }); 351 | }; 352 | 353 | 354 | if (requestId.length == 0 || callbackFunction.length == 0 ) { 355 | NSLog(@"NativeXHR: Required parameters id and callback url were not supplied."); 356 | return; 357 | } 358 | 359 | if (urlString.length == 0) { 360 | return sendResult( @{ @"error" : @"Invalid url"}); 361 | } 362 | 363 | NSURL *url = [NSURL URLWithString:urlString]; 364 | 365 | if (![url.scheme.lowercaseString isEqualToString:@"http"] && ![url.scheme.lowercaseString isEqualToString:@"https"]) { 366 | NSString *msg = [NSString stringWithFormat:@"NativeXHR: Invalid url scheme '%@'; only http and https are supported by NativeXHR", url.scheme]; 367 | return sendResult( @{ @"error" : msg}); 368 | } 369 | 370 | __block NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; 371 | if (method.length) 372 | request.HTTPMethod = [method uppercaseString]; 373 | 374 | id val = [body objectForKey:@"timeout"]; 375 | if ([val isKindOfClass:NSNumber.class]) { 376 | request.timeoutInterval = [(NSNumber *) val doubleValue]; 377 | } 378 | 379 | val = [body objectForKey:@"headers"]; 380 | if ([val isKindOfClass:NSDictionary.class]) { 381 | [(NSDictionary *) val enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) { 382 | if (![key isKindOfClass:NSString.class] || ![obj isKindOfClass:NSString.class]) 383 | return; 384 | [request setValue:(NSString *)obj forHTTPHeaderField:(NSString *)key]; 385 | }]; 386 | } 387 | 388 | NSString *body64 = [body cdvwkStringForKey:@"body"]; 389 | if (body64.length) { 390 | request.HTTPBody = [[NSData alloc] initWithBase64EncodedString:body64 options:0]; 391 | } 392 | 393 | NSURLSessionDataTask *task = [self.urlSession dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { // FortityFalsePositive 394 | 395 | NSMutableDictionary* result = [NSMutableDictionary dictionary]; 396 | 397 | if (data != nil) { 398 | result[@"data"] = [[NSString alloc] initWithData:[data base64EncodedDataWithOptions:0] encoding:NSUTF8StringEncoding]; 399 | } 400 | if (response != nil) { 401 | NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; 402 | dictionary[@"expectedContentLength"] = @(response.expectedContentLength); 403 | dictionary[@"suggestedFileName"] = response.suggestedFilename; 404 | dictionary[@"mimeType"] = response.MIMEType; 405 | dictionary[@"url"] = response.URL.absoluteString; 406 | dictionary[@"textEncodingName"] = response.textEncodingName; 407 | if ([response isKindOfClass:NSHTTPURLResponse.class]) { 408 | NSHTTPURLResponse* urlResponse = (NSHTTPURLResponse *) response; 409 | dictionary[@"allHeaderFields"] = urlResponse.allHeaderFields; 410 | dictionary[@"statusCode"] = @(urlResponse.statusCode); 411 | dictionary[@"localizedStatusCode"] = [NSHTTPURLResponse localizedStringForStatusCode:urlResponse.statusCode]; 412 | } 413 | 414 | result[@"response"] = dictionary; 415 | } 416 | 417 | if (error != nil) { 418 | result[@"error"] = [error localizedDescription]; 419 | result[@"underlyingErrorCode"] = @(error.code); 420 | } 421 | sendResult(result); 422 | }]; 423 | 424 | task.taskDescription = requestId; 425 | [task resume]; 426 | } 427 | 428 | - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message { 429 | 430 | if (![message.name isEqualToString:@"nativeXHR"] || ![message.body isKindOfClass:NSDictionary.class]) { 431 | NSLog(@"NativeXHR: Invalid script message '%@' with body '%@' received. Ignoring.", message.name, message.body); 432 | return; 433 | } 434 | __weak __typeof(message.webView) weakWebView = message.webView; 435 | NSDictionary *body = message.body; 436 | 437 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 438 | [self performNativeXHR:body inWebView:weakWebView]; 439 | }); 440 | 441 | } 442 | 443 | @end 444 | 445 | NS_ASSUME_NONNULL_END 446 | -------------------------------------------------------------------------------- /src/www/ios/xhr-polyfill.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 Oracle and/or its affiliates. 3 | * 4 | * The Universal Permissive License (UPL), Version 1.0 5 | * 6 | * Subject to the condition set forth below, permission is hereby granted to any person obtaining a copy of this 7 | * software, associated documentation and/or data (collectively the "Software"), free of charge and under any and 8 | * all copyright rights in the Software, and any and all patent rights owned or freely licensable by each 9 | * licensor hereunder covering either (i) the unmodified Software as contributed to or provided by such licensor, 10 | * or (ii) the Larger Works (as defined below), to deal in both 11 | * 12 | * 13 | * (a) the Software, and 14 | * 15 | * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if one is included with the Software 16 | * (each a “Larger Work” to which the Software is contributed by such licensors), 17 | * 18 | * without restriction, including without limitation the rights to copy, create derivative works of, display, 19 | * perform, and distribute the Software and make, use, sell, offer for sale, import, export, have made, and 20 | * have sold the Software and the Larger Work(s), and to sublicense the foregoing rights on either these or other 21 | * terms. 22 | * 23 | * This license is subject to the following condition: 24 | * 25 | * The above copyright notice and either this complete permission notice or at a minimum a reference to the UPL 26 | * must be included in all copies or substantial portions of the Software. 27 | * 28 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 29 | * TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 30 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 31 | * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 32 | * DEALINGS IN THE SOFTWARE. 33 | */ 34 | 'use strict'; 35 | (function () 36 | { 37 | 38 | 39 | var exec = require('cordova/exec'); 40 | 41 | // Handles native file:// XHR GET requests 42 | function FileHandler(reqContext, config) 43 | { 44 | this._reqContext = reqContext; 45 | this._config = config; 46 | } 47 | 48 | FileHandler._getMimeType = function (reqContext) 49 | { 50 | if (reqContext.overrideMimeType) 51 | return reqContext.overrideMimeType; 52 | else if (reqContext.responseHeaders['content-type']) 53 | return reqContext.responseHeaders['content-type']; 54 | 55 | var url = reqContext.url; 56 | var ext = url.substr(url.lastIndexOf('.')); 57 | return FileHandler._EXT_TO_MIME[ext] ? FileHandler._EXT_TO_MIME[ext] : ""; 58 | }; 59 | 60 | FileHandler.getHandlerForResponseType = function (reqContext) 61 | { 62 | var responseType = reqContext.responseType; 63 | if (FileHandler._RESPONSE_HANDLERS[responseType]) 64 | return FileHandler._RESPONSE_HANDLERS[responseType]; 65 | else 66 | return FileHandler._RESPONSE_HANDLERS["text"]; 67 | }; 68 | 69 | FileHandler._EXT_TO_MIME = { 70 | ".img": "image/jpeg", 71 | ".png": "image/png", 72 | ".gif": "image/gif", 73 | ".xml": "application/xml", 74 | ".xsl": "application/xml", 75 | ".html": "text/html", 76 | ".htm": "text/html", 77 | ".svg": "image/svg+xml", 78 | ".svgz": "image/svg+xml", 79 | ".json": "application/json", 80 | ".js": "application/javascript" 81 | }; 82 | 83 | FileHandler._RESPONSE_HANDLERS = 84 | { 85 | "text": 86 | { 87 | action: "readAsText", 88 | properties: ["response", "responseText"], 89 | convert: function (mimeType, r) 90 | { 91 | this._size = r["length"]; 92 | return r; 93 | }, 94 | responseSize: function () 95 | { 96 | return isNaN(this._size) ? 0 : this._size; 97 | } 98 | }, 99 | "arraybuffer": 100 | { 101 | action: "readAsArrayBuffer", 102 | properties: ["response"], 103 | convert: function (mimeType, r) 104 | { 105 | this._size = r["byteLength"]; 106 | return r; 107 | }, 108 | responseSize: function () 109 | { 110 | return isNaN(this._size) ? 0 : this._size; 111 | } 112 | }, 113 | "json": 114 | { 115 | action: "readAsText", 116 | properties: ["response"], 117 | convert: function (mimeType, r) 118 | { 119 | this._size = r["length"]; 120 | try 121 | { 122 | r = JSON.parse(r); 123 | } 124 | catch (e) 125 | { 126 | } 127 | 128 | return r; 129 | }, 130 | responseSize: function () 131 | { 132 | return isNaN(this._size) ? 0 : this._size; 133 | } 134 | }, 135 | "document": 136 | { 137 | action: "readAsText", 138 | properties: ["response", "responseXML"], 139 | convert: function (mimeType, r) 140 | { 141 | this._size = r["length"]; 142 | try 143 | { 144 | r = new DOMParser().parseFromString(r, "text/xml"); 145 | } 146 | catch (e) 147 | { 148 | } 149 | return r; 150 | }, 151 | responseSize: function () 152 | { 153 | return isNaN(this._size) ? 0 : this._size; 154 | } 155 | }, 156 | "blob": 157 | { 158 | action: "readAsArrayBuffer", 159 | properties: ["response"], 160 | convert: function (mimeType, r) 161 | { 162 | var blob = new Blob([r], {"type": mimeType}); 163 | this._size = blob.size; 164 | return blob; 165 | }, 166 | responseSize: function () 167 | { 168 | return isNaN(this._size) ? 0 : this._size; 169 | } 170 | } 171 | }; 172 | 173 | FileHandler.prototype._isTraceLoggingEnabled = function () 174 | { 175 | return this._config["NativeXHRLogging"] === "full"; 176 | }; 177 | 178 | FileHandler._presend = function (reqContext) 179 | { 180 | reqContext.dispatchReadyStateChangeEvent(1); // OPEN 181 | reqContext.dispatchProgressEvent("loadstart"); 182 | // no upload events for a GET 183 | }; 184 | 185 | FileHandler._error = function (reqContext, e) 186 | { 187 | if (this._isTraceLoggingEnabled()) 188 | console.log("xhr-polyfill.js - native file XHR Response: Unable to find file %o\n%o", reqContext.url, e); 189 | 190 | reqContext.status = 404; 191 | reqContext.responseText = "File Not Found"; 192 | 193 | reqContext.dispatchReadyStateChangeEvent(2); // HEADERS_RECIEVED 194 | reqContext.dispatchReadyStateChangeEvent(3); // LOADING 195 | reqContext.dispatchProgressEvent("progress"); 196 | 197 | reqContext.dispatchReadyStateChangeEvent(4); // DONE 198 | reqContext.dispatchProgressEvent("error"); 199 | reqContext.dispatchProgressEvent("loadend"); 200 | }; 201 | 202 | FileHandler._success = function (reqContext, rspTypeHandler, result) 203 | { 204 | if (this._isTraceLoggingEnabled()) 205 | console.log("xhr-polyfill.js - native file XHR Response:\n%o\n", reqContext.url, result); 206 | 207 | var mimeType = FileHandler._getMimeType(reqContext); 208 | var convertedResult = rspTypeHandler.convert(mimeType, result); 209 | 210 | var respSize = rspTypeHandler.responseSize(); 211 | for (var i = 0; i < rspTypeHandler.properties.length; i++) 212 | reqContext[rspTypeHandler.properties[i]] = convertedResult; 213 | 214 | reqContext.status = 200; 215 | reqContext.statusText = "OK"; 216 | reqContext.responseURL = reqContext.url; 217 | 218 | reqContext.dispatchReadyStateChangeEvent(2); // HEADERS_RECIEVED 219 | reqContext.dispatchReadyStateChangeEvent(3); // LOADING 220 | reqContext.dispatchProgressEvent("progress", respSize); 221 | reqContext.dispatchReadyStateChangeEvent(4); // DONE 222 | reqContext.dispatchProgressEvent("load", respSize); 223 | reqContext.dispatchProgressEvent("loadend", respSize); 224 | }; 225 | 226 | FileHandler.prototype.send = function () 227 | { 228 | var reqContext = this._reqContext; 229 | var rspTypeHandler = FileHandler.getHandlerForResponseType(reqContext); 230 | 231 | if (this._isTraceLoggingEnabled()) 232 | console.log("xhr-polyfill.js - native file XHR Request:\n%o", reqContext.url); 233 | 234 | FileHandler._presend(reqContext); 235 | 236 | exec(FileHandler._success.bind(this, reqContext, rspTypeHandler), 237 | FileHandler._error.bind(this, reqContext), 238 | "CDVWKWebViewFileXhr", rspTypeHandler.action, [reqContext.url]); 239 | }; 240 | 241 | // handles http and https native XHR requests 242 | function HttpHandler(reqContext, config) 243 | { 244 | this._reqContext = reqContext; 245 | this._config = config; 246 | } 247 | 248 | HttpHandler._generateId = function () 249 | { 250 | return [performance.now().toString(16), Math.random().toString(16)].join("-"); // FortifyFalsePositive 251 | }; 252 | 253 | HttpHandler._convertTextToBinaryBase64String = function(mimeType, text) 254 | { 255 | var blob = new Blob([text], {type: mimeType}); 256 | var reader = new FileReader(); 257 | var promise = new Promise(function (resolve, reject) 258 | { 259 | reader.onload = function () 260 | { 261 | var base64str = btoa(reader.result); 262 | resolve(base64str); 263 | }; 264 | reader.onerror = function () 265 | { 266 | var base64str = btoa(reader.error); 267 | reject(base64str); 268 | }; 269 | }); 270 | 271 | reader.readAsBinaryString(blob); 272 | 273 | return promise; 274 | }; 275 | 276 | HttpHandler._REQUEST_HANDLERS = 277 | { 278 | "": { 279 | convert: function (reqContext) 280 | { 281 | return Promise.resolve(null); 282 | } 283 | }, 284 | "ArrayBuffer": { 285 | convert: function (reqContext) 286 | { 287 | var body = reqContext.requestData; 288 | 289 | var type = reqContext.requestHeaders['content-type']; 290 | if (!type) 291 | type = reqContext.requestHeaders['content-type'] = "application/octet-binary;"; 292 | 293 | var blob = new Blob([body], {type: type}); 294 | var reader = new FileReader(); 295 | var promise = new Promise(function (resolve, reject) 296 | { 297 | reader.onload = function () 298 | { 299 | var base64str = btoa(reader.result); 300 | resolve(base64str); 301 | }; 302 | reader.onerror = function () 303 | { 304 | var base64str = btoa(reader.error); 305 | reject(base64str); 306 | }; 307 | }); 308 | 309 | reader.readAsBinaryString(blob); 310 | 311 | return promise; 312 | } 313 | }, 314 | "FormData": { 315 | convert: function (reqContext) 316 | { 317 | var body = reqContext.requestData; 318 | var promise = new Promise(function (resolve, reject) 319 | { 320 | var contentType = reqContext.requestHeaders["content-type"]; 321 | 322 | // FormData polyfill - request the body and context-type 323 | body.__getRequestParts().then(function (parts) 324 | { 325 | if (!contentType) 326 | reqContext.requestHeaders["content-type"] = parts.contentType; 327 | 328 | var reader = new FileReader(); 329 | reader.onload = function () 330 | { 331 | var base64str = btoa(reader.result); 332 | resolve(base64str); 333 | }; 334 | reader.onerror = function () 335 | { 336 | var base64str = btoa(reader.error); 337 | reject(base64str); 338 | }; 339 | 340 | reader.readAsBinaryString(parts.body); 341 | }); 342 | }); 343 | 344 | return promise; 345 | } 346 | }, 347 | "Blob": { 348 | convert: function (reqContext) 349 | { 350 | if (!reqContext.requestHeaders["content-type"]) 351 | reqContext.requestHeaders["content-type"] = "application/octet-binary;"; 352 | 353 | var body = reqContext.requestData; 354 | var reader = new FileReader(); 355 | var promise = new Promise(function (resolve, reject) 356 | { 357 | reader.onload = function () 358 | { 359 | var base64str = btoa(reader.result); 360 | resolve(base64str); 361 | }; 362 | reader.onerror = function () 363 | { 364 | var base64str = btoa(reader.error); 365 | reject(base64str); 366 | }; 367 | }); 368 | 369 | reader.readAsBinaryString(body); 370 | 371 | return promise; 372 | } 373 | }, 374 | "string": { 375 | convert: function (reqContext) 376 | { 377 | if (!reqContext.requestHeaders["content-type"]) 378 | reqContext.requestHeaders["content-type"] = "text/plain;"; 379 | 380 | var body = reqContext.requestData; 381 | var mimeType = reqContext.requestHeaders["content-type"]; 382 | return HttpHandler._convertTextToBinaryBase64String(mimeType, body); 383 | } 384 | }, 385 | "Object": { 386 | convert: function (reqContext) 387 | { 388 | if (!reqContext.requestHeaders["content-type"]) 389 | reqContext.requestHeaders["content-type"] = "application/json;"; 390 | 391 | var body = JSON.stringify(reqContext.requestData); 392 | var mimeType = reqContext.requestHeaders["content-type"]; 393 | return HttpHandler._convertTextToBinaryBase64String(mimeType, body); 394 | } 395 | }, 396 | "Document": { 397 | convert: function (reqContext) 398 | { 399 | if (!reqContext.requestHeaders["content-type"]) 400 | reqContext.requestHeaders["content-type"] = "application/xhtml+xml;"; 401 | 402 | var body = reqContext.requestData; 403 | var serializer = new XMLSerializer(); 404 | var body = serializer.serializeToString(body); 405 | var mimeType = reqContext.requestHeaders["content-type"]; 406 | return HttpHandler._convertTextToBinaryBase64String(mimeType, body); 407 | } 408 | } 409 | }; 410 | 411 | HttpHandler._convertEncode64StringToText = function(mimeType, encoding, r) 412 | { 413 | var binaryString = atob(r); 414 | var byteNumbers = new Array(binaryString.length); 415 | for (var i = 0; i < binaryString.length; i++) 416 | byteNumbers[i] = binaryString.charCodeAt(i); 417 | 418 | var byteArray = new Uint8Array(byteNumbers); 419 | 420 | var blob = new Blob([byteArray], {type: mimeType}); 421 | 422 | var reader = new FileReader(); 423 | var promise = new Promise(function (resolve, reject) 424 | { 425 | reader.onload = function () 426 | { 427 | 428 | resolve(reader.result); 429 | }; 430 | reader.onerror = function () 431 | { 432 | reject(reader.error); 433 | }; 434 | }); 435 | 436 | reader.readAsText(blob, encoding); 437 | return promise; 438 | }; 439 | 440 | HttpHandler._convertEncode64StringToArrayBuffer = function (mimeType, encoding, r) 441 | { 442 | var binaryString = atob(r); 443 | var byteNumbers = new Array(binaryString.length); 444 | for (var i = 0; i < binaryString.length; i++) 445 | byteNumbers[i] = binaryString.charCodeAt(i); 446 | 447 | var byteArray = new Uint8Array(byteNumbers); 448 | 449 | var blob = new Blob([byteArray], {type: mimeType}); 450 | var reader = new FileReader(); 451 | var promise = new Promise(function (resolve, reject) 452 | { 453 | reader.onload = function () 454 | { 455 | 456 | resolve(reader.result); 457 | }; 458 | reader.onerror = function () 459 | { 460 | reject(reader.error); 461 | }; 462 | }); 463 | reader.readAsArrayBuffer(blob); 464 | return promise; 465 | }; 466 | 467 | HttpHandler._RESPONSE_HANDLERS = { 468 | "text": { 469 | convert: HttpHandler._convertEncode64StringToText 470 | }, 471 | "arraybuffer": { 472 | convert: HttpHandler._convertEncode64StringToArrayBuffer 473 | }, 474 | "blob": { 475 | convert: HttpHandler._convertEncode64StringToArrayBuffer 476 | }, 477 | "json": { 478 | convert: HttpHandler._convertEncode64StringToText 479 | }, 480 | "document": { 481 | convert: HttpHandler._convertEncode64StringToText 482 | } 483 | }; 484 | 485 | HttpHandler._UNDERLYING_ERROR_CODES = { 486 | NSURLErrorUnknown: -1, 487 | NSURLErrorCancelled: -999, 488 | NSURLErrorBadURL: -1000, 489 | NSURLErrorTimedOut: -1001, 490 | NSURLErrorUnsupportedURL: -1002, 491 | NSURLErrorCannotFindHost: -1003, 492 | NSURLErrorCannotConnectToHost: -1004, 493 | NSURLErrorNetworkConnectionLost: -1005, 494 | NSURLErrorDNSLookupFailed: -1006, 495 | NSURLErrorHTTPTooManyRedirects: -1007, 496 | NSURLErrorResourceUnavailable: -1008, 497 | NSURLErrorNotConnectedToInternet: -1009, 498 | NSURLErrorRedirectToNonExistentLocation: -1010, 499 | NSURLErrorBadServerResponse: -1011, 500 | NSURLErrorUserCancelledAuthentication: -1012, 501 | NSURLErrorUserAuthenticationRequired: -1013, 502 | NSURLErrorZeroByteResource: -1014, 503 | NSURLErrorCannotDecodeRawData: -1015, 504 | NSURLErrorCannotDecodeContentData: -1016, 505 | NSURLErrorCannotParseResponse: -1017, 506 | NSURLErrorAppTransportSecurityRequiresSecureConnection: -1022, 507 | NSURLErrorFileDoesNotExist: -1100, 508 | NSURLErrorFileIsDirectory: -1101, 509 | NSURLErrorNoPermissionsToReadFile: -1102, 510 | NSURLErrorDataLengthExceedsMaximum: -1103, 511 | NSURLErrorFileOutsideSafeArea: -1104, 512 | NSURLErrorSecureConnectionFailed: -1200, 513 | NSURLErrorServerCertificateHasBadDate: -1201, 514 | NSURLErrorServerCertificateUntrusted: -1202, 515 | NSURLErrorServerCertificateHasUnknownRoot: -1203, 516 | NSURLErrorServerCertificateNotYetValid: -1204, 517 | NSURLErrorClientCertificateRejected: -1205, 518 | NSURLErrorClientCertificateRequired: -1206, 519 | NSURLErrorCannotLoadFromNetwork: -2000, 520 | NSURLErrorCannotCreateFile: -3000, 521 | NSURLErrorCannotOpenFile: -3001, 522 | NSURLErrorCannotCloseFile: -3002, 523 | NSURLErrorCannotWriteToFile: -3003, 524 | NSURLErrorCannotRemoveFile: -3004, 525 | NSURLErrorCannotMoveFile: -3005, 526 | NSURLErrorDownloadDecodingFailedMidStream: -3006, 527 | NSURLErrorDownloadDecodingFailedToComplete: -3007, 528 | NSURLErrorInternationalRoamingOff: -1018, 529 | NSURLErrorCallIsActive: -1019, 530 | NSURLErrorDataNotAllowed: -1020, 531 | NSURLErrorRequestBodyStreamExhausted: -1021, 532 | NSURLErrorBackgroundSessionRequiresSharedContainer: -995, 533 | NSURLErrorBackgroundSessionInUseByAnotherProcess: -996, 534 | NSURLErrorBackgroundSessionWasDisconnected: -997 535 | }; 536 | 537 | HttpHandler._getHandlerForRequestBodyType = function (reqContext) 538 | { 539 | var body = reqContext.requestData; 540 | if (!body) 541 | return HttpHandler._REQUEST_HANDLERS[""]; 542 | else if (typeof body === "string") 543 | return HttpHandler._REQUEST_HANDLERS["string"]; 544 | else if (Blob.prototype.isPrototypeOf(body)) 545 | return HttpHandler._REQUEST_HANDLERS["Blob"]; 546 | else if (FormData.prototype.isPrototypeOf(body)) 547 | return HttpHandler._REQUEST_HANDLERS["FormData"]; 548 | else if (ArrayBuffer.prototype.isPrototypeOf(body) || 549 | body.buffer) 550 | return HttpHandler._REQUEST_HANDLERS["ArrayBuffer"]; 551 | else if (Document.prototype.isPrototypeOf(body)) 552 | return HttpHandler._REQUEST_HANDLERS["Document"]; 553 | else 554 | return HttpHandler._REQUEST_HANDLERS["Object"]; 555 | }; 556 | 557 | HttpHandler._getHandlerForResponseType = function (reqContext) 558 | { 559 | var responseType = reqContext.responseType; 560 | if (HttpHandler._RESPONSE_HANDLERS[responseType]) 561 | return HttpHandler._RESPONSE_HANDLERS[responseType]; 562 | else 563 | return HttpHandler._RESPONSE_HANDLERS["text"]; 564 | }; 565 | 566 | HttpHandler.prototype._isTraceLoggingEnabled = function () 567 | { 568 | return this._config["NativeXHRLogging"] === "full"; 569 | }; 570 | 571 | HttpHandler._resolveUri = function (uri) 572 | { 573 | if (uri.indexOf("://") > -1) 574 | return uri; 575 | 576 | var resolver = document.createElement("a"); 577 | document.body.appendChild(resolver); 578 | resolver.href = uri; 579 | var absoluteUri = resolver.href; 580 | resolver.parentNode.removeChild(resolver); 581 | return absoluteUri; 582 | }; 583 | 584 | HttpHandler.prototype.send = function () 585 | { 586 | var reqContext = this._reqContext; 587 | var id = HttpHandler._generateId(); 588 | 589 | if (reqContext.user && reqContext.password) 590 | { 591 | var token = [reqContext.user, reqContext.password].join(":"); 592 | reqContext.requestHeaders["authorization"] = ["Basic", btoa(token)].join(" "); 593 | } 594 | 595 | window.__nativeXHRResponseQueue.set(id, this); 596 | 597 | var handler = HttpHandler._getHandlerForRequestBodyType(reqContext); 598 | handler.convert(reqContext).then(function (bodyAsBase64String) 599 | { 600 | var requestDataSize = bodyAsBase64String ? bodyAsBase64String.length : 0; 601 | reqContext.requestData = undefined; 602 | 603 | var timeoutInSecs = (isNaN(reqContext.timeout) ? undefined : (reqContext.timeout / 1000)); 604 | 605 | var reqPayLoad = {id: id, callback: "nativeXHRResponse", 606 | url: HttpHandler._resolveUri(reqContext.url), method: reqContext.method, 607 | headers: reqContext.requestHeaders, 608 | body: bodyAsBase64String, timeout: timeoutInSecs}; 609 | 610 | if (this._isTraceLoggingEnabled()) 611 | console.log("xhr-polyfill.js - native XHR Request:\n %o", reqPayLoad); 612 | 613 | HttpHandler._presend(reqContext, requestDataSize); 614 | 615 | window.webkit.messageHandlers.nativeXHR.postMessage(reqPayLoad); 616 | }.bind(this)).catch(HttpHandler._error.bind(this, reqContext)); 617 | }; 618 | 619 | HttpHandler._presend = function (reqContext, requestDataSize) 620 | { 621 | reqContext.dispatchReadyStateChangeEvent(1); // OPEN 622 | reqContext.dispatchProgressEvent("loadstart"); 623 | 624 | // simulate upload progress events 625 | reqContext.dispatchUploadProgressEvent("loadstart"); 626 | reqContext.dispatchUploadProgressEvent("progress", requestDataSize); 627 | reqContext.dispatchUploadProgressEvent("load", requestDataSize); 628 | reqContext.dispatchUploadProgressEvent("loadend", requestDataSize); 629 | }; 630 | 631 | HttpHandler._success = function (reqContext, payload, mimeType, result) 632 | { 633 | var rspTypeHandler = FileHandler.getHandlerForResponseType(reqContext); 634 | var convertedResult = rspTypeHandler.convert(mimeType, result); 635 | 636 | // normalize header keys to lower case. 637 | var responseHeaders = payload.response.allHeaderFields; 638 | var normalizedHeaders = {}; 639 | var keys = Object.keys(responseHeaders); 640 | for (var i = 0; i < keys.length; i++) 641 | { 642 | var key = keys[i]; 643 | normalizedHeaders[key.toLowerCase()] = responseHeaders[key]; 644 | } 645 | 646 | reqContext.responseHeaders = normalizedHeaders; 647 | 648 | reqContext.responseURL = payload.response.url; 649 | reqContext.status = payload.response.statusCode; 650 | reqContext.statusText = payload.response.localizedStatusCode; 651 | 652 | for (var i = 0; i < rspTypeHandler.properties.length; i++) 653 | reqContext[rspTypeHandler.properties[i]] = convertedResult; 654 | 655 | var respSize = rspTypeHandler.responseSize(); 656 | 657 | reqContext.dispatchReadyStateChangeEvent(2); //HEADERS_RECIEVED 658 | reqContext.dispatchReadyStateChangeEvent(3); //LOADING 659 | reqContext.dispatchProgressEvent("progress", respSize); 660 | reqContext.dispatchReadyStateChangeEvent(4); //DONE 661 | 662 | reqContext.dispatchProgressEvent("load", respSize); 663 | reqContext.dispatchProgressEvent("loadend", respSize); 664 | }; 665 | 666 | HttpHandler._error = function (reqContext, error, underlyingErrorCode) 667 | { 668 | var isTimeout = (HttpHandler._UNDERLYING_ERROR_CODES.NSURLErrorTimedOut === underlyingErrorCode); 669 | 670 | if (isTimeout) 671 | { 672 | reqContext.status = 0; 673 | reqContext.statusText = reqContext.responseText = null; 674 | } 675 | else 676 | { 677 | reqContext.status = 400; 678 | reqContext.statusText = "Bad Request"; 679 | reqContext.responseText = error; 680 | } 681 | 682 | reqContext.dispatchReadyStateChangeEvent(2); //HEADERS_RECIEVED 683 | reqContext.dispatchReadyStateChangeEvent(3); //LOADING 684 | reqContext.dispatchProgressEvent("progress"); 685 | reqContext.dispatchReadyStateChangeEvent(4); //DONE 686 | 687 | if (isTimeout) 688 | reqContext.dispatchProgressEvent("timeout"); 689 | else 690 | reqContext.dispatchProgressEvent("error"); 691 | 692 | reqContext.dispatchProgressEvent("loadend"); 693 | }; 694 | 695 | 696 | HttpHandler.prototype.load = function (payload) 697 | { 698 | var reqContext = this._reqContext; 699 | 700 | if (this._isTraceLoggingEnabled()) 701 | console.log("xhr-polyfill.js - native XHR Response:\n%o", payload); 702 | 703 | if (payload.error) 704 | HttpHandler._error(reqContext, payload.error, payload['underlyingErrorCode']); 705 | else 706 | { 707 | var handler = HttpHandler._getHandlerForResponseType(reqContext); 708 | var mimeType = payload.response.mimeType; 709 | if (reqContext.overrideMimeType) // look for overrideMimeType 710 | mimeType = reqContext.overrideMimeType; 711 | else if (payload.response.allHeaderFields && payload.response.allHeaderFields["content-type"]) 712 | mimeType = payload.response.allHeaderFields["content-type"]; 713 | 714 | var encoding = payload.response.textEncodingName ? payload.response.textEncodingName : "UTF-8"; 715 | 716 | handler.convert(mimeType, encoding, payload.data).then(HttpHandler._success.bind(this, 717 | reqContext, payload, mimeType)); 718 | } 719 | }; 720 | 721 | window.__nativeXHRResponseQueue = new Map(); 722 | window.nativeXHRResponse = function (id, payload) 723 | { 724 | try 725 | { 726 | var handler = window.__nativeXHRResponseQueue.get(id); 727 | payload["id"] = id; 728 | handler.load(payload); 729 | } 730 | catch (e) 731 | { 732 | console.log("xhr-polyfill.js - exception delivering request %o\n%o", id, e); 733 | } 734 | finally 735 | { 736 | window.__nativeXHRResponseQueue.delete(id); 737 | } 738 | }; 739 | 740 | // sends the request using JS native XMLHttpRequest 741 | function DelegateHandler(reqContext, config) 742 | { 743 | this._reqContext = reqContext; 744 | this._reqContext.delegate = new window._XMLHttpRequest(); 745 | this._config = config; 746 | } 747 | 748 | DelegateHandler._FROM_PROPERTIES = ["response", "responseText", "responseXML", "responseURL", 749 | "status", "statusText"]; 750 | 751 | DelegateHandler._TO_PROPERTIES = ["responseType", "timeout", "withCredentials"]; 752 | 753 | DelegateHandler._parseResponseHeaders = function (delegate, toHeaders) 754 | { 755 | var fromHeaders = delegate.getAllResponseHeaders().split(/\r?\n/); 756 | for (var i = 0; i < fromHeaders.length; i++) 757 | { 758 | var tokens = fromHeaders[i]; 759 | var n = tokens.indexOf(": "); 760 | if (n > -1) 761 | { 762 | var key = tokens.substr(0, n).toLowerCase(); 763 | var value = tokens.substr(n + 2); 764 | toHeaders[key] = value; 765 | } 766 | } 767 | }; 768 | 769 | DelegateHandler._progressEventRelay = function (reqContext, event) 770 | { 771 | var respSize = isNaN(event.totalSize) ? 0 : event.totalSize; 772 | reqContext.dispatchProgressEvent(event.type, respSize); 773 | }; 774 | 775 | DelegateHandler._uploadProgressEventRelay = function (reqContext, event) 776 | { 777 | var respSize = isNaN(event.totalSize) ? 0 : event.totalSize; 778 | reqContext.dispatchUploadProgressEvent(event.type, respSize); 779 | }; 780 | 781 | DelegateHandler._readystatechangeEventRelay = function (reqContext, delegate, event) 782 | { 783 | 784 | if (delegate.readyState > 1) // readyState gt HEADERS_RECIEVED 785 | { 786 | if (Object.keys(reqContext.responseHeaders).length === 0) 787 | DelegateHandler._parseResponseHeaders(delegate, reqContext.responseHeaders); 788 | 789 | for (var i = 0; i < DelegateHandler._FROM_PROPERTIES.length; i++) 790 | { 791 | try 792 | { 793 | reqContext[DelegateHandler._FROM_PROPERTIES[i]] = delegate[DelegateHandler._FROM_PROPERTIES[i]]; 794 | } 795 | catch (e) 796 | { 797 | } 798 | } 799 | 800 | reqContext.dispatchReadyStateChangeEvent(delegate.readyState); 801 | } 802 | }; 803 | 804 | DelegateHandler.prototype.send = function () 805 | { 806 | var reqContext = this._reqContext; 807 | var delegate = reqContext.delegate; 808 | 809 | delegate.onreadystatechange = DelegateHandler._readystatechangeEventRelay.bind(delegate, 810 | reqContext, delegate); 811 | ["ontimeout", "onloadstart", "onprogress", "onabort", "onerror", "onload", 812 | "onloadend"].forEach(function (eventType) 813 | { 814 | delegate[eventType] = DelegateHandler._progressEventRelay.bind(delegate, reqContext); 815 | delegate.upload[eventType] = DelegateHandler._uploadProgressEventRelay.bind(delegate, 816 | reqContext); 817 | }); 818 | 819 | if (reqContext.overrideMimeType) 820 | delegate.overrideMimeType(reqContext.overrideMimeType); 821 | 822 | delegate.open(reqContext.method, reqContext.url, reqContext.async, reqContext.user, 823 | reqContext.password); 824 | 825 | for (var i = 0; i < DelegateHandler._TO_PROPERTIES.length; i++) 826 | delegate[DelegateHandler._TO_PROPERTIES[i]] = reqContext[DelegateHandler._TO_PROPERTIES[i]]; 827 | 828 | var keys = Object.keys(reqContext.requestHeaders); 829 | for (var i = 0; i < keys.length; i++) 830 | delegate.setRequestHeader(keys[i], reqContext.requestHeaders[keys[i]]); 831 | 832 | var requestData = reqContext.requestData; 833 | reqContext.requestData = undefined; 834 | 835 | // returns a native FormData from the plugin's polyfill 836 | if (FormData.prototype.isPrototypeOf(requestData)) 837 | requestData = requestData.__getNative(); 838 | 839 | delegate.send(requestData); 840 | }; 841 | 842 | function HandlerFactory() 843 | { 844 | } 845 | 846 | HandlerFactory._getConfig = function () 847 | { 848 | var promise; 849 | if (HandlerFactory._config) 850 | { 851 | promise = Promise.resolve(HandlerFactory._config); 852 | } 853 | else 854 | { 855 | promise = new Promise(function (done) 856 | { 857 | function success(result) 858 | { 859 | HandlerFactory._config = result; 860 | done(HandlerFactory._config); 861 | } 862 | 863 | function error() 864 | { 865 | HandlerFactory._config = {"InterceptRemoteRequests": "secureOnly", 866 | "NativeXHRLogging": "none"}; 867 | done(HandlerFactory._config); 868 | } 869 | 870 | exec(success, error, "CDVWKWebViewFileXhr", "getConfig", []); 871 | }); 872 | 873 | } 874 | 875 | return promise; 876 | }; 877 | 878 | HandlerFactory.getHandler = function (context) 879 | { 880 | var promise = new Promise(function (resolve) 881 | { 882 | HandlerFactory._getConfig().then(function (config) 883 | { 884 | var interceptRemoteRequests = config["InterceptRemoteRequests"]; 885 | 886 | if (context.interceptRemoteRequests) // backdoor to override per instance 887 | interceptRemoteRequests = context.interceptRemoteRequests; 888 | 889 | if ("GET" === context.method && typeof context.url === "string" && 890 | ((context.url.indexOf("://") === -1 && window.location.protocol === "file:") || 891 | (context.url.toLowerCase().startsWith("file://")))) 892 | { 893 | resolve(new FileHandler(context, config)); 894 | } 895 | else 896 | { 897 | if ("all" === interceptRemoteRequests || 898 | ("secureOnly" === interceptRemoteRequests && context.url.startsWith("https://"))) 899 | resolve(new HttpHandler(context, config)); 900 | else 901 | resolve(new DelegateHandler(context, config)); 902 | } 903 | }); 904 | }); 905 | 906 | return promise; 907 | }; 908 | 909 | function _XMLHttpRequestUpload() 910 | { 911 | this._context = {"listeners": {}}; 912 | } 913 | 914 | _XMLHttpRequestUpload.prototype.removeEventListener = function (type, listener) 915 | { 916 | var listeners = this._context.listeners; 917 | if (!listener) 918 | listeners[type] = []; 919 | else 920 | { 921 | var lset = listeners[type] ? listeners[type] : []; 922 | var i = lset.indexOf(listener); 923 | if (i > -1) 924 | lset.splice(i, 1); 925 | } 926 | }; 927 | 928 | _XMLHttpRequestUpload.prototype.addEventListener = function (type, listener) 929 | { 930 | if (!listener) 931 | return; 932 | 933 | var listeners = this._context.listeners; 934 | var lset = listeners[type]; 935 | if (!lset) 936 | lset = listeners[type] = []; 937 | 938 | if (lset.indexOf(listeners) < 0) 939 | lset.push(listener); 940 | }; 941 | 942 | _XMLHttpRequestUpload.prototype.dispatchEvent = function (event) 943 | { 944 | if (!event) 945 | return; 946 | 947 | var type = event.type; 948 | var listeners = this._context.listeners; 949 | var lset = listeners[type] ? listeners[type] : []; 950 | 951 | // call property listeners 952 | var listener = this._context[["on", type].join("")]; 953 | if (listener) 954 | { 955 | try 956 | { 957 | listener.call(this, event); 958 | } 959 | catch (e) 960 | { 961 | console.log("xhr-polyfill.js - exception delivering upload event %o\n%o", event, e); 962 | } 963 | } 964 | }; 965 | 966 | /** @type {?} */ 967 | window._XMLHttpRequest = window.XMLHttpRequest; 968 | window.XMLHttpRequest = function () 969 | { 970 | this._context = {delegate: null, requestHeaders: {}, responseHeaders: {}, 971 | listeners: {}, readyState: 0, responseType: "text", withCredentials: false, 972 | upload: new _XMLHttpRequestUpload(), status: 0}; 973 | 974 | this._context.dispatchProgressEvent = function (req, type, respSize) 975 | { 976 | if (isNaN(respSize)) 977 | respSize = 0; 978 | 979 | var event = document.createEvent("Event"); 980 | event.initEvent(type, false, false); 981 | ["total", "totalSize", "loaded", "position"].forEach(function (propName) 982 | { 983 | Object.defineProperty(event, propName, {value: respSize}); 984 | }); 985 | Object.defineProperty(event, "lengthComputable", {value: respSize === 0 ? false : true}); 986 | 987 | req.dispatchEvent(event); 988 | }.bind(this._context, this); 989 | 990 | this._context.dispatchReadyStateChangeEvent = function (req, readyState) 991 | { 992 | var event = document.createEvent("Event"); 993 | event.initEvent("readystatechange", false, false); 994 | 995 | this.readyState = readyState; 996 | req.dispatchEvent(event); 997 | }.bind(this._context, this); 998 | 999 | this._context.dispatchUploadProgressEvent = function (type, reqSize) 1000 | { 1001 | // no body sent on a GET request 1002 | if (this.method === "GET") 1003 | return; 1004 | 1005 | if (isNaN(reqSize)) 1006 | reqSize = 0; 1007 | 1008 | var event = document.createEvent("Event"); 1009 | event.initEvent(type, false, false); 1010 | ["total", "totalSize", "loaded", "position"].forEach(function (propName) 1011 | { 1012 | Object.defineProperty(event, propName, {value: reqSize}); 1013 | }); 1014 | Object.defineProperty(event, "lengthComputable", {value: reqSize === 0 ? false : true}); 1015 | 1016 | this.upload.dispatchEvent(event); 1017 | }.bind(this._context); 1018 | }; 1019 | 1020 | 1021 | // define readonly const properties 1022 | ["UNSENT", "OPENED", "HEADERS_RECEIVED", "LOADING", "DONE"].forEach(function (propName, i) 1023 | { 1024 | Object.defineProperty(window.XMLHttpRequest.prototype, propName, 1025 | { 1026 | "get": function () 1027 | { 1028 | return i; 1029 | } 1030 | }); 1031 | }); 1032 | 1033 | // define readonly properties. 1034 | ["readyState", "response", "responseText", "responseURL", "responseXML", 1035 | "status", "statusText", "upload"].forEach(function (propName) 1036 | { 1037 | Object.defineProperty(window.XMLHttpRequest.prototype, propName, 1038 | { 1039 | "get": function () 1040 | { 1041 | return this._context[propName]; 1042 | } 1043 | }); 1044 | }); 1045 | 1046 | // define read/write properties 1047 | ["responseType", "timeout", "withCredentials"].forEach(function (propName) 1048 | { 1049 | Object.defineProperty(window.XMLHttpRequest.prototype, propName, 1050 | { 1051 | "get": function () 1052 | { 1053 | return this._context[propName]; 1054 | }, 1055 | "set": function (value) 1056 | { 1057 | this._context[propName] = value; 1058 | } 1059 | }); 1060 | }); 1061 | 1062 | // define read/write readychange event listener properties 1063 | Object.defineProperty(window.XMLHttpRequest.prototype, "onreadystatechange", 1064 | { 1065 | "get": function () 1066 | { 1067 | return this._context["onreadystatechange"]; 1068 | }, 1069 | "set": function (value) 1070 | { 1071 | if (typeof value === "function") 1072 | this._context["onreadystatechange"] = value; 1073 | } 1074 | }); 1075 | 1076 | // define read/write event progress listener properties 1077 | ["ontimeout", "onloadstart", "onprogress", "onabort", "onerror", "onload", "onloadend"].forEach( 1078 | function (propName) 1079 | { 1080 | Object.defineProperty(window.XMLHttpRequest.prototype, propName, 1081 | { 1082 | "get": function () 1083 | { 1084 | return this._context[propName]; 1085 | }, 1086 | "set": function (value) 1087 | { 1088 | if (typeof value === "function") 1089 | this._context[propName] = value; 1090 | } 1091 | }); 1092 | 1093 | Object.defineProperty(_XMLHttpRequestUpload.prototype, propName, 1094 | { 1095 | "get": function () 1096 | { 1097 | return this._context[propName]; 1098 | }, 1099 | "set": function (value) 1100 | { 1101 | if (typeof value === "function") 1102 | this._context[propName] = value; 1103 | } 1104 | }); 1105 | }); 1106 | 1107 | window.XMLHttpRequest.prototype.setRequestHeader = function (header, value) 1108 | { 1109 | // normalize value pair to strings 1110 | header = String(header).toLowerCase();; 1111 | value = String(value); 1112 | this._context.requestHeaders[header] = value; 1113 | }; 1114 | 1115 | window.XMLHttpRequest.prototype.abort = function () 1116 | { 1117 | if (this._context["delegate"]) 1118 | this._context.delegate.abort(); 1119 | }; 1120 | 1121 | window.XMLHttpRequest.prototype.getResponseHeader = function (name) 1122 | { 1123 | name = name.toLowerCase(); 1124 | return this._context.responseHeaders[name]; 1125 | }; 1126 | 1127 | window.XMLHttpRequest.prototype.overrideMimeType = function (mimetype) 1128 | { 1129 | return this._context.overrideMimeType = mimetype; 1130 | }; 1131 | 1132 | window.XMLHttpRequest.prototype.getAllResponseHeaders = function () 1133 | { 1134 | var responseHeaders = this._context.responseHeaders; 1135 | var names = Object.keys(responseHeaders); 1136 | var list = []; 1137 | for (var i = 0; i < names.length; i++) 1138 | list.push([names[i], responseHeaders[names[i]]].join(":")); 1139 | 1140 | return list.join("\n"); 1141 | }; 1142 | 1143 | window.XMLHttpRequest.prototype.removeEventListener = function (type, listener) 1144 | { 1145 | var listeners = this._context.listeners; 1146 | if (!listener) 1147 | listeners[type] = []; 1148 | else 1149 | { 1150 | var lset = listeners[type] ? listeners[type] : []; 1151 | var i = lset.indexOf(listener); 1152 | if (i > -1) 1153 | lset.splice(i, 1); 1154 | } 1155 | }; 1156 | 1157 | window.XMLHttpRequest.prototype.addEventListener = function (type, listener) 1158 | { 1159 | if (!listener) 1160 | return; 1161 | 1162 | var listeners = this._context.listeners; 1163 | var lset = listeners[type]; 1164 | if (!lset) 1165 | lset = listeners[type] = []; 1166 | 1167 | if (lset.indexOf(listeners) < 0) 1168 | lset.push(listener); 1169 | }; 1170 | 1171 | window.XMLHttpRequest.prototype.dispatchEvent = function (event) 1172 | { 1173 | if (!event) 1174 | return; 1175 | 1176 | var type = event.type; 1177 | var listeners = this._context.listeners; 1178 | var lset = listeners[type] ? listeners[type] : []; 1179 | 1180 | // call property listeners 1181 | var listener = this._context[["on", type].join("")]; 1182 | if (listener) 1183 | { 1184 | try 1185 | { 1186 | listener.call(this, event); 1187 | } 1188 | catch (e) 1189 | { 1190 | console.log("xhr-polyfill.js - exception delivering event %o\n%o", event, e); 1191 | } 1192 | } 1193 | 1194 | // call listeners registered via addEventListener 1195 | for (var i = 0; i < lset.length; i++) 1196 | { 1197 | listener = lset[i]; 1198 | if (listener) 1199 | { 1200 | try 1201 | { 1202 | listener.call(this, event); 1203 | } 1204 | catch (e) 1205 | { 1206 | console.log("xhr-polyfill.js - exception delivering event %o\n%o", event, e); 1207 | } 1208 | } 1209 | } 1210 | }; 1211 | 1212 | window.XMLHttpRequest.prototype.open = function (method, url, async, user, password) 1213 | { 1214 | this._context.method = !method ? "GET" : method.toUpperCase(); // FortifyFalsePositive 1215 | this._context.url = url; 1216 | this._context.async = async === undefined ? true : async; 1217 | this._context.user = user; 1218 | this._context.password = password; 1219 | }; 1220 | 1221 | window.XMLHttpRequest.prototype.send = function (data) 1222 | { 1223 | if ("GET" !== this._context.method && "HEAD" !== this._context.method) 1224 | this._context.requestData = data; 1225 | 1226 | HandlerFactory.getHandler(this._context).then(function (handler) 1227 | { 1228 | handler.send(); 1229 | }); 1230 | }; 1231 | 1232 | /** 1233 | * Override plugin config settings per request instance for the "InterceptRemoteRequests" 1234 | * config param. 1235 | * 1236 | * @param {string} value enumerations are: "all", "secureOnly", "none". 1237 | */ 1238 | window.XMLHttpRequest.prototype.__setInterceptRemoteRequests = function (value) 1239 | { 1240 | if (["all", "secureOnly", "none"].indexOf(value) > -1) 1241 | { 1242 | this._context.interceptRemoteRequests = value; 1243 | } 1244 | }; 1245 | 1246 | })(); 1247 | -------------------------------------------------------------------------------- /tests/tests.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2014, 2016, Oracle and/or its affiliates. 3 | The Universal Permissive License (UPL), Version 1.0 4 | */ 5 | 6 | /* jshint jasmine: true */ 7 | /* global Connection */ 8 | 9 | exports.defineAutoTests = function () 10 | { 11 | var SECURE_TESTS_DOMAIN = "https://den01cxr.us.oracle.com:7102"; 12 | var NONSECURE_TESTS_DOMAIN = "http://den01cxr.us.oracle.com:7101"; 13 | 14 | var expects = {}; 15 | 16 | window.xhrCallback = function (id, result) 17 | { 18 | expects[id](result); 19 | delete expects[id]; 20 | }; 21 | 22 | 23 | describe('FormData Polyfill:', function () 24 | { 25 | it("basic API", function (done) 26 | { 27 | var fd = new FormData(); 28 | 29 | // set & get 30 | fd.set("foo", "bar1"); 31 | expect(fd.get("foo")).toBe("bar1"); 32 | 33 | // append & get 34 | fd.append("foo", "bar2"); 35 | expect(fd.get("foo")).toBe("bar1"); 36 | 37 | // getAll 38 | var values = fd.getAll("foo"); 39 | expect(values).toContain("bar1"); 40 | expect(values).toContain("bar2"); 41 | 42 | // values 43 | var vit = fd.values(); 44 | var e = vit.next(); 45 | expect(e.done).toBe(false); 46 | expect(e.value).toBe("bar1"); 47 | var e = vit.next(); 48 | expect(e.done).toBe(false); 49 | expect(e.value).toBe("bar2"); 50 | var e = vit.next(); 51 | expect(e.done).toBe(true); 52 | 53 | // entries 54 | var eit = fd.entries(); 55 | e = eit.next(); 56 | expect(e.done).toBe(false); 57 | expect(e.value[0]).toBe("foo"); 58 | expect(e.value[1]).toBe("bar1"); 59 | e = eit.next(); 60 | expect(e.done).toBe(false); 61 | expect(e.value[0]).toBe("foo"); 62 | expect(e.value[1]).toBe("bar2"); 63 | e = eit.next(); 64 | expect(e.done).toBe(true); 65 | 66 | // has, get && delete 67 | fd.set("bar", "foo"); 68 | expect(fd.get("bar")).toBe("foo"); 69 | expect(fd.has("bar")).toBe(true); 70 | fd.delete("bar"); 71 | expect(fd.get("bar")).toBe(undefined); 72 | expect(fd.has("bar")).toBe(false); 73 | 74 | // keys 75 | fd.set("bar", "foo"); 76 | var kit = fd.keys(); 77 | e = kit.next(); 78 | expect(e.done).toBe(false); 79 | expect(e.value).toBe("foo"); 80 | e = kit.next(); 81 | expect(e.done).toBe(false); 82 | expect(e.value).toBe("bar"); 83 | e = kit.next(); 84 | expect(e.done).toBe(true); 85 | 86 | // forEach 87 | var i = 0; 88 | fd.forEach(function (value, key, fd) 89 | { 90 | expect(["bar1", "bar2", "foo"]).toContain(value); 91 | expect(["foo", "bar"]).toContain(key); 92 | expect(fd).toBeDefined(); 93 | expect(fd.set).toBeDefined(); 94 | expect(fd.get).toBeDefined(); 95 | i++; 96 | }); 97 | 98 | expect(i).toBe(3); 99 | 100 | // angularJS uses the toString in the $http service for isFormData check 101 | expect(fd.toString()).toBe("[object FormData]"); 102 | 103 | done(); 104 | 105 | }); 106 | 107 | it("HtmlFormElement constructor", function (done) 108 | { 109 | function createForm() 110 | { 111 | var node = document.createElement("form"); 112 | return node; 113 | } 114 | 115 | function createInputText() 116 | { 117 | var node = document.createElement("input"); 118 | node.id = "in1"; 119 | node.name = "input1"; 120 | node.value = "value1"; 121 | return node; 122 | } 123 | 124 | function createSelectSingle() 125 | { 126 | var select = document.createElement("select"); 127 | select.id = "select1"; 128 | 129 | var option = document.createElement("option"); 130 | option.value = "option1"; 131 | select.appendChild(option); 132 | 133 | option = document.createElement("option"); 134 | option.value = "option2"; 135 | option.selected = true; 136 | select.appendChild(option); 137 | 138 | return select; 139 | } 140 | 141 | function createCheckbox() 142 | { 143 | var node = document.createElement("input"); 144 | node.id = "checkbox1"; 145 | node.type = "checkbox"; 146 | node.value = "checked1"; 147 | node.checked = true; 148 | return node; 149 | } 150 | 151 | var form = createForm(); 152 | form.appendChild(createInputText()); 153 | form.appendChild(createSelectSingle()); 154 | form.appendChild(createCheckbox()); 155 | document.body.appendChild(form); 156 | 157 | var fd = new FormData(form); 158 | expect(fd.get("input1")).toBe("value1"); 159 | expect(fd.get("select1")).toBe("option2"); 160 | expect(fd.get("checkbox1")).toBe("checked1"); 161 | 162 | document.body.removeChild(form); 163 | done(); 164 | }); 165 | 166 | it("https:// mixed types document response", function (done) 167 | { 168 | 169 | var file3Content = btoa("Content of Blob 3"); 170 | function loadend(evt) 171 | { 172 | expect(this.readyState).toBe(this.DONE); 173 | expect(this.status).toBe(200); 174 | expect(this.response instanceof Document).toEqual(true); 175 | expect(this.response.querySelector("#request-content-type").textContent) 176 | .toContain("multipart/form-data"); 177 | expect(this.response.querySelector("#field1").textContent).toEqual("1"); 178 | expect(this.response.querySelector("#field2").textContent).toEqual("2"); 179 | expect(this.response.querySelector("#file1").textContent).toEqual("Content of Blob 1"); 180 | expect(this.response.querySelector("#file1").getAttribute("filename")).toEqual("file1.txt"); 181 | expect(this.response.querySelector("#file2").textContent).toEqual("Content of Blob 2"); 182 | expect(this.response.querySelector("#file2").getAttribute("filename")).toEqual("file2.txt"); 183 | expect(this.response.querySelector("#file3").textContent).toEqual(file3Content); 184 | expect(this.response.querySelector("#file3").getAttribute("filename")).toEqual("file3.txt"); 185 | 186 | done(); 187 | } 188 | 189 | var fd = new FormData(); 190 | fd.append("field1", "1"); 191 | fd.append("field2", "2"); 192 | 193 | var file1 = new Blob(["Content of Blob 1"], {type: "text/html"}); 194 | fd.append("file1", file1, "file1.txt"); 195 | 196 | var file2 = new Blob(["Content of Blob 2"], {type: "text/html"}); 197 | fd.append("file2", file2, "file2.txt"); 198 | 199 | var file3 = new Blob([file3Content]); 200 | fd.append("file3", file3, "file3.txt"); 201 | 202 | var xhr = new XMLHttpRequest(); 203 | xhr.addEventListener("loadend", loadend); 204 | xhr.open("POST", 205 | SECURE_TESTS_DOMAIN + "/RestApp-ViewController-context-root/servletmultipart"); 206 | xhr.responseType = "document"; 207 | xhr.send(fd); 208 | 209 | }); 210 | 211 | it("http:// mixed types document response", function (done) 212 | { 213 | // uses native FormData thru the Delegate to the native XMLHttpRequest 214 | 215 | function loadend(evt) 216 | { 217 | expect(this.readyState).toBe(this.DONE); 218 | expect(this.status).toBe(200); 219 | expect(this.response instanceof Document).toEqual(true); 220 | expect(this.response.querySelector("#request-content-type").textContent) 221 | .toContain("multipart/form-data"); 222 | expect(this.response.querySelector("#field1").textContent).toEqual("1"); 223 | expect(this.response.querySelector("#field2").textContent).toEqual("2"); 224 | expect(this.response.querySelector("#file1").textContent).toEqual("Content of Blob 1"); 225 | expect(this.response.querySelector("#file1").getAttribute("filename")).toEqual("file1.txt"); 226 | expect(this.response.querySelector("#file2").textContent).toEqual("Content of Blob 2"); 227 | expect(this.response.querySelector("#file2").getAttribute("filename")).toEqual("file2.txt"); 228 | done(); 229 | } 230 | 231 | var fd = new FormData(); 232 | fd.append("field1", "1"); 233 | fd.append("field2", "2"); 234 | 235 | var file1 = new Blob(["Content of Blob 1"], {type: "text/html"}); 236 | fd.append("file1", file1, "file1.txt"); 237 | 238 | var file2 = new Blob(["Content of Blob 2"], {type: "text/html"}); 239 | fd.append("file2", file2, "file2.txt"); 240 | 241 | var xhr = new XMLHttpRequest(); 242 | xhr.addEventListener("loadend", loadend); 243 | xhr.open("POST", 244 | NONSECURE_TESTS_DOMAIN + "/RestApp-ViewController-context-root/servletmultipart"); 245 | xhr.responseType = "document"; 246 | xhr.send(fd); 247 | 248 | }); 249 | 250 | it("download/upload/binary compare", function(done) 251 | { 252 | // 1) download an image as a blob 253 | // 2) add the blob to a form data and send it back to the server as a multipart form 254 | // 3) server performs a binary compare of the original file to the uploaded 255 | // 4) returns a document response with the results of the compare 256 | 257 | var downloadPromise = new Promise(function (resolve, reject) 258 | { 259 | var xhr = new XMLHttpRequest(); 260 | xhr.onloadend = function (event) 261 | { 262 | resolve(xhr.response); 263 | }; 264 | xhr.onerror = function (event) 265 | { 266 | reject(xhr.response); 267 | 268 | }; 269 | 270 | xhr.responseType = "blob"; 271 | xhr.open("GET", SECURE_TESTS_DOMAIN + "/RestApp-ViewController-context-root/servletmultipartplayback"); 272 | xhr.send(); 273 | }); 274 | 275 | downloadPromise.then(function (blob) 276 | { 277 | expect(blob).toBeDefined(); 278 | expect(blob instanceof Blob).toEqual(true); 279 | expect(blob.type).toEqual("image/png"); 280 | 281 | var fd = new FormData(); 282 | fd.append("simple-test", blob, "simple-test.html"); 283 | 284 | var xhr = new XMLHttpRequest(); 285 | xhr.onloadend = function () 286 | { 287 | expect(this.readyState).toBe(this.DONE); 288 | expect(this.status).toBe(200); 289 | expect(this.response instanceof Document).toEqual(true); 290 | expect(this.response.querySelector("#are-same").textContent).toEqual("true"); 291 | done(); 292 | }; 293 | xhr.responseType = "document"; 294 | xhr.open("POST", SECURE_TESTS_DOMAIN + "/RestApp-ViewController-context-root/servletmultipartplayback"); 295 | xhr.send(fd); 296 | 297 | }); 298 | 299 | 300 | }, 120000); 301 | 302 | 303 | }); 304 | 305 | describe('http:// GET remote:', function () 306 | { 307 | it("responseType text, loadend addEventListener", function (done) 308 | { 309 | 310 | function loadend(evt) 311 | { 312 | expect(this.readyState).toBe(this.DONE); 313 | expect(this.status).toBe(200); 314 | expect(this.responseText).toBeDefined(); 315 | expect(this.responseText).toContain("Hello World"); 316 | done(); 317 | } 318 | 319 | var xhr = new XMLHttpRequest(); 320 | xhr.addEventListener("loadend", loadend); 321 | xhr.open("GET", 322 | SECURE_TESTS_DOMAIN + "/RestApp-ViewController-context-root/simple-test.html"); 323 | xhr.send(); 324 | 325 | }); 326 | 327 | it("responseType text, loadend onloadend", function (done) 328 | { 329 | 330 | function loadend(evt) 331 | { 332 | expect(this.readyState).toBe(this.DONE); 333 | expect(this.status).toBe(200); 334 | expect(this.responseText).toBeDefined(); 335 | expect(this.responseText).toContain("Hello World"); 336 | done(); 337 | } 338 | 339 | var xhr = new XMLHttpRequest(); 340 | xhr.onloadend = loadend; 341 | xhr.open("GET", 342 | SECURE_TESTS_DOMAIN + "/RestApp-ViewController-context-root/simple-test.html"); 343 | xhr.send(); 344 | }); 345 | 346 | it("responseType text, loadend onloadend", function (done) 347 | { 348 | 349 | function loadend(evt) 350 | { 351 | expect(this.readyState).toBe(this.DONE); 352 | expect(this.status).toBe(200); 353 | expect(this.responseText).toBeDefined(); 354 | expect(this.responseText).toContain("Hello World"); 355 | done(); 356 | } 357 | 358 | var xhr = new XMLHttpRequest(); 359 | xhr.onloadend = loadend; 360 | xhr.open("GET", 361 | SECURE_TESTS_DOMAIN + "/RestApp-ViewController-context-root/simple-test.html"); 362 | xhr.send(); 363 | }); 364 | 365 | 366 | it("responseType arraybuffer, loaded onloadend", function (done) 367 | { 368 | 369 | function loadend(evt) 370 | { 371 | expect(this.status).toBe(200); 372 | expect(this.response).toBeDefined(); 373 | expect(this.response instanceof ArrayBuffer).toEqual(true); 374 | done(); 375 | } 376 | 377 | var xhr = new XMLHttpRequest(); 378 | xhr.onloadend = loadend; 379 | xhr.open("GET", 380 | SECURE_TESTS_DOMAIN + "/RestApp-ViewController-context-root/simple-test.png"); 381 | xhr.responseType = "arraybuffer"; 382 | xhr.send(); 383 | }); 384 | 385 | it("responseType blob, loaded onloadend", function (done) 386 | { 387 | 388 | function loadend(evt) 389 | { 390 | expect(this.status).toBe(200); 391 | expect(this.response).toBeDefined(); 392 | expect(this.response instanceof Blob).toEqual(true); 393 | expect(this.response.type).toEqual("text/html"); 394 | done(); 395 | } 396 | 397 | var xhr = new XMLHttpRequest(); 398 | xhr.onloadend = loadend; 399 | xhr.open("GET", 400 | SECURE_TESTS_DOMAIN + "/RestApp-ViewController-context-root/simple-test.html"); 401 | xhr.responseType = "blob"; 402 | xhr.send(); 403 | }); 404 | 405 | it("responseType document, loaded onloadend", function (done) 406 | { 407 | 408 | function loadend(evt) 409 | { 410 | expect(this.status).toBe(200); 411 | expect(this.response).toBeDefined(); 412 | expect(this.response instanceof Document).toEqual(true); 413 | expect(this.response.querySelector("h1").textContent).toEqual("Hello World"); 414 | done(); 415 | } 416 | 417 | var xhr = new XMLHttpRequest(); 418 | xhr.onloadend = loadend; 419 | xhr.open("GET", 420 | SECURE_TESTS_DOMAIN + "/RestApp-ViewController-context-root/simple-test.html"); 421 | xhr.responseType = "document"; 422 | xhr.send(); 423 | }); 424 | 425 | it("responseType json, loaded onloadend", function (done) 426 | { 427 | 428 | function loadend(evt) 429 | { 430 | expect(this.status).toBe(200); 431 | expect(this.response).toBeDefined(); 432 | expect(this.response.message).toEqual("Hello World"); 433 | done(); 434 | } 435 | 436 | var xhr = new XMLHttpRequest(); 437 | xhr.onloadend = loadend; 438 | xhr.open("GET", 439 | SECURE_TESTS_DOMAIN + "/RestApp-ViewController-context-root/simple-test.json"); 440 | xhr.responseType = "json"; 441 | xhr.send(); 442 | }); 443 | }); 444 | 445 | describe('http:// POST remote:', function () 446 | { 447 | it("post HTML with responseType text, loadend addEventListener", function (done) 448 | { 449 | var html = "

Hello World

"; 450 | 451 | function loadend(evt) 452 | { 453 | expect(this.status).toBe(200); 454 | expect(this.response).toBeDefined(); 455 | expect(this.response).toEqual(html); 456 | done(); 457 | } 458 | 459 | var xhr = new XMLHttpRequest(); 460 | xhr.addEventListener("loadend", loadend); 461 | xhr.open("POST", 462 | SECURE_TESTS_DOMAIN + "/RestApp-ViewController-context-root/playbackservlet"); 463 | xhr.responseType = "text"; 464 | xhr.send(html); 465 | }); 466 | 467 | it("post ArrayBuffer with responseType ArrayBuffer, onloadend listener", function (done) 468 | { 469 | var bin = []; 470 | bin.length = 1000; 471 | bin.fill(0, 0, 1000); 472 | 473 | var inBuff = new Int8Array(bin); 474 | 475 | function loadend(evt) 476 | { 477 | expect(this.status).toBe(200); 478 | expect(this.response instanceof ArrayBuffer).toBeDefined(); 479 | 480 | // compare content 481 | var outBuff = new Int8Array(this.response); 482 | var bout = outBuff['slice'] ? outBuff.slice() : outBuff; 483 | var isSame = true; 484 | for (var i = 0; i < bout.length; i++) 485 | { 486 | if (bout[i] !== bin[i]) 487 | { 488 | isSame = false; 489 | break; 490 | } 491 | } 492 | 493 | expect(isSame).toEqual(true); 494 | done(); 495 | } 496 | 497 | var xhr = new XMLHttpRequest(); 498 | xhr.onloadend = loadend; 499 | xhr.open("POST", 500 | SECURE_TESTS_DOMAIN + "/RestApp-ViewController-context-root/playbackservlet"); 501 | xhr.responseType = "arraybuffer"; 502 | xhr.send(inBuff); 503 | }); 504 | 505 | it("post ArrayBuffer with responseType Blob, onloadend listener", function (done) 506 | { 507 | function toArrayBuffer(blob) 508 | { 509 | var reader = new FileReader(); 510 | var promise = new Promise(function (resolve, reject) 511 | { 512 | reader.onload = function () 513 | { 514 | resolve(reader.result); 515 | }; 516 | 517 | }); 518 | 519 | reader.readAsArrayBuffer(blob); 520 | return promise; 521 | } 522 | 523 | 524 | var bin = []; 525 | bin.length = 1000; 526 | bin.fill(0, 0, 1000); 527 | var inBuff = new Int8Array(bin); 528 | 529 | function loadend(evt) 530 | { 531 | expect(this.status).toBe(200); 532 | expect(this.response instanceof Blob).toBeDefined(); 533 | 534 | toArrayBuffer(this.response).then(function (response) 535 | { 536 | // compare content 537 | var outBuff = new Int8Array(response); 538 | var bout = outBuff['slice'] ? outBuff.slice() : outBuff; 539 | var isSame = true; 540 | for (var i = 0; i < bout.length; i++) 541 | { 542 | if (bout[i] !== bin[i]) 543 | { 544 | isSame = false; 545 | break; 546 | } 547 | } 548 | 549 | expect(isSame).toEqual(true); 550 | done(); 551 | }); 552 | } 553 | 554 | var xhr = new XMLHttpRequest(); 555 | xhr.onloadend = loadend; 556 | xhr.open("POST", 557 | SECURE_TESTS_DOMAIN + "/RestApp-ViewController-context-root/playbackservlet"); 558 | xhr.responseType = "blob"; 559 | xhr.send(inBuff); 560 | }); 561 | 562 | 563 | it("post FormData with responseType text, onloadend listener", function (done) 564 | { 565 | var fd = new FormData(); 566 | fd.append("param1", "1"); 567 | fd.append("param2", "2"); 568 | 569 | function loadend(event) 570 | { 571 | expect(this.status).toBe(200); 572 | expect(this.response).toBeDefined(); 573 | expect(this.response).toContain('content-disposition: form-data; name="param1"'); 574 | expect(this.response).toContain('content-disposition: form-data; name="param2"'); 575 | done(); 576 | } 577 | 578 | var xhr = new XMLHttpRequest(); 579 | xhr.onloadend = loadend; 580 | xhr.open("POST", 581 | SECURE_TESTS_DOMAIN + "/RestApp-ViewController-context-root/playbackservlet"); 582 | xhr.responseType = "text"; 583 | var runit = false; 584 | xhr.send(fd); 585 | 586 | }); 587 | 588 | it("post document with responseType document, onloadend listener", function (done) 589 | { 590 | 591 | function loadend(event) 592 | { 593 | expect(this.status).toBe(200); 594 | expect(this.response).toBeDefined(); 595 | expect(this.response.title).toEqual(document.title); 596 | done(); 597 | } 598 | 599 | var xhr = new XMLHttpRequest(); 600 | xhr.onloadend = loadend; 601 | xhr.open("POST", 602 | SECURE_TESTS_DOMAIN + "/RestApp-ViewController-context-root/playbackservlet"); 603 | xhr.responseType = "document"; 604 | xhr.send(document); 605 | 606 | }); 607 | 608 | it("post HTML test upload events", function (done) 609 | { 610 | var html = "

Hello World

"; 611 | 612 | var uploadEvents = []; 613 | function captureUploadEvents(event) 614 | { 615 | uploadEvents.push(event.type); 616 | } 617 | 618 | function loadend(evt) 619 | { 620 | expect(this.status).toBe(200); 621 | expect(this.response).toBeDefined(); 622 | expect(this.response).toEqual(html); 623 | expect(uploadEvents).toContain("loadstart"); 624 | expect(uploadEvents).toContain("progress"); 625 | expect(uploadEvents).toContain("load"); 626 | expect(uploadEvents).toContain("loadend"); 627 | done(); 628 | } 629 | 630 | var xhr = new XMLHttpRequest(); 631 | 632 | ["ontimeout", "onloadstart", "onprogress", "onabort", "onerror", "onload", "onloadend" 633 | ].forEach(function (propName) 634 | { 635 | xhr.upload[propName] = captureUploadEvents; 636 | }); 637 | 638 | xhr.onloadend = loadend; 639 | xhr.open("POST", 640 | SECURE_TESTS_DOMAIN + "/RestApp-ViewController-context-root/playbackservlet"); 641 | xhr.responseType = "text"; 642 | xhr.send(html); 643 | }); 644 | 645 | }); 646 | 647 | 648 | describe('file:// GET:', function () 649 | { 650 | _resolveUri = function (uri) 651 | { 652 | var resolver = document.createElement("a"); 653 | document.body.appendChild(resolver); 654 | resolver.href = uri; 655 | var absoluteUri = resolver.href; 656 | resolver.parentNode.removeChild(resolver); 657 | return absoluteUri; 658 | }; 659 | 660 | 661 | it("responseType text, loadend addEventListener", function (done) 662 | { 663 | 664 | function loadend(evt) 665 | { 666 | expect(this.readyState).toBe(this.DONE); 667 | expect(this.status).toBe(200); 668 | expect(this.responseText).toBeDefined(); 669 | expect(this.responseText).toContain("folder."); 670 | done(); 671 | } 672 | 673 | var xhr = new XMLHttpRequest(); 674 | xhr.addEventListener("loadend", loadend); 675 | xhr.open("GET", "wkwebview-file-xhr-tests/customers.html"); 676 | xhr.send(); 677 | 678 | }); 679 | 680 | it("responseType text, absolute URL, loadend addEventListener", function (done) 681 | { 682 | 683 | function loadend(evt) 684 | { 685 | expect(this.readyState).toBe(this.DONE); 686 | expect(this.status).toBe(200); 687 | expect(this.responseText).toBeDefined(); 688 | expect(this.responseText).toContain("folder."); 689 | done(); 690 | } 691 | 692 | var xhr = new XMLHttpRequest(); 693 | xhr.addEventListener("loadend", loadend); 694 | var uri = _resolveUri("../wkwebview-file-xhr-tests/customers.html"); 695 | xhr.open("GET", uri); 696 | xhr.send(); 697 | 698 | }); 699 | 700 | it("responseType text, loadend onloadend", function (done) 701 | { 702 | 703 | function loadend(evt) 704 | { 705 | expect(this.readyState).toBe(this.DONE); 706 | expect(this.status).toBe(200); 707 | expect(this.responseText).toBeDefined(); 708 | expect(this.responseText).toContain("folder."); 709 | done(); 710 | } 711 | 712 | var xhr = new XMLHttpRequest(); 713 | xhr.onloadend = loadend; 714 | xhr.open("GET", "wkwebview-file-xhr-tests/customers.html"); 715 | xhr.send(); 716 | }); 717 | 718 | it("responseType text, loadend onloadend", function (done) 719 | { 720 | 721 | function loadend(evt) 722 | { 723 | expect(this.readyState).toBe(this.DONE); 724 | expect(this.status).toBe(200); 725 | expect(this.responseText).toBeDefined(); 726 | expect(this.responseText).toContain("folder."); 727 | done(); 728 | } 729 | 730 | var xhr = new XMLHttpRequest(); 731 | xhr.onloadend = loadend; 732 | xhr.open("GET", "wkwebview-file-xhr-tests/customers.html"); 733 | xhr.send(); 734 | }); 735 | 736 | 737 | it("responseType arraybuffer, loaded onloadend", function (done) 738 | { 739 | 740 | function loadend(evt) 741 | { 742 | expect(this.status).toBe(200); 743 | expect(this.response).toBeDefined(); 744 | expect(this.response instanceof ArrayBuffer).toEqual(true); 745 | done(); 746 | } 747 | 748 | var xhr = new XMLHttpRequest(); 749 | xhr.onloadend = loadend; 750 | xhr.open("GET", "wkwebview-file-xhr-tests/customers.html"); 751 | xhr.responseType = "arraybuffer"; 752 | xhr.send(); 753 | }); 754 | 755 | it("responseType blob, loaded onloadend", function (done) 756 | { 757 | 758 | function loadend(evt) 759 | { 760 | expect(this.status).toBe(200); 761 | expect(this.response).toBeDefined(); 762 | expect(this.response instanceof Blob).toEqual(true); 763 | expect(this.response.type).toEqual("text/html"); 764 | done(); 765 | } 766 | 767 | var xhr = new XMLHttpRequest(); 768 | xhr.onloadend = loadend; 769 | xhr.open("GET", "wkwebview-file-xhr-tests/customers.html"); 770 | xhr.responseType = "blob"; 771 | xhr.send(); 772 | }); 773 | 774 | it("responseType document, loaded onloadend", function (done) 775 | { 776 | 777 | function loadend(evt) 778 | { 779 | expect(this.status).toBe(200); 780 | expect(this.response).toBeDefined(); 781 | expect(this.response instanceof Document).toEqual(true); 782 | expect(this.response.getElementById("contentHeader").textContent).toEqual( 783 | "Customers Content Area"); 784 | done(); 785 | } 786 | 787 | var xhr = new XMLHttpRequest(); 788 | xhr.onloadend = loadend; 789 | xhr.open("GET", "wkwebview-file-xhr-tests/customers.html"); 790 | xhr.responseType = "document"; 791 | xhr.send(); 792 | }); 793 | 794 | it("responseType json, loaded onloadend", function (done) 795 | { 796 | 797 | function loadend(evt) 798 | { 799 | expect(this.status).toBe(200); 800 | expect(this.response).toBeDefined(); 801 | expect(this.response.name).toEqual("anonymous"); 802 | expect(this.response.comment).toContain("Copyright (c) 2014, 2016"); 803 | done(); 804 | } 805 | 806 | var xhr = new XMLHttpRequest(); 807 | xhr.onloadend = loadend; 808 | xhr.open("GET", "wkwebview-file-xhr-tests/customers.json"); 809 | xhr.responseType = "json"; 810 | xhr.send(); 811 | }); 812 | }); 813 | 814 | describe('REST end-points:', function () 815 | { 816 | it("GET Request", function (done) 817 | { 818 | function loadend(evt) 819 | { 820 | expect(this.status).toBe(200); 821 | expect(this.response).toBeDefined(); 822 | var products = this.response; 823 | expect(products.length).toEqual(100); 824 | expect(products[0].id).toEqual(0); 825 | done(); 826 | } 827 | 828 | var xhr = new XMLHttpRequest(); 829 | xhr.open("GET", 830 | SECURE_TESTS_DOMAIN + "/RestApp-ViewController-context-root/rest/products/get"); 831 | xhr.setRequestHeader("Content-Type", "application/json"); 832 | xhr.setRequestHeader("Accept", "application/json"); 833 | xhr.onloadend = loadend; 834 | xhr.responseType = "json"; 835 | xhr.send(); 836 | }); 837 | 838 | it("POST Request", function (done) 839 | { 840 | function loadend(evt) 841 | { 842 | expect(this.status).toBe(200); 843 | expect(this.response).toBeDefined(); 844 | expect(this.response.id).toEqual(99); 845 | done(); 846 | } 847 | 848 | var xhr = new XMLHttpRequest(); 849 | xhr.open("POST", 850 | SECURE_TESTS_DOMAIN + "/RestApp-ViewController-context-root/rest/products/post"); 851 | xhr.onloadend = loadend; 852 | xhr.responseType = "json"; 853 | xhr.setRequestHeader("Content-Type", "application/json"); 854 | xhr.setRequestHeader("Accept", "application/json"); 855 | xhr.send(JSON.stringify({id: 99, name: "Product 99"})); 856 | }); 857 | 858 | it("PUT Request", function (done) 859 | { 860 | function loadend(evt) 861 | { 862 | expect(this.status).toBe(200); 863 | expect(this.response).toBeDefined(); 864 | expect(this.response.id).toEqual(99); 865 | done(); 866 | } 867 | 868 | var xhr = new XMLHttpRequest(); 869 | xhr.open("PUT", 870 | SECURE_TESTS_DOMAIN + "/RestApp-ViewController-context-root/rest/products/put"); 871 | xhr.onloadend = loadend; 872 | xhr.responseType = "json"; 873 | xhr.setRequestHeader("Content-Type", "application/json"); 874 | xhr.setRequestHeader("Accept", "application/json"); 875 | xhr.send(JSON.stringify({id: 99, name: "Product 99"})); 876 | }); 877 | 878 | it("DELETE Request", function (done) 879 | { 880 | function loadend(evt) 881 | { 882 | expect(this.status).toBe(200); 883 | expect(this.response).toBeDefined(); 884 | expect(this.response.id).toEqual(99); 885 | done(); 886 | } 887 | 888 | var xhr = new XMLHttpRequest(); 889 | xhr.open("DELETE", 890 | SECURE_TESTS_DOMAIN + "/RestApp-ViewController-context-root/rest/products/delete"); 891 | xhr.onloadend = loadend; 892 | xhr.responseType = "json"; 893 | xhr.setRequestHeader("Content-Type", "application/json"); 894 | xhr.setRequestHeader("Accept", "application/json"); 895 | xhr.send(JSON.stringify({id: 99, name: "Product 99"})); 896 | }); 897 | 898 | it("POST 500 server error", function (done) 899 | { 900 | var events = []; 901 | function logEvents(event) 902 | { 903 | events.push(event.type); 904 | } 905 | 906 | function loadend(evt) 907 | { 908 | expect(this.status).toBe(500); 909 | expect(this.response).toBeDefined(); 910 | expect(events).toContain("loadstart"); 911 | expect(events).toContain("progress"); 912 | expect(events).not.toContain("error"); 913 | done(); 914 | } 915 | 916 | var xhr = new XMLHttpRequest(); 917 | xhr.open("POST", 918 | SECURE_TESTS_DOMAIN + "/RestApp-ViewController-context-root/exceptionservlet"); 919 | xhr.onloadend = loadend; 920 | xhr.onloadstart = logEvents; 921 | xhr.onprogress = logEvents; 922 | xhr.onerror = logEvents; 923 | 924 | xhr.send(" "); 925 | }); 926 | 927 | it("POST timeout", function (done) 928 | { 929 | var events = []; 930 | function logEvents(event) 931 | { 932 | events.push(event.type); 933 | } 934 | 935 | function loadend(evt) 936 | { 937 | expect(this.status).toBe(0); 938 | expect(this.response).not.toBeDefined(); 939 | expect(events).toContain("loadstart"); 940 | expect(events).toContain("progress"); 941 | expect(events).toContain("timeout"); 942 | done(); 943 | } 944 | 945 | var xhr = new XMLHttpRequest(); 946 | xhr.open("POST", 947 | SECURE_TESTS_DOMAIN + "/RestApp-ViewController-context-root/rest/products/postTimeout"); 948 | xhr.onloadend = loadend; 949 | xhr.onloadstart = logEvents; 950 | xhr.onprogress = logEvents; 951 | xhr.ontimeout = logEvents; 952 | xhr.timeout = 1; 953 | xhr.responseType = "json"; 954 | xhr.setRequestHeader("Content-Type", "application/json"); 955 | xhr.setRequestHeader("Accept", "application/json"); 956 | xhr.send(JSON.stringify({timeout: 5000})); 957 | }); 958 | 959 | it("GET utf-16", function (done) 960 | { 961 | 962 | function loadend(evt) 963 | { 964 | expect(this.status).toBe(200); 965 | expect(this.response).toEqual(unescape("nice tunes \u000E\u000E")); 966 | expect(this.getResponseHeader("content-type")).toEqual("text/html;charset=utf-16"); 967 | expect(this.getResponseHeader("Content-Type")).toEqual("text/html;charset=utf-16"); 968 | done(); 969 | } 970 | 971 | var xhr = new XMLHttpRequest(); 972 | xhr.open("GET", 973 | SECURE_TESTS_DOMAIN + 974 | "/RestApp-ViewController-context-root/rest/products/getspecialenc"); 975 | xhr.onloadend = loadend; 976 | xhr.responseType = "text"; 977 | xhr.setRequestHeader("Content-Type", "application/json"); 978 | xhr.setRequestHeader("Accept", "text/html"); 979 | xhr.send(); 980 | }); 981 | 982 | }, 10000); 983 | 984 | describe('Cordova API:', function () 985 | { 986 | it("CDVWKWebViewFileXhr readAsText no args", function (done) 987 | { 988 | function success() 989 | { 990 | // should not be called 991 | expect(true).toEqual(false); 992 | done(); 993 | } 994 | function error() 995 | { 996 | expect(true).toEqual(true); 997 | done(); 998 | } 999 | var exec = require('cordova/exec'); 1000 | // invoke readAsText with on arguments. expected outcome is an error 1001 | exec(success, error, "CDVWKWebViewFileXhr", "readAsText", []); 1002 | }); 1003 | 1004 | it("CDVWKWebViewFileXhr readAsArrayBuffer no args", function (done) 1005 | { 1006 | function success() 1007 | { 1008 | // should not be called 1009 | expect(true).toEqual(false); 1010 | done(); 1011 | } 1012 | function error() 1013 | { 1014 | expect(true).toEqual(true); 1015 | done(); 1016 | } 1017 | var exec = require('cordova/exec'); 1018 | // invoke readAsArrayBuffer with on arguments. expected outcome is an error 1019 | exec(success, error, "CDVWKWebViewFileXhr", "readAsArrayBuffer", []); 1020 | }); 1021 | 1022 | it("CDVWKWebViewFileXhr readAsText relative path security 404", function (done) 1023 | { 1024 | function success() 1025 | { 1026 | // should not be called 1027 | expect(true).toEqual(false); 1028 | done(); 1029 | } 1030 | function error(msg) 1031 | { 1032 | expect(true).toEqual(true); 1033 | done(); 1034 | } 1035 | var exec = require('cordova/exec'); 1036 | // invoke readAsArrayBuffer with on arguments. expected outcome is an error 1037 | exec(success, error, "CDVWKWebViewFileXhr", "readAsText", ["../cordova/Api.js"]); 1038 | }); 1039 | }); 1040 | 1041 | describe('nativeXHR remote GET', function () 1042 | { 1043 | it("nativeXHR GET Request", function (done) 1044 | { 1045 | expects["id1"] = function (result) 1046 | { 1047 | expect(result.response.statusCode).toBe(200); 1048 | expect(result.response.mimeType).toBe("application/json"); 1049 | expect(result.response.localizedStatusCode).toBe("no error"); 1050 | expect(result.response.allHeaderFields).toBeDefined(); 1051 | expect(result.response.allHeaderFields["Content-Type"]).toBe("application/json"); 1052 | expect(result.response.allHeaderFields["Content-Length"]).toBe("3881"); 1053 | expect(result.response.mimeType).toBe("application/json"); 1054 | expect(result.error).not.toBeDefined(); 1055 | expect(result.data).toBeDefined(); 1056 | done(); 1057 | }; 1058 | 1059 | var xhr = {}; 1060 | xhr.headers = {"Content-Type": "application/json", "Accept": "application/json"}; 1061 | xhr.url = SECURE_TESTS_DOMAIN + "/RestApp-ViewController-context-root/rest/products/get"; 1062 | xhr.id = "id1"; 1063 | xhr.method = "GET"; 1064 | xhr.callback = "xhrCallback"; 1065 | window.webkit.messageHandlers.nativeXHR.postMessage(xhr); 1066 | }); 1067 | 1068 | it("nativeXHR GET Missing Resource Request", function (done) 1069 | { 1070 | expects["id2"] = function (result) 1071 | { 1072 | expect(result.response.statusCode).toBe(404); 1073 | expect(result.error).not.toBeDefined(); 1074 | expect(result.data).toBeDefined(); 1075 | done(); 1076 | }; 1077 | 1078 | var xhr = {}; 1079 | xhr.url = SECURE_TESTS_DOMAIN + "/RestApp-ViewController-context-root/rest/products/does-not-exist"; 1080 | xhr.id = "id2"; 1081 | xhr.callback = "xhrCallback"; 1082 | window.webkit.messageHandlers.nativeXHR.postMessage(xhr); 1083 | }); 1084 | 1085 | it("nativeXHR GET Server Doesn't Exist", function (done) 1086 | { 1087 | expects["id3"] = function (result) 1088 | { 1089 | expect(result.response).not.toBeDefined(); 1090 | expect(result.error).toBeDefined(); 1091 | expect(result.data).not.toBeDefined(); 1092 | done(); 1093 | }; 1094 | 1095 | var xhr = {}; 1096 | xhr.url = "http://i-am-not.a.validserver.us.oracle.com/"; 1097 | xhr.id = "id3"; 1098 | xhr.callback = "xhrCallback"; 1099 | window.webkit.messageHandlers.nativeXHR.postMessage(xhr); 1100 | }); 1101 | 1102 | it("nativeXHR getConfig", function (done) 1103 | { 1104 | 1105 | function success(result) 1106 | { 1107 | // should not be called 1108 | expect(result.InterceptRemoteRequests).toEqual("secureOnly"); 1109 | expect(["none", "full"]).toContain(result.NativeXHRLogging); 1110 | done(); 1111 | } 1112 | function error() 1113 | { 1114 | expect(false).toEqual(true); 1115 | done(); 1116 | } 1117 | var exec = require('cordova/exec'); 1118 | exec(success, error, "CDVWKWebViewFileXhr", "getConfig", []); 1119 | 1120 | }); 1121 | 1122 | it("nativeXHR GET timeout", function (done) 1123 | { 1124 | expects["id4"] = function (result) 1125 | { 1126 | expect(result.response).not.toBeDefined(); 1127 | expect(result.error).toBeDefined(); 1128 | expect(result.error).toEqual("The request timed out."); 1129 | expect(result.underlyingErrorCode).toBeDefined(); 1130 | expect(result.underlyingErrorCode).toEqual(-1001); 1131 | expect(result.data).not.toBeDefined(); 1132 | done(); 1133 | }; 1134 | 1135 | var xhr = {}; 1136 | xhr.headers = {"Content-Type": "application/json", "Accept": "application/json"}; 1137 | xhr.url = SECURE_TESTS_DOMAIN + "/RestApp-ViewController-context-root/rest/products/postTimeout"; 1138 | xhr.id = "id4"; 1139 | xhr.timeout = 1; 1140 | xhr.callback = "xhrCallback"; 1141 | xhr.method = "POST" 1142 | xhr.body = btoa(JSON.stringify({timeout: 5000})); 1143 | window.webkit.messageHandlers.nativeXHR.postMessage(xhr); 1144 | }); 1145 | 1146 | describe('Custom Headers', function () 1147 | { 1148 | it("normalize types", function (done) 1149 | { 1150 | var html = "

Hello World

"; 1151 | 1152 | var now = new Date(); 1153 | 1154 | function loadend(evt) 1155 | { 1156 | expect(this.status).toBe(200); 1157 | expect(this.response).toBeDefined(); 1158 | expect(this.response).toEqual(html); 1159 | expect(this.getResponseHeader("x-custom-1")).toEqual("42"); 1160 | expect(this.getResponseHeader("x-custom-2")).toEqual("1,2,3"); 1161 | expect(this.getResponseHeader("x-custom-3")).toEqual(now.toString()); 1162 | done(); 1163 | } 1164 | 1165 | var xhr = new XMLHttpRequest(); 1166 | xhr.addEventListener("loadend", loadend); 1167 | xhr.setRequestHeader("x-custom-1", 42); 1168 | xhr.setRequestHeader("x-custom-2", [1, 2, 3]); 1169 | xhr.setRequestHeader("x-custom-3", now); 1170 | xhr.open("POST", 1171 | SECURE_TESTS_DOMAIN + "/RestApp-ViewController-context-root/playbackservlet"); 1172 | xhr.responseType = "text"; 1173 | xhr.send(html); 1174 | }); 1175 | 1176 | }); 1177 | /* 1178 | This test is disabled due to the endpoint. The application's config.xml must be configured with 1179 | the AllowUntrustedCerts = on preference to enable support for self signed certificates 1180 | */ 1181 | /* 1182 | it("nativeXHR testAllowUntrustedCerts", function (done) 1183 | { 1184 | expects["id5"] = function (result) 1185 | { 1186 | expect(result.response.statusCode).toEqual(200); 1187 | done(); 1188 | }; 1189 | 1190 | var xhr = {}; 1191 | xhr.url = "https://den02mkn.us.oracle.com:4443/serviceapi/entityModel/metadata/entityTypes?count=true"; 1192 | xhr.headers = {"authorization": "Basic ZW1hYXN0ZXN0dGVuYW50MS5lbWNzYWRtaW46V2VsY29tZTEh", 1193 | "cache-control": "no-cache", 1194 | "x-user-identity-domain-name": "emaastesttenant1"}; 1195 | xhr.id = "id5"; 1196 | xhr.callback = "xhrCallback"; 1197 | window.webkit.messageHandlers.nativeXHR.postMessage(xhr); 1198 | }, 30000); 1199 | */ 1200 | }); 1201 | 1202 | /* 1203 | // commented out the PSR tests as they take several minutes to run 1204 | describe('PSR Remote:', function () 1205 | { 1206 | function getMbBuffer(numMbs) 1207 | { 1208 | if (isNaN(numMbs)) 1209 | numMbs = 1; 1210 | 1211 | var fillArray = []; 1212 | var bytes = numMbs * 1048576; 1213 | fillArray.length = bytes; 1214 | fillArray.fill(0, 0, bytes); 1215 | 1216 | return new Int8Array(fillArray); 1217 | } 1218 | 1219 | function getMbString(numMbs) 1220 | { 1221 | if (isNaN(numMbs)) 1222 | numMbs = 1; 1223 | 1224 | var fillArray = []; 1225 | var bytes = numMbs * 1048576; 1226 | fillArray.length = bytes; 1227 | fillArray.fill(0, 0, bytes); 1228 | 1229 | return fillArray.join(""); 1230 | } 1231 | 1232 | var logSummary = []; 1233 | function logMeasure(test, buffsize, startTs) 1234 | { 1235 | var totalSecs = ((performance.now() - startTs) / 1000); 1236 | var totalMb = buffsize / 1048576; 1237 | var mbPerSecs = totalSecs / totalMb; 1238 | 1239 | 1240 | var msg = [test, "send/recieve", buffsize, "bytes in", + 1241 | totalSecs, 1242 | "sec(s)."].join(" "); 1243 | 1244 | var tokens = test.split(" "); 1245 | logSummary.push(['"' + tokens[0] + '"', 1246 | '"' + tokens[1] + '"', 1247 | tokens[2], 1248 | totalMb, 1249 | totalSecs, 1250 | mbPerSecs].join(",")); 1251 | 1252 | console.log(msg); 1253 | } 1254 | 1255 | function dumpLogSummary() 1256 | { 1257 | var xhr = new XMLHttpRequest(); 1258 | xhr.open("POST", 1259 | SECURE_TESTS_DOMAIN + 1260 | "/RestApp-ViewController-context-root/rest/products/postPsrLog"); 1261 | xhr.responseType = "text"; 1262 | xhr.setRequestHeader("Content-Type", "application/json"); 1263 | xhr.setRequestHeader("Accept", "text/html"); 1264 | xhr.send(logSummary.join("\n")); 1265 | logSummary = []; 1266 | } 1267 | 1268 | function psrTest(description, sizeInMb, resonseType, xhr, resolve) 1269 | { 1270 | var buff; 1271 | if ("arraybuffer" === resonseType) 1272 | buff = getMbBuffer(sizeInMb); 1273 | else if ("text" === resonseType) 1274 | buff = getMbString(sizeInMb); 1275 | 1276 | var startTs; 1277 | 1278 | function loadend(evt) 1279 | { 1280 | expect(this.status).toBe(200); 1281 | expect(this.response).toBeDefined(); 1282 | 1283 | var size = Number.NaN; 1284 | if ("arraybuffer" === resonseType && this.response && !isNaN(this.response['byteLength'])) 1285 | size = this.response.byteLength; 1286 | else if ("text" === resonseType && this.response && !isNaN(this.response['length'])) 1287 | size = this.response.length; 1288 | 1289 | logMeasure(description, size, startTs); 1290 | resolve(true); 1291 | } 1292 | 1293 | xhr.onloadend = loadend; 1294 | xhr.open("POST", 1295 | NONSECURE_TESTS_DOMAIN + "/RestApp-ViewController-context-root/playbackservlet"); 1296 | xhr.responseType = resonseType; 1297 | 1298 | startTs = performance.now(); 1299 | xhr.send(buff); 1300 | } 1301 | 1302 | function sendXHR(description, sizeInMB, resonseType, xhr) 1303 | { 1304 | var execCallback = psrTest.bind(this, description, sizeInMB, resonseType, xhr); 1305 | return new Promise(execCallback); 1306 | } 1307 | 1308 | var MAX_RETRIES = 3; 1309 | var MAX_MB = 5; 1310 | 1311 | function forEach(responseType, xhrType, retry, sizeInMb) 1312 | { 1313 | if (isNaN(retry)) 1314 | retry = 1; 1315 | if (isNaN(sizeInMb)) 1316 | sizeInMb = 1; 1317 | 1318 | var description = [xhrType, responseType, retry].join(" "); 1319 | 1320 | var xhr; 1321 | if (xhrType === "NativeJS") 1322 | { 1323 | xhr = new window._XMLHttpRequest(); 1324 | } 1325 | else if (xhrType === "DelegateNativeJS") 1326 | { 1327 | xhr = new XMLHttpRequest(); 1328 | xhr.__setInterceptRemoteRequests("none"); 1329 | } 1330 | else if (xhrType === "NativeIOS") 1331 | { 1332 | xhr = new XMLHttpRequest(); 1333 | xhr.__setInterceptRemoteRequests("all"); 1334 | } 1335 | 1336 | return sendXHR(description, sizeInMb, responseType, xhr).then(function () 1337 | { 1338 | if (MAX_RETRIES < ++retry) 1339 | { 1340 | retry = 1; 1341 | if (MAX_MB < ++sizeInMb) 1342 | return Promise.resolve(true); 1343 | } 1344 | 1345 | return forEach(responseType, xhrType, retry, sizeInMb); 1346 | }); 1347 | } 1348 | 1349 | it("Native JS arraybuffer send/recieve", function (done) 1350 | { 1351 | forEach("arraybuffer", "NativeJS").then(function () 1352 | { 1353 | done(); 1354 | }); 1355 | }, 240000); 1356 | 1357 | it("Delegate Native JS arraybuffer send/recieve", function (done) 1358 | { 1359 | forEach("arraybuffer", "DelegateNativeJS").then(function () 1360 | { 1361 | done(); 1362 | }); 1363 | }, 240000); 1364 | 1365 | it("Native IOS arraybuffer send/recieve", function (done) 1366 | { 1367 | forEach("arraybuffer", "NativeIOS").then(function () 1368 | { 1369 | done(); 1370 | }); 1371 | }, 240000); 1372 | 1373 | 1374 | it("Native JS text send/recieve", function (done) 1375 | { 1376 | forEach("text", "NativeJS").then(function () 1377 | { 1378 | done(); 1379 | }); 1380 | }, 240000); 1381 | 1382 | it("Delegate Native JS text send/recieve", function (done) 1383 | { 1384 | forEach("text", "DelegateNativeJS").then(function () 1385 | { 1386 | done(); 1387 | }); 1388 | }, 240000); 1389 | 1390 | it("Native IOS text send/recieve", function (done) 1391 | { 1392 | forEach("text", "NativeIOS").then(function () 1393 | { 1394 | done(); 1395 | dumpLogSummary(); 1396 | }); 1397 | }, 240000); 1398 | }); 1399 | 1400 | */ 1401 | }; 1402 | 1403 | --------------------------------------------------------------------------------