├── .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 |
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 |
--------------------------------------------------------------------------------