Compare commits

...

291 Commits

Author SHA1 Message Date
Sefa Ilkimen
b6a8acd25b release v3.2.0 2021-07-26 01:22:31 +02:00
Sefa Ilkimen
0ccf64e289 chore: add workaround for missing DX files in build-tools 31 2021-07-22 15:40:38 +02:00
Sefa Ilkimen
986ddeefd4 chore: remove obsolete mipsel workaround 2021-07-22 14:36:34 +02:00
Sefa Ilkimen
67c2c733f5 chore: enable network logging on browserstack 2021-07-15 15:34:34 +02:00
Sefa Ilkimen
d077d02062 Merge pull request #421 from silkimen/feat/#420-implement-blacklist-to-disable-TLS-protocols-on-Android
feat: #420 implement blacklist to disable unsafe SSL/TLS protocol ver…
2021-07-15 14:35:24 +02:00
Sefa Ilkimen
060794354d docu: add docu for secure socket protocol blacklisting 2021-07-15 14:19:10 +02:00
Sefa Ilkimen
4ebd259c7e Merge branch 'feat/#420-implement-blacklist-to-disable-TLS-protocols-on-Android' of https://github.com/silkimen/cordova-plugin-advanced-http into feat/#420-implement-blacklist-to-disable-TLS-protocols-on-Android 2021-07-15 12:53:39 +02:00
Sefa Ilkimen
bbd4cf01ab refactor: apply review feddback 2021-07-15 12:53:12 +02:00
Sefa Ilkimen
7eb3395aa4 Update test/e2e-specs.js
Co-authored-by: Robin Hartmann <contact.robin.hartmann@gmail.com>
2021-07-15 12:38:09 +02:00
Sefa Ilkimen
05abdfcd91 Update CHANGELOG.md
Co-authored-by: Robin Hartmann <contact.robin.hartmann@gmail.com>
2021-07-15 12:36:10 +02:00
Sefa Ilkimen
bda4eedfb9 feat: #420 implement blacklist to disable unsafe SSL/TLS protocol versions on Android 2021-07-15 04:00:35 +02:00
Sefa Ilkimen
9d6005af29 - chore: implement new test reporter to see more details on failed tests
- fix: fix broken e2e tests on Android 8+
2021-07-15 03:14:46 +02:00
Sefa Ilkimen
94126b9021 release v3.1.1 2021-07-07 12:41:00 +02:00
Sefa Ilkimen
007d5c609f chore: reduce false positives in e2e tests 2021-07-07 05:07:34 +02:00
Sefa Ilkimen
badf6dcdc2 fix: #372 [Bug] Android: malformed empty multipart requests 2021-07-07 03:16:43 +02:00
Sefa Ilkimen
c081060a9e fix: broken e2e tests caused by wrong plugin entries in app template 2021-07-07 03:15:21 +02:00
Sefa Ilkimen
2ce130133c - chore: update Travis CI badge
- chore: update Travis CI Xcode image version
2021-07-06 22:40:58 +02:00
Sefa Ilkimen
cfcc572ca0 chore(CI): remove slack integration 2021-07-06 19:08:54 +02:00
Sefa Ilkimen
d7688b485d fix: e2e test returns false positive due to test timing (request finished to early) 2021-03-24 06:54:59 +01:00
Sefa Ilkimen
81ba667e37 fix: broken e2e spec 2021-03-24 06:43:42 +01:00
Sefa Ilkimen
5ad1967b46 Merge branch 'master' of https://github.com/silkimen/cordova-plugin-advanced-http 2021-03-24 06:24:39 +01:00
Sefa Ilkimen
90ed474b29 docs: update changelog 2021-03-24 06:24:20 +01:00
Sefa Ilkimen
1947906c4c Merge pull request #399 from avargaskun/master
Fix: Memory leak on iOS
2021-03-24 06:22:08 +01:00
Sefa Ilkimen
b25b07a2a7 chore: update appium caps for local testing 2021-03-24 06:18:33 +01:00
Sefa Ilkimen
a01625ecfb chore: update requested appium version for saucelabs build 2021-03-24 06:06:58 +01:00
Sefa Ilkimen
b03ae7d6d1 chore: update appium capabilites for testing on saucelabs 2021-03-24 05:53:22 +01:00
Sefa Ilkimen
fdaea418be chore: update capabilities for e2e tests running on browser stack
- increase test device OS versions
2021-03-24 05:35:45 +01:00
Sefa Ilkimen
d84df0fb00 chore: update travis CI pipeline
- use node 14
- use `xcode12.2` image for iOS build
2021-03-24 05:25:20 +01:00
Sefa Ilkimen
baedd60329 chore: update cordova cli and cordova platform versions in e2e template 2021-03-24 05:12:00 +01:00
Antonio Vargas Garcia
80b22d4202 Fix: Memory leak on iOS
- Requests are leaking instances of the AFHTTPSessionManager
- Over time this causes iOS to terminate the app
- Inspiration for the fix: https://stackoverflow.com/a/41345142
2021-02-10 22:16:41 -08:00
Sefa Ilkimen
89ac260ef6 Merge pull request #390 from silkimen/dependabot/npm_and_yarn/ini-1.3.8
chore(deps): bump ini from 1.3.5 to 1.3.8
2021-01-18 03:58:02 +01:00
dependabot[bot]
6a266218fb chore(deps): bump ini from 1.3.5 to 1.3.8
Bumps [ini](https://github.com/isaacs/ini) from 1.3.5 to 1.3.8.
- [Release notes](https://github.com/isaacs/ini/releases)
- [Commits](https://github.com/isaacs/ini/compare/v1.3.5...v1.3.8)

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-18 02:49:36 +00:00
Sefa Ilkimen
6050830423 Merge pull request #389 from ath0mas/fix/344-redirect-specs
Resolve #344 restore and fix redirect specs
2021-01-18 03:31:31 +01:00
Sefa Ilkimen
95c9eb85d0 Merge pull request #394 from ath0mas/feature/docs-local-testing
docs: update readme to include Local Testing
2021-01-18 02:28:45 +01:00
Alexis THOMAS
7e3ff25195 ignore idea files 2021-01-17 21:31:13 +01:00
Alexis THOMAS
8a6b9e583e docs: update readme to include Local Testing 2021-01-17 21:26:11 +01:00
Alexis THOMAS
b32dfc7143 fix #344: "disableRedirect" removed since 3.0, use "setFollowRedirect" 2021-01-17 18:26:37 +01:00
Alexis THOMAS
a72bf758c6 docs: link to go-httpbin 2020-12-10 00:06:32 +01:00
Alexis THOMAS
45f9cbfaa4 fix #344: change redirect specs to httpbingo.org 2020-12-10 00:04:18 +01:00
Sefa Ilkimen
f8f52e1e97 chore: add FUNDING.yml 2020-12-09 00:54:33 +01:00
Sefa Ilkimen
9b995724f4 chore: use node v12 in Travis CI builds 2020-10-19 04:32:01 +02:00
Sefa Ilkimen
25a0a9a7ae fix: #372 [Bug] Android: malformed empty multipart requests 2020-10-19 04:11:49 +02:00
Sefa Ilkimen
e44def06a5 refactor: some minor cleanup and refactoring 2020-10-19 01:35:05 +02:00
Sefa Ilkimen
3d288951bf chore: update dev dependencies 2020-10-19 00:19:48 +02:00
Sefa Ilkimen
f39063cec9 chore: disable CodeQL for Java 2020-10-19 00:10:49 +02:00
Sefa Ilkimen
1bcb8016ff Setup CodeQL Analysis 2020-10-18 23:58:36 +02:00
Sefa Ilkimen
6339b9b83d release v3.1.0 2020-10-16 00:58:40 +02:00
Sefa Ilkimen
389534d661 chore: update changelog 2020-10-13 00:46:48 +02:00
Sefa Ilkimen
c10722eca0 Merge branch 'mmig-feature-abort' 2020-10-13 00:23:34 +02:00
Sefa Ilkimen
6a60058fc6 chore: auto fix some indentations 2020-10-13 00:22:42 +02:00
russa
fcedfae1ac FIX do not evaluate test result if test is skipped 2020-10-09 16:51:43 +02:00
russa
aca165b900 added unsupported warning for abort for Android versions < 6 2020-10-08 21:17:26 +02:00
russa
09c2b383ff skip abort-tests if Android version is < 6 2020-10-08 21:10:45 +02:00
russa
6918a2ed15 added support for skipping tests 2020-10-08 21:08:30 +02:00
russa
5b827d500d improve handling for race condition that request finished before adding it to pending requests map 2020-10-08 21:06:56 +02:00
russa
1c27b62148 added configuration for allowing http (cleartext) requests on Android 7 and later (sdk >= 24) 2020-10-08 18:12:59 +02:00
Sefa Ilkimen
f33a911e7e Merge pull request #364 from silkimen/dependabot/npm_and_yarn/bl-4.0.3
chore(deps): bump bl from 4.0.2 to 4.0.3
2020-10-01 02:20:41 +02:00
russa
097faad07a removed unsupported warning for aborting requests on ios from README 2020-09-25 18:59:45 +02:00
russa
62c400c6db added support for aborting requests for ios platform 2020-09-25 18:56:15 +02:00
russa
f823d24438 added tests for abort 2020-09-16 19:51:29 +02:00
russa
64a7148444 FIX use same error message for abort as on android platform 2020-09-16 19:48:23 +02:00
russa
f6736d9150 added usage description for abort() 2020-09-16 18:37:15 +02:00
russa
bc90ae85fb added 'unsupported' feedback for abort() on ios platform 2020-09-16 18:36:55 +02:00
russa
389e860125 added support for abort() on browser platform 2020-09-16 18:35:01 +02:00
russa
2367d264c1 added support for abort() on android platform 2020-09-16 18:05:30 +02:00
russa
269d5d4c8a added abort() function to js-module 2020-09-16 17:44:59 +02:00
dependabot[bot]
b6ee4de379 chore(deps): bump bl from 4.0.2 to 4.0.3
Bumps [bl](https://github.com/rvagg/bl) from 4.0.2 to 4.0.3.
- [Release notes](https://github.com/rvagg/bl/releases)
- [Commits](https://github.com/rvagg/bl/compare/v4.0.2...v4.0.3)

Signed-off-by: dependabot[bot] <support@github.com>
2020-09-02 15:37:00 +00:00
Sefa Ilkimen
5f327dc82a release v3.0.1 2020-08-18 12:52:01 +02:00
Sefa Ilkimen
1639efe8d0 fix: #355 [Bug] [Browser] responseType "json" not working with valid JSON response 2020-08-18 02:16:03 +02:00
Sefa Ilkimen
9bb0c58e35 chore: bump version and update readme 2020-08-17 15:59:23 +02:00
Sefa Ilkimen
57562a0dcf fix: #359 [Bug] [Android] memory leakage leads to app crashes 2020-08-17 03:02:05 +02:00
Sefa Ilkimen
ad4079625e Merge pull request #354 from silkimen/dependabot/npm_and_yarn/lodash-4.17.19
chore(deps): bump lodash from 4.17.15 to 4.17.19
2020-08-17 00:47:29 +02:00
dependabot[bot]
98d3d38e07 chore(deps): bump lodash from 4.17.15 to 4.17.19
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-18 20:24:07 +00:00
Sefa Ilkimen
c748406090 release v3.0.0 2020-07-14 03:13:05 +02:00
Sefa Ilkimen
dc6cf4d45b chore: fix update-plugin-xml.js 2020-07-14 03:12:29 +02:00
Sefa Ilkimen
7661e02598 chore: accept android sdk licenses 2020-07-14 02:39:59 +02:00
Sefa Ilkimen
20ec4bee31 chore: use node 10.x for TravisCI build 2020-07-14 01:33:22 +02:00
Sefa Ilkimen
4eed89e530 chore: update change log 2020-07-14 00:50:49 +02:00
Sefa Ilkimen
f43b2f9f5c Merge pull request #345 from ikosta/master
fix(): default filename for blob
2020-07-07 17:43:46 +02:00
Konstantinos Tsanakas
7b4d37acd9 Update www/ponyfills.js
Co-authored-by: Sefa Ilkimen <silkimen@users.noreply.github.com>
2020-07-07 11:03:36 +02:00
Konstantinos Tsanakas
ca5306cb47 fix(ponyfills): default filename for blob 2020-07-06 15:23:02 +02:00
Konstantinos Tsanakas
c0c1af5ee6 fix(): default filename for blob 2020-06-25 13:36:38 +02:00
Sefa Ilkimen
42e629e34d chore: update readme 2020-06-25 10:35:29 +02:00
Sefa Ilkimen
3bec8dde5f feat: #158 support removing headers which were previously set via "setHeader" 2020-06-25 10:28:43 +02:00
Sefa Ilkimen
8a3bc17810 chore: update dependencies 2020-06-25 09:35:06 +02:00
Sefa Ilkimen
1eb83478dc - deprecate: #297 drop support for Android < 5.1
- bump version
- remove deprecated functions
2020-06-25 07:47:51 +02:00
Sefa Ilkimen
1f0df54111 workaround #344: disable two specs until https://github.com/postmanlabs/httpbin/issues/617 is fixed 2020-06-25 07:09:53 +02:00
Sefa Ilkimen
ef09e466ec release v2.5.1 2020-06-04 01:32:33 +02:00
Sefa Ilkimen
88de0550f4 chore: update README 2020-05-30 18:39:20 +02:00
Sefa Ilkimen
7f680b07ec chore: update changelog 2020-05-29 05:03:12 +02:00
Sefa Ilkimen
a259e7cf8d Merge branch 'clear-cookies-ios'
# Conflicts:
#	test/e2e-specs.js
2020-05-29 05:00:25 +02:00
Sefa Ilkimen
1e3c9e6645 chore: update changelog 2020-05-29 04:22:50 +02:00
Sefa Ilkimen
e0cf458179 Merge branch 'antikalk-master' 2020-05-29 04:15:02 +02:00
Sefa Ilkimen
4f4e7ffa33 fix: broken handling for empty strings 2020-05-29 04:03:20 +02:00
Sefa Ilkimen
65e4a4d4dc fix: e2e spec "should allow empty response body even though responseType is set #334"
- `cordova.plugin.http.get` does not support passing an options object => using `cordova.plugin.http.sendRequest` instead
- but still fails on test "should return empty body string correctly (GET)"
2020-05-29 03:35:20 +02:00
antikalk
fed2b03cc6 Revert "+ dont manipulate response data"
This reverts commit bfc6ba2008.
2020-05-21 22:41:49 +02:00
antikalk
29e0b385de Merge branch 'master' of https://github.com/antikalk/cordova-plugin-advanced-http 2020-05-16 16:55:10 +02:00
antikalk
bfc6ba2008 + dont manipulate response data 2020-05-16 16:54:58 +02:00
Oliver
060aa088f5 Merge branch 'master' into master 2020-05-15 08:32:03 +02:00
antikalk
e775057389 + added e2e test 2020-05-14 22:22:03 +02:00
antikalk
81874c6bc8 + test if empty response data is handled correctly 2020-05-13 09:13:51 +02:00
antikalk
658bbd8b28 + set data to null if response data is not set 2020-05-13 09:12:59 +02:00
antikalk
a1e4be37d4 + early return pattern 2020-05-13 08:07:13 +02:00
antikalk
5be52d78d1 + handle empty success responses 2020-05-12 14:44:22 +02:00
Sefa Ilkimen
0a23e29403 chore: document X.509 client cert feature 2020-05-02 00:48:52 +02:00
Sefa Ilkimen
6009ae82f7 chore: update readme to prevent confusion on request timeout 2020-05-02 00:06:58 +02:00
Sefa Ilkimen
0bfb81c57f chore: fix browserstack testing for android 2020-05-01 23:56:39 +02:00
Sefa Ilkimen
f8f4bdc9df fix: auth challenge block isn't handling server trust settings correctly 2020-03-05 03:23:28 +01:00
Sefa Ilkimen
d84e3995d2 chore: update appium caps 2020-03-05 02:04:48 +01:00
Sefa Ilkimen
f5597dd176 WIP: implement setClientAuthMode()for iOS 2020-03-05 01:36:27 +01:00
Sefa Ilkimen
b25b7db4be release v2.4.1 2020-02-18 01:04:57 +01:00
Sefa Ilkimen
92be8f8e96 fix #248: clearCookies() does not work on iOS 2020-02-03 03:58:09 +01:00
Sefa Ilkimen
9ffdc5d2eb test: implement e2e test for #248 2020-02-03 03:23:50 +01:00
Sefa Ilkimen
23a98ae491 fix #300: FormData object containing null or undefined value is not serialized correctly 2020-02-03 02:46:31 +01:00
Sefa Ilkimen
ab9dad8f46 Merge branch 'antikalk-master' 2020-02-03 01:38:00 +01:00
Sefa Ilkimen
69344b5357 chore: update changelog 2020-02-03 01:37:21 +01:00
Sefa Ilkimen
c88d1b6cc7 test: implement e2e test for #301 2020-02-03 01:30:28 +01:00
antikalk
b6f369b868 fix for issues #220 and #286
When responseType is set to json, the data should be returned as plain json string, not as a base64 encoded string.
2020-01-29 20:14:06 +01:00
Sefa Ilkimen
39fa17e4ed update changelog 2020-01-27 23:57:45 +01:00
Sefa Ilkimen
5a19c6ad06 Merge branch 'fix/#296-multipart-serializer-on-browser-platform' 2020-01-27 23:45:53 +01:00
Sefa Ilkimen
192059d34e release v2.4.0 2020-01-27 04:49:11 +01:00
Sefa Ilkimen
7193b636f1 feat #283: improve error message on timeout on browser platform 2020-01-27 04:44:13 +01:00
Sefa Ilkimen
662b4352f5 update readme and changelog 2020-01-27 02:58:45 +01:00
Sefa Ilkimen
e18f9eee51 implement feat #155 for iOS 2020-01-27 02:22:29 +01:00
Sefa Ilkimen
c7eb60e670 implement feat #155 for android 2020-01-27 02:10:32 +01:00
Sefa Ilkimen
6a930de82f implement www interface for #155 2020-01-27 02:10:01 +01:00
Sefa Ilkimen
588e4a0e57 implement e2e specs for #155 2020-01-27 02:09:21 +01:00
Sefa Ilkimen
78db1dc516 fix #296: [Bug] [browser] multipart requests are not serialized correctly 2020-01-27 01:29:04 +01:00
Sefa Ilkimen
aded59e3d1 implement raw serializer support for browser target 2020-01-26 18:41:31 +01:00
Sefa Ilkimen
9ef582b37f Merge branch 'chuchuva-raw-request-body' 2020-01-26 18:00:47 +01:00
Sefa Ilkimen
33fea67603 update readme and changelog 2020-01-26 17:55:04 +01:00
Sefa Ilkimen
faffe0e078 fix e2e tests for raw serializer 2020-01-26 17:50:40 +01:00
Sefa Ilkimen
99c7f5d331 add default content type for raw serializer 2020-01-26 17:50:18 +01:00
Pavel Chuchuva
b74ad73742 Add documentation for 'raw' serializer 2020-01-18 08:44:58 +11:00
Pavel Chuchuva
7fd7ab223c Add tests for 'raw' serializer 2020-01-18 08:23:43 +11:00
Pavel Chuchuva
937010bd4e Add support for sending 'raw' requests on iOS too 2020-01-14 16:09:47 +11:00
Pavel Chuchuva
7ab4b634ca Add support for sending 'raw' requests on Android
request.data can be `Uint8Array` or `ArrayBuffer`. The plugin will send it as is, without any processing.
2020-01-12 08:18:02 +11:00
Sefa Ilkimen
32187a12fe chore: fix #281 broken builds in forks 2019-12-15 05:18:58 +01:00
Sefa Ilkimen
a4f121728c release v2.3.1 2019-12-14 04:36:31 +01:00
Sefa Ilkimen
21d991b04e chore: setup testing android version on BrowserStack 2019-12-14 04:09:11 +01:00
Sefa Ilkimen
31b1eee355 chore: update version and changelog 2019-12-14 02:15:49 +01:00
Sefa Ilkimen
3d1b195831 Merge pull request #275 from ath0mas/patch-1
Fix WebStorageCookieStore getAllCookies()
2019-12-14 02:08:23 +01:00
Alexis THOMAS
014753e127 fix(cookieStore): error in getAllCookies
typo in Array.prototype call was throwing a TypeError
2019-12-05 15:38:30 +01:00
Sefa Ilkimen
5ee26bc2cc Update badges in readme 2019-12-02 02:46:21 +01:00
Sefa Ilkimen
7d7f02b4b3 release v2.3.0 2019-12-02 02:20:52 +01:00
Sefa Ilkimen
afc9e3e944 Update readme 2019-12-01 07:30:45 +01:00
Sefa Ilkimen
9af2d1c21a Update license and readme file 2019-12-01 06:47:29 +01:00
Sefa Ilkimen
9dce2fb964 Update badges in readme 2019-12-01 06:28:21 +01:00
Sefa Ilkimen
eb946b49ab Add GitHub Actions CI workflow 2019-12-01 06:08:16 +01:00
Sefa Ilkimen
72ca81b515 Update changelog 2019-11-30 17:36:49 +01:00
Sefa Ilkimen
68633f1bb8 chore: update changelog and readme 2019-11-18 04:22:37 +01:00
Sefa Ilkimen
ee26b78e0d chore: update mocha timeout values 2019-11-18 03:09:32 +01:00
Sefa Ilkimen
d924f98844 chore: update slack notifications for travis builds 2019-11-18 02:59:22 +01:00
Sefa Ilkimen
8fceb4df97 chore: configure mocha threshold values 2019-11-18 02:28:52 +01:00
Sefa Ilkimen
7a09fa9460 feat: add ponyfills to support multipart requests on android webview versions < 50 and iOS versions < 13.2 2019-11-18 02:01:02 +01:00
Sefa Ilkimen
3e5c941fdd refactor: iOS implementation 2019-11-17 22:46:51 +01:00
Sefa Ilkimen
0f273f401b feat(ios): implement multipart requests
- expose new AFHTTPSessionManager method "uploadTaskWithHTTPMethod"
2019-11-17 21:29:38 +01:00
Sefa Ilkimen
684874184d - refactor: iOS implementation
- chore: add flags to run only one spec or disable spec in e2e tests
2019-11-17 19:33:19 +01:00
Sefa Ilkimen
19e1e7206b implement e2e tests for #101 2019-11-14 04:45:20 +01:00
Sefa Ilkimen
21bec76c11 refactor e2e test scripts 2019-11-14 02:06:33 +01:00
Sefa Ilkimen
594d03aa41 WIP: major progress for #101
- feat(www): implement preprocessor for FormData instances
- feat(www): implement API checks for multipart requests
- feat(android): implement multipart requests
- chore(specs): implement www specs for new prprocessor
2019-11-11 04:49:35 +01:00
Sefa Ilkimen
b3276ad2d4 update cordova-ios in test app template 2019-11-10 17:05:52 +01:00
Sefa Ilkimen
3b4a5b7c26 add missing module reference for "dependency-validator.js" 2019-11-10 06:55:38 +01:00
Sefa Ilkimen
867b8ea202 - WIP: implement data pre-processor for #101
- implement checks for #101
- add some specs
2019-11-10 06:40:31 +01:00
Sefa Ilkimen
cc3e70771c release v2.2.0 2019-09-29 23:39:40 +02:00
Sefa Ilkimen
9e892119cc feat(#127): adding multiple file upload
- implement multi uploading files for ios
- update readme
2019-09-29 21:43:58 +02:00
Sefa Ilkimen
f93f69e0aa feat(#127): adding multiple file upload
- remove www interface function "uploadFiles" as it confuses more than it helps
- implement multi uploading files for android
- add e2e spec
2019-09-29 20:37:36 +02:00
Sefa Ilkimen
4ace394464 feat(#127): adding multiple file upload
- fix missing option params
2019-09-27 13:25:14 +02:00
Sefa Ilkimen
ae3e821639 feat(#127): adding multiple file upload
- prepare www interface
- implement options validation
- implement specs for js interface
- improve error messages
2019-09-26 16:37:19 +02:00
Sefa Ilkimen
e17768041c fix: missing file reference in plugin.xml 2019-09-26 04:44:40 +02:00
Sefa Ilkimen
34a559bc6e feature #253: add support for response type "json" 2019-09-26 04:31:08 +02:00
Sefa Ilkimen
2ce1fc407d feature #239: add enumeration style object for error codes 2019-09-26 03:00:30 +02:00
Sefa Ilkimen
4b73c3762c Merge pull request #243 from joellimberg/patch-1
Readme typo fix: ”plugin.” → “plugin.http.”
2019-07-30 23:46:05 +02:00
Joel Limberg
5a9c176bca Readme typo fix: ”plugin.” → “plugin.http.” 2019-07-26 22:07:46 +03:00
Sefa Ilkimen
40aa944d0f fix #231: remove broken link 2019-07-23 23:02:34 +02:00
Sefa Ilkimen
c7feb7aca3 improve readme 2019-07-23 22:39:56 +02:00
Sefa Ilkimen
680105175b Update issue templates 2019-06-25 02:26:16 +02:00
Sefa Ilkimen
07811d0380 Update issue templates 2019-06-25 02:25:20 +02:00
Sefa Ilkimen
026e8589c2 Update issue templates 2019-06-24 17:49:08 +02:00
Sefa Ilkimen
e1592001af Update issue templates 2019-06-24 17:36:30 +02:00
Sefa Ilkimen
f057b126ba release v2.1.1 2019-06-14 17:01:30 +02:00
Sefa Ilkimen
2b567cdf32 Fix #224: responseType "arraybuffer" and "blob" not working on browser platform 2019-06-14 16:43:36 +02:00
Sefa Ilkimen
d99c7bb154 release v2.1.0 2019-06-14 02:57:16 +02:00
Sefa Ilkimen
336a56be1c update changelog 2019-06-14 02:56:59 +02:00
Sefa Ilkimen
8b962cc350 Merge pull request #205 from BeMyEye/master
add OKHTTP_VERSION plugin variable
2019-06-14 02:37:52 +02:00
Sefa Ilkimen
13bf4666b0 feature #171: support for responseType "blob" 2019-06-14 02:29:45 +02:00
Sefa Ilkimen
1fc3d6230c - update readme
- implement response type support for browser platform
2019-06-14 00:52:27 +02:00
Sefa Ilkimen
85346e0381 Merge branch 'binary_response'
# Conflicts:
#	test/e2e-specs.js
2019-06-13 16:44:35 +02:00
Sefa Ilkimen
c9f8c8b66a release v2.0.11 2019-06-13 16:12:50 +02:00
Sefa Ilkimen
9b26a0f031 fix #221: headers are not set on Android when request fails due to non-success status code 2019-06-13 16:00:26 +02:00
Sefa Ilkimen
298c031433 release v2.0.10 2019-06-13 00:20:01 +02:00
Sefa Ilkimen
eab6acf85c - update changelog
- increment version
2019-06-13 00:12:09 +02:00
Sefa Ilkimen
8cf0d21a7a add response type "arraybuffer" support for iOS 2019-06-12 23:43:40 +02:00
Sefa Ilkimen
f91727e14a add response type "arraybuffer" support for android 2019-06-12 23:01:15 +02:00
Sefa Ilkimen
7b485507dc - use appium 1.9.1 for saucelabs
- update readme
2019-06-03 13:49:51 +02:00
Sefa Ilkimen
03b0abb74e using updated appium version for saucelabs 2019-06-03 13:32:45 +02:00
Sefa Ilkimen
c3d60c37bf fix #218: headers are used as params on browser platform 2019-06-03 11:59:35 +02:00
Sefa Ilkimen
54ef8ae169 release v2.0.9 2019-04-15 18:35:51 +02:00
Sefa Ilkimen
c83940a8f6 deprecate "disableRedirect" in favor of "setFollowRedirect" 2019-04-15 18:09:34 +02:00
Sefa Ilkimen
ccabbf2a29 - add some specs
- prepare new "follow redirect" config interface
2019-04-15 04:23:03 +02:00
Sefa Ilkimen
7ba0852698 fix JS specs 2019-04-15 03:59:47 +02:00
Sefa Ilkimen
4f3ff9097f WIP: implementing X509 client cert authentication (android "buffer" mode) 2019-04-15 03:18:59 +02:00
Sefa Ilkimen
620ce3f81c Fix #204: broken support for cordova-android < 7.0 2019-04-15 02:47:14 +02:00
cvaliere
6e68bf4dfe Merge pull request #1 from BeMyEye/use-preference-OKHTTP_VERSION
use OKHTTP_VERSION for okhttp-urlconnection
2019-04-11 17:35:14 +02:00
Kevin Simon
87f0f3600c use OKHTTP_VERSION for okhttp-urlconnection 2019-04-11 17:24:27 +02:00
Sefa Ilkimen
0bf39650e1 release v2.0.8 2019-04-10 12:06:17 +02:00
Sefa Ilkimen
d444363a8e - fix #198: Cookie header is always passed even if there is no cookie
- some cleanup
2019-04-09 17:25:55 +02:00
Sefa Ilkimen
13976bbe43 Update change log and version number 2019-04-08 20:16:24 +02:00
Sefa Ilkimen
70c8b9bb32 Fix #201: browser implementation broken due to broken dependency 2019-04-08 20:12:36 +02:00
Sefa Ilkimen
4b964e8b7c Fix #197: iOS crashes on multiple HTTPS downloadFile() calls (reverting a8e3637) 2019-04-08 19:14:39 +02:00
Sefa Ilkimen
56f8b41f4f Fix #189: error code mappings for TLS handshake problems are not precise 2019-04-08 17:58:39 +02:00
Sefa Ilkimen
e1e720fe2d Add missing handler on browser platform 2019-04-08 17:31:38 +02:00
Sefa Ilkimen
a1b6ea94f0 Fix #200: remove string switch and use ugly if ... else 😢 to be compatible with Java target 6 2019-04-05 16:48:18 +02:00
Sefa Ilkimen
d977392a49 - some cleanup
- deprecate "setSSLCertMode" in favor of "setServerTrustMode"
2019-04-05 16:26:04 +02:00
Sefa Ilkimen
8d28f4ab80 WIP: implementing X509 client cert authentication 2019-04-05 05:22:34 +02:00
Sefa Ilkimen
38b3e6ffb1 release v2.0.7 2019-04-03 19:20:58 +02:00
Sefa Ilkimen
7d984bcf89 add spec for #197 2019-04-03 19:14:39 +02:00
Sefa Ilkimen
2cfac2026b - increment version
- update changelog
- improve query params spec
2019-04-03 18:58:19 +02:00
Sefa Ilkimen
8bdbf7ca78 fix query params handling for iOS 2019-04-03 14:28:12 +02:00
Sefa Ilkimen
d5ce3ed76b fix query param url encoding 2019-04-03 13:03:49 +02:00
Sefa Ilkimen
8a83a940cb WIP: support deeply structured query params (fix #48) 2019-04-02 12:51:20 +02:00
Sefa Ilkimen
230de36618 WIP:
- some refactoring for future features
- move params serializer into JS code
2019-04-02 12:04:49 +02:00
Sefa Ilkimen
0af726d8ce align iOS error codes with Android error codes 2019-03-28 15:37:56 +01:00
Sefa Ilkimen
ba57d3cba1 re-integrate OkHttp3 connection factory 2019-03-22 18:09:25 +01:00
Sefa Ilkimen
508b1b4f83 re-implement disabling verification of hostname 2019-03-22 05:03:24 +01:00
Sefa Ilkimen
7992bd0991 WIP: started re-implementing SSL cert modes 2019-03-22 04:06:50 +01:00
Sefa Ilkimen
a331b57ab2 WIP: fix disableRedirect() 2019-03-22 02:18:43 +01:00
Sefa Ilkimen
ee30160921 WIP: reduce duplicate code & cleanup 2019-03-22 02:06:39 +01:00
Sefa Ilkimen
e8e1c4273f WIP: re-implement Upload and Download Runnables 2019-03-21 17:45:24 +01:00
Sefa Ilkimen
752b2cdcb7 WIP: started refactoring
- one HTTP request class implementing Runnable for all request methods
 - broken SSL cert handling
2019-03-21 15:12:53 +01:00
Sefa Ilkimen
314314d7f9 release v2.0.6 2019-03-11 14:20:57 +01:00
Sefa Ilkimen
a8e3637f27 Merge branch 'chrisjdev-master' 2019-03-11 14:01:37 +01:00
Sefa Ilkimen
56272b9a5d Update changelog 2019-03-11 13:59:17 +01:00
Sefa Ilkimen
8f859db57f Merge branch 'master' of https://github.com/chrisjdev/cordova-plugin-advanced-http into chrisjdev-master 2019-03-11 11:34:10 +01:00
Sefa Ilkimen
e673754b13 Test for #184 2019-03-01 18:12:46 +01:00
Sefa Ilkimen
a0f376233c Fix #187: setSSLCertMode with "default" throws an error on Android 2019-03-01 18:12:06 +01:00
Sefa Ilkimen
fcd142a70b release v2.0.5 2019-02-25 13:08:25 +01:00
Sefa Ilkimen
807400bc63 Fix #185: need more detailed SSL error message 2019-02-22 19:20:12 +01:00
Sefa Ilkimen
1fd857f1d9 release v2.0.4 2019-01-17 17:30:43 +01:00
Sefa Ilkimen
f801d2a283 Update changelog 2019-01-17 17:26:24 +01:00
Sefa Ilkimen
6033ea4b76 Fix #179: Can't send empty string with utf8 serializer 2019-01-17 17:03:12 +01:00
Sefa Ilkimen
dadbf97d0c release v2.0.3 2019-01-15 17:00:54 +01:00
Sefa Ilkimen
f06788d199 Update cordova to fix peer dependency issues 2019-01-15 16:58:00 +01:00
Sefa Ilkimen
a195de409d Fix #172: plugin does not respect user installed CA certs on Android 2018-12-23 19:00:51 +01:00
Chris J
0fade8351d Merge pull request #1 from chrisjdev/keepSessionManager
Keep AFHTTPSessionManager instance with the plugin
2018-12-20 10:49:05 -05:00
Chris J
ebd6ae9793 Keep AFHTTPSessionManager instance with the plugin
Persist the AFHTTPSessionManager instance for the life of the plugin to allow reusing the underlying sockets, for example, with "Connection: keep-alive" headers.
2018-12-20 10:47:13 -05:00
Sefa Ilkimen
49f219723d Fix test scripts for linux bash 2018-12-12 19:46:58 +01:00
Sefa Ilkimen
f1bb4f36d0 Fix android test suite 2018-12-12 15:29:27 +01:00
Sefa Ilkimen
e2a869bbd2 Running android tests based on android image instead of xcode image (travis config) 2018-12-12 15:23:05 +01:00
Sefa Ilkimen
d2f79352ae release v2.0.2 2018-12-10 18:52:43 +01:00
Sefa Ilkimen
cca1be9d91 Update android SDK and build tools 2018-12-10 18:03:11 +01:00
Sefa Ilkimen
b6d8763a18 Update cordova-android version in test app 2018-12-10 15:57:47 +01:00
Sefa Ilkimen
c70ce750ed Using Xcode 9.3 image for tests because higher versions don't include JDK v1.8 and therefore crash android builds 2018-12-10 15:54:19 +01:00
Sefa Ilkimen
54abb2ba37 Fix #170: Can't build iOS Test App with Xcode 10.1 2018-12-10 15:17:36 +01:00
Sefa Ilkimen
8d433d050f update changelog 2018-12-06 18:47:55 +01:00
Sefa Ilkimen
bf616fc6e5 Merge pull request #164 from ath0mas/patch-1
Fix argument double encoding for HttpHead
2018-12-06 18:03:16 +01:00
Alexis THOMAS
c31765bbcd Fix argument double encoding for HttpHead
To follow #157 that fixed double encoding on file download
2018-11-26 10:37:21 +01:00
Sefa Ilkimen
b5ec508c30 Merge pull request #157 from TheZopo/double-arg-encoding-fix
Fix argument double encoding on file download
2018-11-25 23:06:04 +01:00
Bastien Marsaud
6fcf4dc8eb Fix argument double encoding on file download
Disable argument encoding in get request
2018-10-18 12:33:05 +02:00
Sefa Ilkimen
f0e1522982 Fix #142: Plugin affected by REDoS Issue of tough-cookie 2018-09-04 20:27:45 +02:00
Sefa Ilkimen
c387e52ea3 release v2.0.1 2018-09-04 02:20:15 +02:00
Sefa Ilkimen
f87afa2217 - fix #136: Content-Type header not overwritable on browser platform
- remove obsolete methods "enableSSLPinning" and "acceptAllCerts"
2018-09-04 01:56:17 +02:00
Sefa Ilkimen
516aa6b61d release v2.0.0 2018-09-03 16:03:46 +02:00
Sefa Ilkimen
a6bf9041a5 add missing "updatecert" task to travis config 2018-09-03 15:25:11 +02:00
Sefa Ilkimen
c41fc11888 SSL certs must be places in "www/certificates"; project root folder is not scanned for certs anymore 2018-09-03 15:02:26 +02:00
Sefa Ilkimen
91515d30bd add test for gzipped content 2018-09-03 14:18:22 +02:00
Sefa Ilkimen
c8638ff204 exclude test certs from repository 2018-09-03 14:17:22 +02:00
Sefa Ilkimen
0acf2e2574 fix test #34 "should pin SSL cert correctly (GET)" 2018-09-03 13:52:09 +02:00
Sefa Ilkimen
f4674028b5 Merge pull request #118 from 0verrfl0w/ios-networking-indicator
Ios networking indicator
2018-07-23 15:18:45 +02:00
Florian Leitmann
5919d90698 Merge remote-tracking branch 'upstream/master' into ios-networking-indicator 2018-05-31 17:29:12 +02:00
Sefa Ilkimen
27e959800a remove deprecated AngularJS integration service 2018-05-27 20:33:02 +02:00
Sefa Ilkimen
96f45d7274 feature #103: implement HTTP SSL cert modes 2018-05-27 19:30:42 +02:00
Florian Leitmann
3dbd07c9df Merge branch 'master' into ios-networking-indicator 2018-05-27 14:01:04 +02:00
Florian Leitmann
cb597c0d30 Added SDNetworkActivityIndicator library and integrated it in plugin.xml definition as well as CordovaHttpPlugin.m
This library allows to show the network activity indicator on iOS devices, to indicate native networking
It uses a counter to hide the indicator depending on whether all request have been fulfilled or not
2018-05-27 12:45:52 +02:00
Sefa Ilkimen
60189a68b3 update httpbin.org SSL cert 2018-05-25 18:58:52 +02:00
Sefa Ilkimen
8e4bfdbc70 remove explicit host name verification setting on Android 2018-05-25 18:45:10 +02:00
Sefa Ilkimen
2ae4c7cf39 refactor cert handling for Android (preparing for v2 changes) 2018-04-11 03:24:27 +02:00
Sefa Ilkimen
32fdf49d31 fix misleading examples in README 2018-04-08 23:59:11 +02:00
Sefa Ilkimen
db0f233737 update README (missing url property in callback signature) 2018-03-21 22:35:31 +01:00
Sefa Ilkimen
2263950de9 release v1.11.1 2018-03-21 13:18:57 +01:00
Sefa Ilkimen
2a9d3296cc update httpbin.org SSL cert 2018-03-21 12:56:11 +01:00
Sefa Ilkimen
55dd751cec fix #92: headers not deserialized on platform "browser" 2018-03-16 03:04:03 +01:00
Sefa Ilkimen
af239d3194 release v1.11.0 2018-03-02 01:09:23 +01:00
Sefa Ilkimen
291e8fe547 browser: fix content type header 2018-03-02 01:00:22 +01:00
Sefa Ilkimen
8b3466e3c3 - update readme
- update version number
2018-03-02 00:46:01 +01:00
Sefa Ilkimen
73e76f6333 feature #11: implement support for "browser" platform 2018-03-01 23:45:54 +01:00
Sefa Ilkimen
3fcac64597 update OkHttp dependency 2018-03-01 16:16:02 +01:00
Sefa Ilkimen
4a437f9435 fix docu for "disableRedirect" 2018-02-28 04:08:32 +01:00
Sefa Ilkimen
2d15b86cb5 add documentation for feature #77 2018-02-28 04:05:13 +01:00
Sefa Ilkimen
63d859ad54 - implement all HTTP operations as shorthand functions of "sendRequest"
- add some more type checking
2018-02-28 03:24:27 +01:00
Sefa Ilkimen
6b9ed72b9f WIP: new API which allows overriding global options 2018-02-28 01:31:35 +01:00
116 changed files with 21692 additions and 4838 deletions

1
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1 @@
github: silkimen

33
.github/ISSUE_TEMPLATE/--bug-report.md vendored Normal file
View File

@@ -0,0 +1,33 @@
---
name: "\U0001F41BBug report"
about: Create a report to help us improve
title: "[Bug] [platform] your issue title"
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is and what you expected to happen.
**System info**
- affected HTTP plugin version: [e.g. 2.1.1]
- affected platform(s) and version(s): [e.g. iOS 12.2]
- affected device(s): [e.g. iPhone 8]
- cordova version: [e.g. 6.5.0]
- cordova platform version(s): [e.g. android 7.0.0, browser 5.0.3]
**Are you using ionic-native-wrapper?**
- ionic-native-wrapper version: [e.g. 5.8.0]
- did you check [ionic-native issue tracker](https://github.com/ionic-team/ionic-native/issues) for your problem?
**Minimum viable code to reproduce**
If applicable, add formatted sample coding to help explain your problem.
e.g.:
```js
cordova.plugin.http.setDataSerializer('urlencoded');
```
**Screenshots**
If applicable, add screenshots to help explain your problem.

View File

@@ -0,0 +1,21 @@
---
name: "\U0001F680Feature request"
about: Suggest an idea for this project
title: "[Feature]"
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. e.g. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen. If applicable, add formatted sample coding to help explain your idea.
```js
// do some fancy stuff here
```
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.

View File

@@ -0,0 +1,18 @@
---
name: "\U0001F914Support question"
about: Ask the community
title: ''
labels: question
assignees: ''
---
**You've got questions?**
We primarily use GitHub as an issue tracker; for usage and support questions, please check out these resources below. Thanks! 😁
* README.md: https://github.com/silkimen/cordova-plugin-advanced-http/blob/master/README.md
* StackOverflow: https://stackoverflow.com/questions/tagged/cordova-plugin-advanced-http using the tag `cordova-plugin-advanced-http`
* Wiki: https://github.com/silkimen/cordova-plugin-advanced-http/wiki
And don't forget: If you get help, help others. Good karma rulez!

65
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,65 @@
name: Cordova HTTP Plugin CI
on: [push]
env:
nodejs: '10.x'
jobs:
test-www-interface:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Install Node.js ${{ env.nodejs }}
uses: actions/setup-node@v1
with:
node-version: ${{ env.nodejs }}
- name: Install node modules
run: npm ci
- name: Run WWW interface tests
run: npm run testjs
build-ios:
runs-on: macOS-latest
steps:
- uses: actions/checkout@v1
- name: Install Node.js ${{ env.nodejs }}
uses: actions/setup-node@v1
with:
node-version: ${{ env.nodejs }}
- name: Install node modules
run: npm ci
- name: Update test cert for httpbin.org
run: npm run updatecert
- name: Build test app
run: scripts/build-test-app.sh --ios --emulator
build-android:
runs-on: ubuntu-latest
env:
BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }}
BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }}
steps:
- uses: actions/checkout@v1
- name: Install Node.js ${{ env.nodejs }}
uses: actions/setup-node@v1
with:
node-version: ${{ env.nodejs }}
- name: Install node modules
run: npm ci
- name: Install JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Update test cert for httpbin.org
run: npm run updatecert
- name: Add workaround for missing DX files in build-tools 31 (https://stackoverflow.com/a/68430992)
run: ln -s $ANDROID_HOME/build-tools/31.0.0/d8 $ANDROID_HOME/build-tools/31.0.0/dx && ln -s $ANDROID_HOME/build-tools/31.0.0/lib/d8.jar $ANDROID_HOME/build-tools/31.0.0/lib/dx.jar
- name: Build test app
run: scripts/build-test-app.sh --android --device
- name: Upload artifact to BrowserStack
if: env.BROWSERSTACK_USERNAME != ''
run: scripts/upload-browserstack.sh --android
- name: Run e2e tests
if: env.BROWSERSTACK_USERNAME != ''
run: scripts/test-app.sh --android --device

62
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@@ -0,0 +1,62 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
name: "CodeQL"
on:
push:
branches: [master]
pull_request:
# The branches below must be a subset of the branches above
branches: [master]
schedule:
- cron: '0 20 * * 3'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# Override automatic language detection by changing the below list
# Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
language: ['javascript']
# Learn more...
# https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
steps:
- name: Checkout repository
uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

4
.gitignore vendored
View File

@@ -1,7 +1,11 @@
node_modules/**
test/e2e-app-template/www/certificates/*.cer
test/e2e-app-template/www/certificates/*.pkcs
tags
.zedstate
npm-debug.log
/temp
/android-sdk-macosx.zip
/android-sdk-macosx/**
.idea/
*.iml

View File

@@ -1,13 +1,3 @@
sudo: false
language: objective-c
os: osx
osx_image: xcode9.1
env:
- TARGET_PLATFORM=android
- TARGET_PLATFORM=ios
cache:
directories:
- node_modules
@@ -15,29 +5,54 @@ cache:
addons:
sauce_connect: true
before_install:
- export LANG=en_US.UTF-8
matrix:
include:
- name: "iOS Build & Test"
language: objective-c
sudo: false
os: osx
osx_image: xcode12.5
install:
- npm install
- if [ $TARGET_PLATFORM = "android" ]; then
brew update &&
brew install gradle &&
scripts/setup-android-sdk.sh &&
export ANDROID_HOME=$(pwd)/android-sdk-macosx &&
export PATH=${PATH}:$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools:$ANDROID_HOME/build-tools/23.0.2;
fi
before_install:
- export LANG=en_US.UTF-8 &&
nvm use 14
script:
- npm run testjs
- travis_wait scripts/build-test-app.sh --$TARGET_PLATFORM --emulator &&
scripts/upload-artifact.sh --$TARGET_PLATFORM &&
scripts/test-app.sh --$TARGET_PLATFORM --emulator;
install:
- npm install
after_success:
script:
- npm run testjs &&
npm run updatecert &&
scripts/build-test-app.sh --ios --emulator &&
scripts/upload-saucelabs.sh --ios &&
scripts/test-app.sh --ios --emulator;
deploy:
- name: "Android Build & Test"
language: android
sudo : required
notifications:
slack:
secure: lXE+2AgsxZU5G5dI91LkMAIgo8MAWfdM7DB5UOtn5LpuNln+2FmJo1gOI7tkdmLOqpXTGYnpI2VyQN3H4nOF21YhuouzD1Sh8n2wtQg1iTm353kuQpqiVhSBX8ZJ7Be1e1G8OsnxoYOxbs4Zo9qI40EruwkvqLCBHWM5MRGyd4M7EFWwb9Z29VZN0y1Nt5g/c3bT76kdKmF+JCLur2OeEKxAity7sIKgZekSqeIMwEVLSxXnda6Dbjc/cg0MJ0iDArkD7iu6fz/Fcrrxgm/pUxjcgvqze7Gy5i31mjEfspnrglWV1cshMd48BTDKCJ2AMmxH8O3GPSWE2txjIvGRWUve7iViNylvmQCVz3Eyf99+4EuuVGa+5PSodQ/CqODx/65EwtcN3PE1tNz2puKOK8nrOJcFkcbG8KTHKUlQtHCkjitbykUnj/hvhLK5/oWlQYVOLWWrHwdGUh8FI8aFPVGjRjWbHbhdayjEIqxwr1ns+6mYrP1EFNXbaeZxnLNC59XpJl1ifuezqYAk7YEiU5j4rtC7YKgyQ3ueb7anOHTJoTMyDn8mpZXgwuyhoBaeEYytQVgRyMtL6Y5cP98Jn2kv0+vdne3rkk9/JEBTo32HOjvoij6rsqEvXC0LhUDJSNadOVdHht0jjoN6zBH37HIE5/3zysLlPcAcHAS83ow=
android:
components:
- tools
- platform-tools
- build-tools-28.0.3
- android-28
- extra-android-support
- extra-android-m2repository
- extra-google-m2repository
before_install:
- export LANG=en_US.UTF-8 &&
curl -sL https://deb.nodesource.com/setup_14.x | sudo -E bash - &&
sudo apt-get install -y nodejs
- yes | sdkmanager --update
install:
- npm install
script:
- npm run testjs &&
npm run updatecert &&
scripts/build-test-app.sh --android --emulator &&
scripts/upload-saucelabs.sh --android &&
scripts/test-app.sh --android --emulator;

3
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"editor.tabSize": 2
}

View File

@@ -1,5 +1,157 @@
# Changelog
## 3.2.0
- Feature #420: implement blacklist feature to disable SSL/TLS versions on Android (thanks to @MobisysGmbH)
## 3.1.1
- Fixed #372: malformed empty multipart request on Android
- Fixed #399: memory leakage leads to app crashes on iOS (thanks avargaskun)
## 3.1.0
- Feature #272: add support for aborting requests (thanks russaa)
## 3.0.1
- Fixed #359: memory leakage leads to app crashes on Android
- Fixed #355: responseType "json" not working with valid JSON response on browser (thanks millerg6711)
## 3.0.0
- Feature #158: support removing headers which were previously set via "setHeader"
- Fixed #345: empty file names are not handled correctly (thanks ikosta)
- :warning: **Breaking Change**: Dropped support for Android < 5.1
- :warning: **Breaking Change**: Removed "disableRedirect", use "setFollowRedirect" instead
- :warning: **Breaking Change**: Removed "setSSLCertMode", use "setServerTrustMode" instead
## 2.5.1
- Fixed #334: empty JSON response triggers error even though request is successful (thanks antikalk)
- Fixed #248: clearCookies() does not work on iOS
## 2.5.0
- Feature #56: add support for X.509 client certificate based authentication
## 2.4.1
- Fixed #296: multipart requests are not serialized on browser platform
- Fixed #301: data is not decoded correctly when responseType is "json" (thanks antikalk)
- Fixed #300: FormData object containing null or undefined value is not serialized correctly
## 2.4.0
- Feature #291: add support for sending 'raw' requests (thanks to jachstet-sea and chuchuva)
- Feature #155: add OPTIONS method
- Feature #283: improve error message on timeout on browser platform
## 2.3.1
- Fixed #275: getAllCookies() is broken because of a typo (thanks ath0mas)
## 2.3.0
- Feature #101: Support "multipart/form-data" requests (thanks SDA SE Open Industry Solutions)
#### Important information
This feature depends on several Web APIs. See https://github.com/silkimen/cordova-plugin-advanced-http/wiki/Web-APIs-required-for-Multipart-requests for more info.
## 2.2.0
- Feature #239: add enumeration style object for error codes
- Feature #253: add support for response type "json"
- Feature #127: add multiple file upload (thanks SDA SE Open Industry Solutions and nilswitschel)
## 2.1.1
- Fixed #224: response type "arraybuffer" and "blob" not working on browser platform
## 2.1.0
- Feature #216: Support for response type `arraybuffer`
- Feature #171: Support for response type `blob`
- Feature #205: Add preference for configuring OKHTTP version (thanks RougeCiel)
## 2.0.11
- Fixed #221: headers not set on Android when request fails due to non-success status code
## 2.0.10
- Fixed #218: headers are used as params on browser platform
## 2.0.9
- Fixed #204: broken support for cordova-android < 7.0
- :warning: **Deprecation**: Deprecated "disableRedirect" in favor of "setFollowRedirect"
## 2.0.8
- Fixed #198: cookie header is always passed even if there is no cookie
- Fixed #201: browser implementation is broken due to broken dependency
- Fixed #197: iOS crashes when multiple request are done simultaneously (reverted a8e3637)
- Fixed #189: error code mappings are not precise
- Fixed #200: compatibility with Java 6 is broken due to string switch on Android
- :warning: **Deprecation**: Deprecated "setSSLCertMode" in favor of "setServerTrustMode"
## 2.0.7
- Fixed #195: URLs are double-encoded on Android
## 2.0.6
- Fixed #187: setSSLCertMode with "default" throws an error on Android
- Fixed #115: HTTP connections are not kept alive on iOS (thanks MorpheusDe97)
## 2.0.5
- Fixed #185: need more detailed SSL error message
## 2.0.4
- Fixed #179: sending empty string with utf8 serializer throws an exception
## 2.0.3
- Fixed #172: plugin does not respect user installed CA certs on Android
#### Important information
We've changed a default behavior on Android. User installed CA certs are respected now.
If you don't want this for your needs, you can switch back to old behavior by setting SSL cert mode to `legacy`.
## 2.0.2
- Fixed #142: Plugin affected by REDoS Issue of tough-cookie
- Fixed #157: Arguments are double URL-encoded on "downloadFile" (thanks TheZopo)
- Fixed #164: Arguments are double URL-encoded on "head" (thanks ath0mas)
## 2.0.1
- Fixed #136: Content-Type header non-overwritable on browser platform
## 2.0.0
- Feature #103: implement HTTP SSL cert modes
- :warning: **Breaking Change**: Removed AngularJS (v1) integration service
- :warning: **Breaking Change**: Removed "enableSSLPinning" and "acceptAllCerts", use "setSSLCertMode" instead
- :warning: **Breaking Change**: Certificates must be placed in "www/certificates" folder
## 1.11.1
- Fixed #92: headers not deserialized on platform "browser"
## 1.11.0
- Feature #77: allow overriding global settings for each single request
- Feature #11: add support for "browser" platform
## 1.10.2
- Fixed #78: overriding header "Content-Type" not working on Android

View File

@@ -1,5 +1,6 @@
The MIT License (MIT)
Copyright (c) 2019 Sefa Ilkimen
Copyright (c) 2017 Mobisys GmbH
Copyright (c) 2014 Wymsee, Inc

327
README.md
View File

@@ -1,19 +1,23 @@
Cordova Advanced HTTP
=====================
[![npm version](https://badge.fury.io/js/cordova-plugin-advanced-http.svg)](https://badge.fury.io/js/cordova-plugin-advanced-http)
[![npm version](https://img.shields.io/npm/v/cordova-plugin-advanced-http)](https://www.npmjs.com/package/cordova-plugin-advanced-http?activeTab=versions)
[![MIT Licence](https://img.shields.io/badge/license-MIT-blue?style=flat)](https://opensource.org/licenses/mit-license.php)
[![downloads/month](https://img.shields.io/npm/dm/cordova-plugin-advanced-http.svg)](https://www.npmjs.com/package/cordova-plugin-advanced-http)
[![MIT Licence](https://badges.frapsoft.com/os/mit/mit.png)](https://opensource.org/licenses/mit-license.php)
[![Build Status](https://travis-ci.org/silkimen/cordova-plugin-advanced-http.svg?branch=master)](https://travis-ci.org/silkimen/cordova-plugin-advanced-http)
[![Travis Build Status](https://img.shields.io/travis/com/silkimen/cordova-plugin-advanced-http?label=Travis%20CI)](https://travis-ci.com/silkimen/cordova-plugin-advanced-http)
[![GitHub Build Status](https://img.shields.io/github/workflow/status/silkimen/cordova-plugin-advanced-http/Cordova%20HTTP%20Plugin%20CI/master?label=GitHub%20Actions)](https://github.com/silkimen/cordova-plugin-advanced-http/actions)
Cordova / Phonegap plugin for communicating with HTTP servers. Supports iOS and Android.
Cordova / Phonegap plugin for communicating with HTTP servers. Supports iOS, Android and [Browser](#browserSupport).
This is a fork of [Wymsee's Cordova-HTTP plugin](https://github.com/wymsee/cordova-HTTP).
## Advantages over Javascript requests
- Background threading - all requests are done in a background thread.
- Handling of HTTP code 401 - read more at [Issue CB-2415](https://issues.apache.org/jira/browse/CB-2415).
- SSL Pinning - read more at [LumberBlog](http://blog.lumberlabs.com/2012/04/why-app-developers-should-care-about.html).
- SSL / TLS Pinning
- CORS restrictions do not apply
- X.509 client certificate based authentication
- Handling of HTTP code 401 - read more at [Issue CB-2415](https://issues.apache.org/jira/browse/CB-2415)
## Updates
@@ -30,6 +34,15 @@ phonegap plugin add cordova-plugin-advanced-http
cordova plugin add cordova-plugin-advanced-http
```
### Plugin Preferences
`AndroidBlacklistSecureSocketProtocols`: define a blacklist of secure socket protocols for Android. This preference allows you to disable protocols which are considered unsafe. You need to provide a comma-separated list of protocols ([check Android SSLSocket#protocols docu for protocol names](https://developer.android.com/reference/javax/net/ssl/SSLSocket#protocols)).
e.g. blacklist `SSLv3` and `TLSv1`:
```xml
<preference name="AndroidBlacklistSecureSocketProtocols" value="SSLv3,TLSv1" />
```
## Usage
### Plain Cordova
@@ -40,19 +53,6 @@ This plugin registers a global object located at `cordova.plugin.http`.
Check the [Ionic docs](https://ionicframework.com/docs/native/http/) for how to use this plugin with Ionic-native.
### With AngularJS (Deprecated)
:warning: *This feature is deprecated and will be removed anytime soon.* :warning:
This plugin creates a cordovaHTTP service inside of a cordovaHTTP module. You must load the module when you create your app's module.
```js
var app = angular.module('myApp', ['ngRoute', 'ngAnimate', 'cordovaHTTP']);
```
You can then inject the cordovaHTTP service into your controllers. The functions can then be used identically to the examples shown below except that instead of accepting success and failure callback functions, each function returns a promise. For more information on promises in AngularJS read the [AngularJS docs](http://docs.angularjs.org/api/ng/service/$q). For more info on promises in general check out this article on [html5rocks](http://www.html5rocks.com/en/tutorials/es6/promises/). Make sure that you load cordova.js or phonegap.js after AngularJS is loaded.
## Synchronous Functions
### getBasicAuthHeader
@@ -70,7 +70,7 @@ cordova.plugin.http.useBasicAuth('user', 'password');
```
### setHeader<a name="setHeader"></a>
Set a header for all future requests to a specified host. Takes a hostname, a header and a value (must be a string value).
Set a header for all future requests to a specified host. Takes a hostname, a header and a value (must be a string value or null).
```js
cordova.plugin.http.setHeader('Hostname', 'Header', 'Value');
@@ -93,14 +93,7 @@ cordova.plugin.http.setHeader('www.example.com', 'Header', 'Value');
cordova.plugin.http.setHeader('www.example.com:8080', 'Header', 'Value');
```
### disableRedirect
If set to `true`, it won't follow redirects automatically. This is a global setting.
```js
cordova.plugin.http.disableRedirect(true);
```
### setDataSerializer
### setDataSerializer<a name="setDataSerializer"></a>
Set the data serializer which will be used for all future PATCH, POST and PUT requests. Takes a string representing the name of the serializer.
```js
@@ -108,21 +101,42 @@ cordova.plugin.http.setDataSerializer('urlencoded');
```
You can choose one of these:
* `urlencoded`: send data as url encoded content in body (content type "application/x-www-form-urlencoded")
* `json`: send data as JSON encoded content in body (content type "application/json")
* `utf8`: send data as plain UTF8 encoded string in body (content type "plain/text")
* `urlencoded`: send data as url encoded content in body
* default content type "application/x-www-form-urlencoded"
* data must be an dictionary style `Object`
* `json`: send data as JSON encoded content in body
* default content type "application/json"
* data must be an `Array` or an dictionary style `Object`
* `utf8`: send data as plain UTF8 encoded string in body
* default content type "plain/text"
* data must be a `String`
* `multipart`: send FormData objects as multipart content in body
* default content type "multipart/form-data"
* data must be an `FormData` instance
* `raw`: send data as is, without any processing
* default content type "application/octet-stream"
* data must be an `Uint8Array` or an `ArrayBuffer`
You can also override the default content type headers by specifying your own headers (see [setHeader](#setHeader)).
This defaults to `urlencoded`. You can also override the default content type headers by specifying your own headers (see [setHeader](#setHeader)).
__Caution__: `urlencoded` does not support serializing deep structures whereas `json` does.
:warning: `urlencoded` does not support serializing deep structures whereas `json` does.
:warning: `multipart` depends on several Web API standards which need to be supported in your web view. Check out https://github.com/silkimen/cordova-plugin-advanced-http/wiki/Web-APIs-required-for-Multipart-requests for more info.
### setRequestTimeout
Set how long to wait for a request to respond, in seconds.
Set the "read" timeout in seconds. This is the timeout interval to use when waiting for additional data.
```js
cordova.plugin.http.setRequestTimeout(5.0);
```
### setFollowRedirect<a name="setFollowRedirect"></a>
Configure if it should follow redirects automatically. This defaults to true.
```js
cordova.plugin.http.setFollowRedirect(true);
```
### getCookieString
Returns saved cookies (as string) matching given URL.
@@ -147,34 +161,63 @@ cordova.plugin.http.clearCookies();
## Asynchronous Functions
These functions all take success and error callbacks as their last 2 arguments.
### enableSSLPinning
Enable or disable SSL pinning. This defaults to false.
### setServerTrustMode<a name="setServerTrustMode"></a>
Set server trust mode, being one of the following values:
To use SSL pinning you must include at least one .cer SSL certificate in your app project. You can pin to your server certificate or to one of the issuing CA certificates. For ios include your certificate in the root level of your bundle (just add the .cer file to your project/target at the root level). For android include your certificate in your project's platforms/android/assets folder. In both cases all .cer files found will be loaded automatically. If you only have a .pem certificate see this [stackoverflow answer](http://stackoverflow.com/a/16583429/3182729). You want to convert it to a DER encoded certificate with a .cer extension.
* `default`: default SSL trustship and hostname verification handling using system's CA certs
* `legacy`: use legacy default behavior (< 2.0.3), excluding user installed CA certs (only for Android)
* `nocheck`: disable SSL certificate checking and hostname verification, trusting all certs (meant to be used only for testing purposes)
* `pinned`: trust only provided certificates
As an alternative, you can store your .cer files in the www/certificates folder.
To use SSL pinning you must include at least one `.cer` SSL certificate in your app project. You can pin to your server certificate or to one of the issuing CA certificates. Include your certificate in the `www/certificates` folder. All `.cer` files found there will be loaded automatically.
:warning: Your certificate must be DER encoded! If you only have a PEM encoded certificate read this [stackoverflow answer](http://stackoverflow.com/a/16583429/3182729). You want to convert it to a DER encoded certificate with a .cer extension.
```js
cordova.plugin.http.enableSSLPinning(true, function() {
// enable SSL pinning
cordova.plugin.http.setServerTrustMode('pinned', function() {
console.log('success!');
}, function() {
console.log('error :(');
});
// use system's default CA certs
cordova.plugin.http.setServerTrustMode('default', function() {
console.log('success!');
}, function() {
console.log('error :(');
});
// disable SSL cert checking, only meant for testing purposes, do NOT use in production!
cordova.plugin.http.setServerTrustMode('nocheck', function() {
console.log('success!');
}, function() {
console.log('error :(');
});
```
### acceptAllCerts
Accept all SSL certificates. Or disable accepting all certificates. This defaults to false.
### setClientAuthMode<a name="setClientAuthMode"></a>
Configure X.509 client certificate authentication. Takes mode and options. `mode` being one of following values:
* `none`: disable client certificate authentication
* `systemstore` (only on Android): use client certificate installed in the Android system store; user will be presented with a list of all installed certificates
* `buffer`: use given client certificate; you will need to provide an options object:
* `rawPkcs`: ArrayBuffer containing raw PKCS12 container with client certificate and private key
* `pkcsPassword`: password of the PKCS container
```js
cordova.plugin.http.acceptAllCerts(true, function() {
console.log('success!');
}, function() {
console.log('error :(');
});
```
// enable client auth using PKCS12 container given in ArrayBuffer `myPkcs12ArrayBuffer`
cordova.plugin.http.setClientAuthMode('buffer', {
rawPkcs: myPkcs12ArrayBuffer,
pkcsPassword: 'mySecretPassword'
}, success, fail);
### validateDomainName
This function was removed in v1.6.2. Domain name validation is disabled automatically when you enable "acceptAllCerts".
// enable client auth using certificate in system store (only on Android)
cordova.plugin.http.setClientAuthMode('systemstore', {}, success, fail);
// disable client auth
cordova.plugin.http.setClientAuthMode('none', {}, success, fail);
```
### removeCookies
Remove all cookies associated with a given URL.
@@ -183,11 +226,67 @@ Remove all cookies associated with a given URL.
cordova.plugin.http.removeCookies(url, callback);
```
### sendRequest<a name="sendRequest"></a>
Execute a HTTP request. Takes a URL and an options object. This is the internally used implementation of the following shorthand functions ([post](#post), [get](#get), [put](#put), [patch](#patch), [delete](#delete), [head](#head), [uploadFile](#uploadFile) and [downloadFile](#downloadFile)). You can use this function, if you want to override global settings for each single request. Check the documentation of the respective shorthand function for details on what is returned on success and failure.
:warning: You need to encode the base URL yourself if it contains special characters like whitespaces. You can use `encodeURI()` for this purpose.
The options object contains following keys:
* `method`: HTTP method to be used, defaults to `get`, needs to be one of the following values:
* `get`, `post`, `put`, `patch`, `head`, `delete`, `options`, `upload`, `download`
* `data`: payload to be send to the server (only applicable on `post`, `put` or `patch` methods)
* `params`: query params to be appended to the URL (only applicable on `get`, `head`, `delete`, `upload` or `download` methods)
* `serializer`: data serializer to be used (only applicable on `post`, `put` or `patch` methods), defaults to global serializer value, see [setDataSerializer](#setDataSerializer) for supported values
* `responseType`: expected response type, defaults to `text`, needs to be one of the following values:
* `text`: data is returned as decoded string, use this for all kinds of string responses (e.g. XML, HTML, plain text, etc.)
* `json` data is treated as JSON and returned as parsed object, returns `undefined` when response body is empty
* `arraybuffer`: data is returned as [ArrayBuffer instance](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer), returns `null` when response body is empty
* `blob`: data is returned as [Blob instance](https://developer.mozilla.org/en-US/docs/Web/API/Blob), returns `null` when response body is empty
* `timeout`: timeout value for the request in seconds, defaults to global timeout value
* `followRedirect`: enable or disable automatically following redirects
* `headers`: headers object (key value pair), will be merged with global values
* `filePath`: file path(s) to be used during upload and download see [uploadFile](#uploadFile) and [downloadFile](#downloadFile) for detailed information
* `name`: name(s) to be used during upload see [uploadFile](#uploadFile) for detailed information
Here's a quick example:
```js
const options = {
method: 'post',
data: { id: 12, message: 'test' },
headers: { Authorization: 'OAuth2: token' }
};
cordova.plugin.http.sendRequest('https://google.com/', options, function(response) {
// prints 200
console.log(response.status);
}, function(response) {
// prints 403
console.log(response.status);
//prints Permission denied
console.log(response.error);
});
```
### post<a name="post"></a>
Execute a POST request. Takes a URL, data, and headers.
```js
cordova.plugin.http.post('https://google.com/', {
test: 'testString'
}, {
Authorization: 'OAuth2: token'
}, function(response) {
console.log(response.status);
}, function(response) {
console.error(response.error);
});
```
#### success
The success function receives a response object with 3 properties: status, data, and headers. **status** is the HTTP response code as numeric value. **data** is the response from the server as a string. **headers** is an object with the headers. The keys of the returned object are the header names and the values are the respective header values. All header names are lowercase.
The success function receives a response object with 4 properties: status, data, url, and headers. **status** is the HTTP response code as numeric value. **data** is the response from the server as a string. **url** is the final URL obtained after any redirects as a string. **headers** is an object with the headers. The keys of the returned object are the header names and the values are the respective header values. All header names are lowercase.
Here's a quick example:
@@ -195,6 +294,7 @@ Here's a quick example:
{
status: 200,
data: '{"id": 12, "message": "test"}',
url: 'http://example.net/rest'
headers: {
'content-length': '247'
}
@@ -227,7 +327,7 @@ cordova.plugin.http.post('https://google.com/', {
```
#### failure
The error function receives a response object with 3 properties: status, error and headers. **status** is the HTTP response code as numeric value. **error** is the error response from the server as a string. **headers** is an object with the headers. The keys of the returned object are the header names and the values are the respective header values. All header names are lowercase.
The error function receives a response object with 4 properties: status, error, url, and headers (url and headers being optional). **status** is a HTTP response code or an internal error code. Positive values are HTTP status codes whereas negative values do represent internal error codes. **error** is the error response from the server as a string or an internal error message. **url** is the final URL obtained after any redirects as a string. **headers** is an object with the headers. The keys of the returned object are the header names and the values are the respective header values. All header names are lowercase.
Here's a quick example:
@@ -235,18 +335,21 @@ Here's a quick example:
{
status: 403,
error: 'Permission denied',
url: 'http://example.net/noperm'
headers: {
'content-length': '247'
}
}
```
### get
:warning: An enumeration style object is exposed as `cordova.plugin.http.ErrorCode`. You can use it to check against internal error codes.
### get<a name="get"></a>
Execute a GET request. Takes a URL, parameters, and headers. See the [post](#post) documentation for details on what is returned on success and failure.
```js
cordova.plugin.http.get('https://google.com/', {
id: 12,
id: '12',
message: 'test'
}, { Authorization: 'OAuth2: token' }, function(response) {
console.log(response.status);
@@ -255,38 +358,49 @@ cordova.plugin.http.get('https://google.com/', {
});
```
### put
### put<a name="put"></a>
Execute a PUT request. Takes a URL, data, and headers. See the [post](#post) documentation for details on what is returned on success and failure.
### patch
### patch<a name="patch"></a>
Execute a PATCH request. Takes a URL, data, and headers. See the [post](#post) documentation for details on what is returned on success and failure.
### delete
### delete<a name="delete"></a>
Execute a DELETE request. Takes a URL, parameters, and headers. See the [post](#post) documentation for details on what is returned on success and failure.
### head
### head<a name="head"></a>
Execute a HEAD request. Takes a URL, parameters, and headers. See the [post](#post) documentation for details on what is returned on success and failure.
### uploadFile
Uploads a file saved on the device. Takes a URL, parameters, headers, filePath, and the name of the parameter to pass the file along as. See the [post](#post) documentation for details on what is returned on success and failure.
### options<a name="options"></a>
Execute a OPTIONS request. Takes a URL, parameters, and headers. See the [post](#post) documentation for details on what is returned on success and failure.
### uploadFile<a name="uploadFile"></a>
Uploads one or more file(s) saved on the device. Takes a URL, parameters, headers, filePath(s), and the name(s) of the parameter to pass the file along as. See the [post](#post) documentation for details on what is returned on success and failure.
```js
// e.g. for single file
const filePath = 'file:///somepicture.jpg';
const name = 'picture';
// e.g. for multiple files
const filePath = ['file:///somepicture.jpg', 'file:///somedocument.doc'];
const name = ['picture', 'document'];
cordova.plugin.http.uploadFile("https://google.com/", {
id: 12,
id: '12',
message: 'test'
}, { Authorization: 'OAuth2: token' }, 'file:///somepicture.jpg', 'picture', function(response) {
}, { Authorization: 'OAuth2: token' }, filePath, name, function(response) {
console.log(response.status);
}, function(response) {
console.error(response.error);
});
```
### downloadFile
### downloadFile<a name="downloadFile"></a>
Downloads a file and saves it to the device. Takes a URL, parameters, headers, and a filePath. See [post](#post) documentation for details on what is returned on failure. On success this function returns a cordova [FileEntry object](http://cordova.apache.org/docs/en/3.3.0/cordova_file_file.md.html#FileEntry).
```js
cordova.plugin.http.downloadFile("https://google.com/", {
id: 12,
id: '12',
message: 'test'
}, { Authorization: 'OAuth2: token' }, 'file:///somepicture.jpg', function(entry) {
// prints the filename
@@ -299,6 +413,59 @@ cordova.plugin.http.downloadFile("https://google.com/", {
});
```
### abort<a name="abort"></a>
Abort a HTTP request. Takes the `requestId` which is returned by [sendRequest](#sendRequest) and its shorthand functions ([post](#post), [get](#get), [put](#put), [patch](#patch), [delete](#delete), [head](#head), [uploadFile](#uploadFile) and [downloadFile](#downloadFile)).
If the request already has finished, the request will finish normally and the abort call result will be `{ aborted: false }`.
If the request is still in progress, the request's `failure` callback will be invoked with response `{ status: -8 }`, and the abort call result `{ aborted: true }`.
:warning: Not supported for Android < 6 (API level < 23). For Android 5.1 and below, calling `abort(reqestId)` will have no effect, i.e. the requests will finish as if the request was not cancelled.
```js
// start a request and get its requestId
var requestId = cordova.plugin.http.downloadFile("https://google.com/", {
id: '12',
message: 'test'
}, { Authorization: 'OAuth2: token' }, 'file:///somepicture.jpg', function(entry) {
// prints the filename
console.log(entry.name);
// prints the filePath
console.log(entry.fullPath);
}, function(response) {
// if request was actually aborted, failure callback with status -8 will be invoked
if(response.status === -8){
console.log('download aborted');
} else {
console.error(response.error);
}
});
//...
// abort request
cordova.plugin.http.abort(requestId, function(result) {
// prints if request was aborted: true | false
console.log(result.aborted);
}, function(response) {
console.error(response.error);
});
```
## Browser support<a name="browserSupport"></a>
This plugin supports a very restricted set of functions on the browser platform.
It's meant for testing purposes, not for production grade usage.
Following features are *not* supported:
* Manipulating Cookies
* Uploading and Downloading files
* Pinning SSL certificate
* Disabling SSL certificate check
* Disabling transparently following redirects (HTTP codes 3xx)
* Circumventing CORS restrictions
## Libraries
@@ -310,6 +477,38 @@ This plugin utilizes some awesome open source libraries:
We made a few modifications to the networking libraries.
## CI Builds & E2E Testing
This plugin uses amazing cloud services to maintain quality. CI Builds and E2E testing are powered by:
* [GitHub Actions](https://github.com/features/actions)
* [Travis CI](https://travis-ci.org/)
* [BrowserStack](https://www.browserstack.com/)
* [Sauce Labs](https://saucelabs.com/)
* [httpbin.org](https://httpbin.org/)
* [go-httpbin](https://httpbingo.org/)
### Local Testing
First, install current package with `npm install` to fetch dev dependencies.
Then, to execute Javascript tests:
```shell
npm run testjs
```
And, to execute E2E tests:
- setup local Android sdk and emulators, or Xcode and simulators for iOS
- launch emulator or simulator
- install [Appium](http://appium.io/) (see [Getting Started](https://github.com/appium/appium/blob/HEAD/docs/en/about-appium/getting-started.md))
- start `appium`
- run
- updating client and server certificates, building test app, and running e2e tests
```shell
npm run testandroid
npm run testios
```
## Contribute & Develop
We've set up a separate document for our [contribution guidelines](CONTRIBUTING.md).

12471
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,12 +1,16 @@
{
"name": "cordova-plugin-advanced-http",
"version": "1.10.2",
"version": "3.2.0",
"description": "Cordova / Phonegap plugin for communicating with HTTP servers using SSL pinning",
"scripts": {
"testandroid": "./scripts/build-test-app.sh --android --emulator && ./scripts/test-app.sh --android --emulator",
"testios": "./scripts/build-test-app.sh --ios --emulator && ./scripts/test-app.sh --ios --emulator",
"updatecert": "node ./scripts/update-e2e-server-cert.js && node ./scripts/update-e2e-client-cert.js",
"buildbrowser": "./scripts/build-test-app.sh --browser",
"buildandroid": "./scripts/build-test-app.sh --android --emulator",
"buildios": "./scripts/build-test-app.sh --ios --emulator",
"testandroid": "npm run updatecert && npm run buildandroid && ./scripts/test-app.sh --android --emulator",
"testios": "npm run updatecert && npm run buildios && ./scripts/test-app.sh --ios --emulator",
"testapp": "npm run testandroid && npm run testios",
"testjs": "mocha ./test/js-mocha-specs.js",
"testjs": "mocha ./test/js-specs.js",
"test": "npm run testjs && npm run testapp",
"release": "npm run test && ./scripts/release.sh"
},
@@ -46,7 +50,7 @@
"pvsaikrishna",
"cvillerm",
"hideov",
"Mobisys"
"silkimen"
],
"license": "MIT",
"bugs": {
@@ -54,15 +58,12 @@
},
"homepage": "https://github.com/silkimen/cordova-plugin-advanced-http#readme",
"devDependencies": {
"chai": "4.1.2",
"chai-as-promised": "7.1.1",
"colors": "1.1.2",
"cordova": "7.1.0",
"mocha": "4.0.0",
"mock-require": "2.0.2",
"mz": "2.7.0",
"umd-tough-cookie": "2.3.2",
"wd": "1.4.1",
"xml2js": "0.4.19"
"chai": "4.2.0",
"colors": "1.4.0",
"cordova": "10.0.0",
"mocha": "8.2.0",
"umd-tough-cookie": "2.4.3",
"wd": "1.12.1",
"xml2js": "0.4.23"
}
}

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<plugin xmlns="http://www.phonegap.com/ns/plugins/1.0" xmlns:android="http://schemas.android.com/apk/res/android" id="cordova-plugin-advanced-http" version="1.10.2">
<plugin xmlns="http://www.phonegap.com/ns/plugins/1.0" xmlns:android="http://schemas.android.com/apk/res/android" id="cordova-plugin-advanced-http" version="3.2.0">
<name>Advanced HTTP plugin</name>
<description>
Cordova / Phonegap plugin for communicating with HTTP servers using SSL pinning
@@ -8,13 +8,20 @@
<engine name="cordova" version=">=4.0.0"/>
</engines>
<dependency id="cordova-plugin-file" version=">=2.0.0"/>
<js-module src="www/lodash.js" name="lodash"/>
<js-module src="www/umd-tough-cookie.js" name="tough-cookie"/>
<js-module src="www/messages.js" name="messages"/>
<js-module src="www/local-storage-store.js" name="local-storage-store"/>
<preference name="AndroidBlacklistSecureSocketProtocols" default="SSLv3,TLSv1"/>
<js-module src="www/cookie-handler.js" name="cookie-handler"/>
<js-module src="www/angular-integration.js" name="angular-integration"/>
<js-module src="www/dependency-validator.js" name="dependency-validator"/>
<js-module src="www/error-codes.js" name="error-codes"/>
<js-module src="www/global-configs.js" name="global-configs"/>
<js-module src="www/helpers.js" name="helpers"/>
<js-module src="www/js-util.js" name="js-util"/>
<js-module src="www/local-storage-store.js" name="local-storage-store"/>
<js-module src="www/lodash.js" name="lodash"/>
<js-module src="www/messages.js" name="messages"/>
<js-module src="www/ponyfills.js" name="ponyfills"/>
<js-module src="www/public-interface.js" name="public-interface"/>
<js-module src="www/umd-tough-cookie.js" name="tough-cookie"/>
<js-module src="www/url-util.js" name="url-util"/>
<js-module src="www/advanced-http.js" name="http">
<clobbers target="cordova.plugin.http"/>
</js-module>
@@ -25,6 +32,8 @@
</feature>
</config-file>
<header-file src="src/ios/CordovaHttpPlugin.h"/>
<header-file src="src/ios/BinaryRequestSerializer.h"/>
<header-file src="src/ios/BinaryResponseSerializer.h"/>
<header-file src="src/ios/TextResponseSerializer.h"/>
<header-file src="src/ios/TextRequestSerializer.h"/>
<header-file src="src/ios/AFNetworking/AFHTTPSessionManager.h"/>
@@ -34,7 +43,10 @@
<header-file src="src/ios/AFNetworking/AFURLRequestSerialization.h"/>
<header-file src="src/ios/AFNetworking/AFURLResponseSerialization.h"/>
<header-file src="src/ios/AFNetworking/AFURLSessionManager.h"/>
<header-file src="src/ios/SDNetworkActivityIndicator/SDNetworkActivityIndicator.h"/>
<source-file src="src/ios/CordovaHttpPlugin.m"/>
<source-file src="src/ios/BinaryRequestSerializer.m"/>
<source-file src="src/ios/BinaryResponseSerializer.m"/>
<source-file src="src/ios/TextResponseSerializer.m"/>
<source-file src="src/ios/TextRequestSerializer.m"/>
<source-file src="src/ios/AFNetworking/AFHTTPSessionManager.m"/>
@@ -43,31 +55,43 @@
<source-file src="src/ios/AFNetworking/AFURLRequestSerialization.m"/>
<source-file src="src/ios/AFNetworking/AFURLResponseSerialization.m"/>
<source-file src="src/ios/AFNetworking/AFURLSessionManager.m"/>
<source-file src="src/ios/SDNetworkActivityIndicator/SDNetworkActivityIndicator.m"/>
<framework src="Security.framework"/>
<framework src="SystemConfiguration.framework"/>
</platform>
<platform name="android">
<config-file target="res/xml/config.xml" parent="/*">
<feature name="CordovaHttpPlugin">
<param name="android-package" value="com.synconset.cordovahttp.CordovaHttpPlugin"/>
<param name="android-package" value="com.silkimen.cordovahttp.CordovaHttpPlugin"/>
</feature>
</config-file>
<config-file target="AndroidManifest.xml" parent="/manifest">
<uses-permission android:name="android.permission.INTERNET"/>
</config-file>
<source-file src="src/android/com/github/kevinsawicki/http/HttpRequest.java" target-dir="src/com/github/kevinsawicki/http"/>
<source-file src="src/android/com/github/kevinsawicki/http/OkConnectionFactory.java" target-dir="src/com/github/kevinsawicki/http"/>
<source-file src="src/android/com/github/kevinsawicki/http/TLSSocketFactory.java" target-dir="src/com/github/kevinsawicki/http"/>
<source-file src="src/android/com/synconset/cordovahttp/CordovaHttp.java" target-dir="src/com/synconset/cordovahttp"/>
<source-file src="src/android/com/synconset/cordovahttp/CordovaHttpDelete.java" target-dir="src/com/synconset/cordovahttp"/>
<source-file src="src/android/com/synconset/cordovahttp/CordovaHttpDownload.java" target-dir="src/com/synconset/cordovahttp"/>
<source-file src="src/android/com/synconset/cordovahttp/CordovaHttpGet.java" target-dir="src/com/synconset/cordovahttp"/>
<source-file src="src/android/com/synconset/cordovahttp/CordovaHttpHead.java" target-dir="src/com/synconset/cordovahttp"/>
<source-file src="src/android/com/synconset/cordovahttp/CordovaHttpPlugin.java" target-dir="src/com/synconset/cordovahttp"/>
<source-file src="src/android/com/synconset/cordovahttp/CordovaHttpPost.java" target-dir="src/com/synconset/cordovahttp"/>
<source-file src="src/android/com/synconset/cordovahttp/CordovaHttpPut.java" target-dir="src/com/synconset/cordovahttp"/>
<source-file src="src/android/com/synconset/cordovahttp/CordovaHttpPatch.java" target-dir="src/com/synconset/cordovahttp"/>
<source-file src="src/android/com/synconset/cordovahttp/CordovaHttpUpload.java" target-dir="src/com/synconset/cordovahttp"/>
<framework src="com.squareup.okhttp3:okhttp-urlconnection:3.9.1"/>
<source-file src="src/android/com/silkimen/cordovahttp/CordovaClientAuth.java" target-dir="src/com/silkimen/cordovahttp"/>
<source-file src="src/android/com/silkimen/cordovahttp/CordovaHttpBase.java" target-dir="src/com/silkimen/cordovahttp"/>
<source-file src="src/android/com/silkimen/cordovahttp/CordovaHttpDownload.java" target-dir="src/com/silkimen/cordovahttp"/>
<source-file src="src/android/com/silkimen/cordovahttp/CordovaHttpOperation.java" target-dir="src/com/silkimen/cordovahttp"/>
<source-file src="src/android/com/silkimen/cordovahttp/CordovaHttpPlugin.java" target-dir="src/com/silkimen/cordovahttp"/>
<source-file src="src/android/com/silkimen/cordovahttp/CordovaHttpResponse.java" target-dir="src/com/silkimen/cordovahttp"/>
<source-file src="src/android/com/silkimen/cordovahttp/CordovaHttpUpload.java" target-dir="src/com/silkimen/cordovahttp"/>
<source-file src="src/android/com/silkimen/cordovahttp/CordovaObservableCallbackContext.java" target-dir="src/com/silkimen/cordovahttp"/>
<source-file src="src/android/com/silkimen/cordovahttp/CordovaServerTrust.java" target-dir="src/com/silkimen/cordovahttp"/>
<source-file src="src/android/com/silkimen/http/HttpBodyDecoder.java" target-dir="src/com/silkimen/http"/>
<source-file src="src/android/com/silkimen/http/HttpRequest.java" target-dir="src/com/silkimen/http"/>
<source-file src="src/android/com/silkimen/http/JsonUtils.java" target-dir="src/com/silkimen/http"/>
<source-file src="src/android/com/silkimen/http/KeyChainKeyManager.java" target-dir="src/com/silkimen/http"/>
<source-file src="src/android/com/silkimen/http/TLSConfiguration.java" target-dir="src/com/silkimen/http"/>
<source-file src="src/android/com/silkimen/http/TLSSocketFactory.java" target-dir="src/com/silkimen/http"/>
</platform>
<platform name="browser">
<config-file target="config.xml" parent="/*">
<feature name="CordovaHttpPlugin">
<param name="browser-package" value="CordovaHttpPlugin"/>
</feature>
</config-file>
<js-module src="src/browser/cordova-http-plugin.js" name="http-proxy">
<runs/>
</js-module>
</platform>
</plugin>

View File

@@ -1,16 +1,47 @@
#!/usr/bin/env bash
set -e
PLATFORM=$([[ "${@#--android}" = "$@" ]] && echo "ios" || echo "android")
TARGET=$([[ "${@#--device}" = "$@" ]] && echo "emulator" || echo "device")
ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"/..
ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )"; cd ..; pwd )"
WORKINGCOPY=$ROOT/temp/workingcopy
CDV=$ROOT/node_modules/.bin/cordova
PLATFORM=ios
TARGET=emulator
while :; do
case $1 in
--android)
PLATFORM=android
;;
--browser)
PLATFORM=browser
;;
--ios)
PLATFORM=ios
;;
--device)
TARGET=device
;;
--emulator)
TARGET=emulator
;;
-?*)
printf 'WARN: Unknown option (ignored): %s\n' "$1" >&2
;;
*)
break
esac
shift
done
printf 'Building test app for %s\n' $PLATFORM
rm -rf $ROOT/temp
mkdir $ROOT/temp
cp -r $ROOT/test/app-template/ $ROOT/temp/
cp $ROOT/test/app-test-definitions.js $ROOT/temp/www/
cp -r $ROOT/test/e2e-app-template/. $ROOT/temp/
cp $ROOT/test/e2e-specs.js $ROOT/temp/www/
rsync -ax --exclude node_modules --exclude scripts --exclude temp --exclude test $ROOT/. $WORKINGCOPY
cd $ROOT/temp
$CDV prepare
$CDV plugins add $ROOT
$CDV build $PLATFORM --$TARGET
$CDV plugins add $WORKINGCOPY
$CDV build $PLATFORM --$TARGET --buildConfig build.json

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env bash
set -e
ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"/..
ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )"; cd ..; pwd )"
pushd $ROOT
VERSION=$(node -e "console.log(require('./package.json').version)")

View File

@@ -1,12 +0,0 @@
#!/usr/bin/env bash
set -e
curl http://dl.google.com/android/android-sdk_r24.4-macosx.zip -o android-sdk-macosx.zip
tar -xvf android-sdk-macosx.zip
echo y | ./android-sdk-macosx/tools/android update sdk --no-ui --all --filter platform-tools
echo y | ./android-sdk-macosx/tools/android update sdk --no-ui --all --filter build-tools-25.0.0
echo y | ./android-sdk-macosx/tools/android update sdk --no-ui --all --filter android-25
echo y | ./android-sdk-macosx/tools/android update sdk --no-ui --all --filter extra-android-support
echo y | ./android-sdk-macosx/tools/android update sdk --no-ui --all --filter extra-android-m2repository
echo y | ./android-sdk-macosx/tools/android update sdk --no-ui --all --filter extra-google-m2repository

View File

@@ -1,13 +1,14 @@
#!/usr/bin/env bash
set -e
ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"/..
ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )"; cd ..; pwd )"
if [ $CI == "true" ] && ([ -z $SAUCE_USERNAME ] || [ -z $SAUCE_ACCESS_KEY ]); then
echo "Skipping CI tests, because Saucelabs credentials are not set.";
if [ $CI == "true" ] && ([ -z $SAUCE_USERNAME ] || [ -z $SAUCE_ACCESS_KEY ]) && ([ -z $BROWSERSTACK_USERNAME ] || [ -z $BROWSERSTACK_ACCESS_KEY ]); then
echo "Skipping CI tests, because Saucelabs and BrowserStack credentials are not set.";
exit 0;
fi
printf 'Running e2e tests\n'
pushd $ROOT
./node_modules/.bin/mocha ./test/app-mocha-specs/test.js "$@"
./node_modules/.bin/mocha --reporter ./test/e2e-tooling/reporter.js ./test/e2e-tooling/test.js "$@"
popd

View File

@@ -0,0 +1,29 @@
const fs = require('fs');
const https = require('https');
const path = require('path');
const SOURCE_URL = 'https://badssl.com/certs/badssl.com-client.p12';
const TARGET_PATH = path.join(__dirname, '../test/e2e-app-template/www/certificates/badssl-client-cert.pkcs');
const downloadPkcsContainer = (source, target) => new Promise((resolve, reject) => {
const file = fs.createWriteStream(target);
const req = https.get(source, response => {
response.pipe(file)
resolve(target);
});
req.on('error', error => {
return reject(error)
});
req.end();
});
console.log(`Updating client certificate from ${SOURCE_URL}`);
downloadPkcsContainer(SOURCE_URL, TARGET_PATH)
.catch(error => {
console.error(`Updating client certificate failed: ${error}`);
process.exit(1);
});

View File

@@ -0,0 +1,40 @@
const fs = require('fs');
const https = require('https');
const path = require('path');
const SOURCE_HOST = 'httpbin.org';
const TARGET_PATH = path.join(__dirname, '../test/e2e-app-template/www/certificates/httpbin.org.cer');
const getCert = hostname => new Promise((resolve, reject) => {
const options = {
hostname,
agent: false,
rejectUnauthorized: false,
ciphers: 'ALL'
};
const req = https.get(options, response => {
const certificate = response.socket.getPeerCertificate();
if (certificate === null) {
return reject({ message: 'The website did not provide a certificate' });
}
resolve(certificate);
});
req.on('error', error => {
return reject(error)
});
req.end();
});
console.log(`Updating server certificate from ${SOURCE_HOST}`);
getCert(SOURCE_HOST)
.then(cert => fs.writeFileSync(TARGET_PATH, cert.raw))
.catch(error => {
console.error(`Updating server certificate failed: ${error}`);
process.exit(1);
});

View File

@@ -1,5 +1,5 @@
const args = process.argv.slice(2);
const fs = require('mz/fs');
const fs = require('fs');
const path = require('path');
const xml2js = require('xml2js');
const xmlPath = path.join(__dirname, '..', 'plugin.xml');
@@ -22,10 +22,12 @@ const stringify = obj => {
return builder.buildObject(obj);
};
fs.readFile(xmlPath, 'utf-8')
.then(xml => parse(xml))
.then(parsed => {
parsed.plugin.$.version = args[0];
const update = async (version) => {
const xml = fs.readFileSync(xmlPath, 'utf-8');
const parsed = await parse(xml);
return fs.writeFile(xmlPath, stringify(parsed));
});
parsed.plugin.$.version = version;
fs.writeFileSync(xmlPath, stringify(parsed));
};
return update(args[0]);

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env bash
set -e
ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"/..
ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )"; cd ..; pwd )"
cd $ROOT
npm i

33
scripts/upload-browserstack.sh Executable file
View File

@@ -0,0 +1,33 @@
#!/usr/bin/env bash
set -e
PLATFORM=$([[ "${@#--android}" = "$@" ]] && echo "ios" || echo "android")
ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )"; cd ..; pwd )"
TEMP=$ROOT/temp
if [ -z $BROWSERSTACK_USERNAME ] || [ -z $BROWSERSTACK_ACCESS_KEY ]; then
echo "Skipping uploading artifact, because BrowserStack credentials are not set.";
exit 0;
fi
if [ $PLATFORM = "android" ]; then
curl -u $BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY \
-X POST \
https://api-cloud.browserstack.com/app-automate/upload \
-F "file=@$TEMP/platforms/android/app/build/outputs/apk/debug/app-debug.apk" \
-F "data={\"custom_id\": \"HttpTestAppAndroid\"}"
else
rm -rf $TEMP/HttpDemo.ipa
pushd $TEMP/platforms/ios/build/emulator
rm -rf ./Payload
mkdir -p ./Payload
cp -r ./HttpDemo.app ./Payload/HttpDemo.app
zip -r $TEMP/HttpDemo.ipa ./Payload
popd
curl -u $BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY \
-X POST \
https://api-cloud.browserstack.com/app-automate/upload \
-F "file=@$TEMP/HttpDemo.ipa" \
-F "data={\"custom_id\": \"HttpTestAppIos\"}"
fi

View File

@@ -2,7 +2,7 @@
set -e
PLATFORM=$([[ "${@#--android}" = "$@" ]] && echo "ios" || echo "android")
ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"/..
ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )"; cd ..; pwd )"
TEMP=$ROOT/temp
if [ -z $SAUCE_USERNAME ] || [ -z $SAUCE_ACCESS_KEY ]; then
@@ -15,7 +15,7 @@ if [ $PLATFORM = "android" ]; then
-X POST \
-H "Content-Type: application/octet-stream" \
https://saucelabs.com/rest/v1/storage/$SAUCE_USERNAME/HttpDemo.apk?overwrite=true \
--data-binary @$TEMP/platforms/android/build/outputs/apk/android-debug.apk
--data-binary @$TEMP/platforms/android/app/build/outputs/apk/debug/app-debug.apk
else
rm -rf $TEMP/HttpDemo.app.zip
pushd $TEMP/platforms/ios/build/emulator

View File

@@ -1,26 +0,0 @@
package com.github.kevinsawicki.http;
import okhttp3.OkUrlFactory;
import okhttp3.OkHttpClient;
import java.net.URL;
import java.net.HttpURLConnection;
import java.net.URLStreamHandler;
import java.net.Proxy;
public class OkConnectionFactory implements HttpRequest.ConnectionFactory {
protected OkHttpClient okHttpClient = new OkHttpClient();
public HttpURLConnection create(URL url) {
OkUrlFactory okUrlFactory = new OkUrlFactory(okHttpClient);
return (HttpURLConnection) okUrlFactory.open(url);
}
public HttpURLConnection create(URL url, Proxy proxy) {
OkHttpClient okHttpClientWithProxy = okHttpClient.newBuilder().proxy(proxy).build();
OkUrlFactory okUrlFactory = new OkUrlFactory(okHttpClientWithProxy);
return (HttpURLConnection) okUrlFactory.open(url);
}
}

View File

@@ -1,61 +0,0 @@
package com.github.kevinsawicki.http;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
public class TLSSocketFactory extends SSLSocketFactory {
private SSLSocketFactory delegate;
public TLSSocketFactory(SSLContext context) {
delegate = context.getSocketFactory();
}
@Override
public String[] getDefaultCipherSuites() {
return delegate.getDefaultCipherSuites();
}
@Override
public String[] getSupportedCipherSuites() {
return delegate.getSupportedCipherSuites();
}
@Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
return enableTLSOnSocket(delegate.createSocket(s, host, port, autoClose));
}
@Override
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
return enableTLSOnSocket(delegate.createSocket(host, port));
}
@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
return enableTLSOnSocket(delegate.createSocket(host, port, localHost, localPort));
}
@Override
public Socket createSocket(InetAddress host, int port) throws IOException {
return enableTLSOnSocket(delegate.createSocket(host, port));
}
@Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
return enableTLSOnSocket(delegate.createSocket(address, port, localAddress, localPort));
}
private Socket enableTLSOnSocket(Socket socket) {
if(socket != null && (socket instanceof SSLSocket)) {
((SSLSocket)socket).setEnabledProtocols(new String[] {"TLSv1", "TLSv1.1", "TLSv1.2"});
}
return socket;
}
}

View File

@@ -0,0 +1,113 @@
package com.silkimen.cordovahttp;
import android.app.Activity;
import android.content.Context;
import android.security.KeyChain;
import android.security.KeyChainAliasCallback;
import android.util.Log;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.net.URI;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import org.apache.cordova.CallbackContext;
import com.silkimen.http.KeyChainKeyManager;
import com.silkimen.http.TLSConfiguration;
class CordovaClientAuth implements Runnable, KeyChainAliasCallback {
private static final String TAG = "Cordova-Plugin-HTTP";
private String mode;
private String aliasString;
private byte[] rawPkcs;
private String pkcsPassword;
private Activity activity;
private Context context;
private TLSConfiguration tlsConfiguration;
private CallbackContext callbackContext;
public CordovaClientAuth(final String mode, final String aliasString, final byte[] rawPkcs,
final String pkcsPassword, final Activity activity, final Context context, final TLSConfiguration configContainer,
final CallbackContext callbackContext) {
this.mode = mode;
this.aliasString = aliasString;
this.rawPkcs = rawPkcs;
this.pkcsPassword = pkcsPassword;
this.activity = activity;
this.tlsConfiguration = configContainer;
this.context = context;
this.callbackContext = callbackContext;
}
@Override
public void run() {
if ("systemstore".equals(this.mode)) {
this.loadFromSystemStore();
} else if ("buffer".equals(this.mode)) {
this.loadFromBuffer();
} else {
this.disableClientAuth();
}
}
private void loadFromSystemStore() {
if (this.aliasString == null) {
KeyChain.choosePrivateKeyAlias(this.activity, this, null, null, null, -1, null);
} else {
this.alias(this.aliasString);
}
}
private void loadFromBuffer() {
try {
KeyStore keyStore = KeyStore.getInstance("PKCS12");
String keyManagerFactoryAlgorithm = KeyManagerFactory.getDefaultAlgorithm();
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(keyManagerFactoryAlgorithm);
ByteArrayInputStream stream = new ByteArrayInputStream(this.rawPkcs);
keyStore.load(stream, this.pkcsPassword.toCharArray());
keyManagerFactory.init(keyStore, this.pkcsPassword.toCharArray());
this.tlsConfiguration.setKeyManagers(keyManagerFactory.getKeyManagers());
this.callbackContext.success();
} catch (Exception e) {
Log.e(TAG, "Couldn't load given PKCS12 container for authentication", e);
this.callbackContext.error("Couldn't load given PKCS12 container for authentication");
}
}
private void disableClientAuth() {
this.tlsConfiguration.setKeyManagers(null);
this.callbackContext.success();
}
@Override
public void alias(final String alias) {
try {
if (alias == null) {
throw new Exception("Couldn't get a consent for private key access");
}
PrivateKey key = KeyChain.getPrivateKey(this.context, alias);
X509Certificate[] chain = KeyChain.getCertificateChain(this.context, alias);
KeyManager keyManager = new KeyChainKeyManager(alias, key, chain);
this.tlsConfiguration.setKeyManagers(new KeyManager[] { keyManager });
this.callbackContext.success(alias);
} catch (Exception e) {
Log.e(TAG, "Couldn't load private key and certificate pair with given alias \"" + alias + "\" for authentication",
e);
this.callbackContext.error(
"Couldn't load private key and certificate pair with given alias \"" + alias + "\" for authentication");
}
}
}

View File

@@ -0,0 +1,233 @@
package com.silkimen.cordovahttp;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import javax.net.ssl.SSLException;
import com.silkimen.http.HttpBodyDecoder;
import com.silkimen.http.HttpRequest;
import com.silkimen.http.HttpRequest.HttpRequestException;
import com.silkimen.http.JsonUtils;
import com.silkimen.http.TLSConfiguration;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.util.Base64;
import android.util.Log;
abstract class CordovaHttpBase implements Runnable {
protected static final String TAG = "Cordova-Plugin-HTTP";
protected String method;
protected String url;
protected String serializer = "none";
protected String responseType;
protected Object data;
protected JSONObject headers;
protected int timeout;
protected boolean followRedirects;
protected TLSConfiguration tlsConfiguration;
protected CordovaObservableCallbackContext callbackContext;
public CordovaHttpBase(String method, String url, String serializer, Object data, JSONObject headers, int timeout,
boolean followRedirects, String responseType, TLSConfiguration tlsConfiguration,
CordovaObservableCallbackContext callbackContext) {
this.method = method;
this.url = url;
this.serializer = serializer;
this.data = data;
this.headers = headers;
this.timeout = timeout;
this.followRedirects = followRedirects;
this.responseType = responseType;
this.tlsConfiguration = tlsConfiguration;
this.callbackContext = callbackContext;
}
public CordovaHttpBase(String method, String url, JSONObject headers, int timeout, boolean followRedirects,
String responseType, TLSConfiguration tlsConfiguration, CordovaObservableCallbackContext callbackContext) {
this.method = method;
this.url = url;
this.headers = headers;
this.timeout = timeout;
this.followRedirects = followRedirects;
this.responseType = responseType;
this.tlsConfiguration = tlsConfiguration;
this.callbackContext = callbackContext;
}
@Override
public void run() {
CordovaHttpResponse response = new CordovaHttpResponse();
HttpRequest request = null;
try {
request = this.createRequest();
this.prepareRequest(request);
this.sendBody(request);
this.processResponse(request, response);
request.disconnect();
} catch (HttpRequestException e) {
Throwable cause = e.getCause();
String message = cause.getMessage();
if (cause instanceof SSLException) {
response.setStatus(-2);
response.setErrorMessage("TLS connection could not be established: " + e.getMessage());
Log.w(TAG, "TLS connection could not be established", e);
} else if (cause instanceof UnknownHostException) {
response.setStatus(-3);
response.setErrorMessage("Host could not be resolved: " + e.getMessage());
Log.w(TAG, "Host could not be resolved", e);
} else if (cause instanceof SocketTimeoutException) {
response.setStatus(-4);
response.setErrorMessage("Request timed out: " + e.getMessage());
Log.w(TAG, "Request timed out", e);
} else if (cause instanceof InterruptedIOException && "thread interrupted".equals(message.toLowerCase())) {
this.setAborted(request, response);
} else {
response.setStatus(-1);
response.setErrorMessage("There was an error with the request: " + message);
Log.w(TAG, "Generic request error", e);
}
} catch (InterruptedException ie) {
this.setAborted(request, response);
} catch (Exception e) {
response.setStatus(-1);
response.setErrorMessage(e.getMessage());
Log.e(TAG, "An unexpected error occured", e);
}
try {
if (response.hasFailed()) {
this.callbackContext.error(response.toJSON());
} else {
this.callbackContext.success(response.toJSON());
}
} catch (JSONException e) {
Log.e(TAG, "An unexpected error occured while creating HTTP response object", e);
}
}
protected HttpRequest createRequest() throws JSONException {
return new HttpRequest(this.url, this.method);
}
protected void prepareRequest(HttpRequest request) throws JSONException, IOException {
request.followRedirects(this.followRedirects);
request.readTimeout(this.timeout);
request.acceptCharset("UTF-8");
request.uncompress(true);
if (this.tlsConfiguration.getHostnameVerifier() != null) {
request.setHostnameVerifier(this.tlsConfiguration.getHostnameVerifier());
}
request.setSSLSocketFactory(this.tlsConfiguration.getTLSSocketFactory());
// setup content type before applying headers, so user can override it
this.setContentType(request);
request.headers(JsonUtils.getStringMap(this.headers));
}
protected void setContentType(HttpRequest request) {
if ("json".equals(this.serializer)) {
request.contentType("application/json", "UTF-8");
} else if ("utf8".equals(this.serializer)) {
request.contentType("text/plain", "UTF-8");
} else if ("raw".equals(this.serializer)) {
request.contentType("application/octet-stream");
} else if ("urlencoded".equals(this.serializer)) {
// intentionally left blank, because content type is set in HttpRequest.form()
} else if ("multipart".equals(this.serializer)) {
// intentionally left blank, because content type is set in HttpRequest.part()
}
}
protected void sendBody(HttpRequest request) throws Exception {
if (this.data == null) {
return;
}
if ("json".equals(this.serializer)) {
request.send(this.data.toString());
} else if ("utf8".equals(this.serializer)) {
request.send(((JSONObject) this.data).getString("text"));
} else if ("raw".equals(this.serializer)) {
request.send(Base64.decode((String)this.data, Base64.DEFAULT));
} else if ("urlencoded".equals(this.serializer)) {
request.form(JsonUtils.getObjectMap((JSONObject) this.data));
} else if ("multipart".equals(this.serializer)) {
JSONArray buffers = ((JSONObject) this.data).getJSONArray("buffers");
JSONArray names = ((JSONObject) this.data).getJSONArray("names");
JSONArray fileNames = ((JSONObject) this.data).getJSONArray("fileNames");
JSONArray types = ((JSONObject) this.data).getJSONArray("types");
for (int i = 0; i < buffers.length(); ++i) {
byte[] bytes = Base64.decode(buffers.getString(i), Base64.DEFAULT);
String name = names.getString(i);
if (fileNames.isNull(i)) {
request.part(name, new String(bytes, "UTF-8"));
} else {
request.part(name, fileNames.getString(i), types.getString(i), new ByteArrayInputStream(bytes));
}
}
// prevent sending malformed empty multipart requests (#372)
if (buffers.length() == 0) {
request.contentType("multipart/form-data; boundary=00content0boundary00");
request.send("\r\n--00content0boundary00--\r\n");
}
}
}
protected void processResponse(HttpRequest request, CordovaHttpResponse response) throws Exception {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
request.receive(outputStream);
response.setStatus(request.code());
response.setUrl(request.url().toString());
response.setHeaders(request.headers());
if (request.code() >= 200 && request.code() < 300) {
if ("text".equals(this.responseType) || "json".equals(this.responseType)) {
String decoded = HttpBodyDecoder.decodeBody(outputStream.toByteArray(), request.charset());
response.setBody(decoded);
} else {
response.setData(outputStream.toByteArray());
}
} else {
response.setErrorMessage(HttpBodyDecoder.decodeBody(outputStream.toByteArray(), request.charset()));
}
}
protected void setAborted(HttpRequest request, CordovaHttpResponse response) {
response.setStatus(-8);
response.setErrorMessage("Request was aborted");
if (request != null) {
try {
request.disconnect();
} catch(Exception any){
Log.w(TAG, "Failed to close aborted request", any);
}
}
Log.i(TAG, "Request was aborted");
}
}

View File

@@ -0,0 +1,41 @@
package com.silkimen.cordovahttp;
import java.io.File;
import java.net.URI;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSocketFactory;
import com.silkimen.http.HttpRequest;
import com.silkimen.http.TLSConfiguration;
import org.apache.cordova.file.FileUtils;
import org.json.JSONObject;
class CordovaHttpDownload extends CordovaHttpBase {
private String filePath;
public CordovaHttpDownload(String url, JSONObject headers, String filePath, int timeout, boolean followRedirects,
TLSConfiguration tlsConfiguration, CordovaObservableCallbackContext callbackContext) {
super("GET", url, headers, timeout, followRedirects, "text", tlsConfiguration, callbackContext);
this.filePath = filePath;
}
@Override
protected void processResponse(HttpRequest request, CordovaHttpResponse response) throws Exception {
response.setStatus(request.code());
response.setUrl(request.url().toString());
response.setHeaders(request.headers());
if (request.code() >= 200 && request.code() < 300) {
File file = new File(new URI(this.filePath));
JSONObject fileEntry = FileUtils.getFilePlugin().getEntryForFile(file);
request.receive(file);
response.setFileEntry(fileEntry);
} else {
response.setErrorMessage("There was an error downloading the file");
}
}
}

View File

@@ -0,0 +1,24 @@
package com.silkimen.cordovahttp;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSocketFactory;
import com.silkimen.http.TLSConfiguration;
import org.json.JSONObject;
class CordovaHttpOperation extends CordovaHttpBase {
public CordovaHttpOperation(String method, String url, String serializer, Object data, JSONObject headers,
int timeout, boolean followRedirects, String responseType, TLSConfiguration tlsConfiguration,
CordovaObservableCallbackContext callbackContext) {
super(method, url, serializer, data, headers, timeout, followRedirects, responseType, tlsConfiguration,
callbackContext);
}
public CordovaHttpOperation(String method, String url, JSONObject headers, int timeout, boolean followRedirects,
String responseType, TLSConfiguration tlsConfiguration, CordovaObservableCallbackContext callbackContext) {
super(method, url, headers, timeout, followRedirects, responseType, tlsConfiguration, callbackContext);
}
}

View File

@@ -0,0 +1,248 @@
package com.silkimen.cordovahttp;
import java.security.KeyStore;
import java.util.HashMap;
import java.util.Observable;
import java.util.Observer;
import java.util.concurrent.Future;
import com.silkimen.http.TLSConfiguration;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.CordovaWebView;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.util.Log;
import android.util.Base64;
import javax.net.ssl.TrustManagerFactory;
public class CordovaHttpPlugin extends CordovaPlugin implements Observer {
private static final String TAG = "Cordova-Plugin-HTTP";
private TLSConfiguration tlsConfiguration;
private HashMap<Integer, Future<?>> reqMap;
private final Object reqMapLock = new Object();
@Override
public void initialize(CordovaInterface cordova, CordovaWebView webView) {
super.initialize(cordova, webView);
this.tlsConfiguration = new TLSConfiguration();
this.reqMap = new HashMap<Integer, Future<?>>();
try {
KeyStore store = KeyStore.getInstance("AndroidCAStore");
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
store.load(null);
tmf.init(store);
this.tlsConfiguration.setHostnameVerifier(null);
this.tlsConfiguration.setTrustManagers(tmf.getTrustManagers());
if (this.preferences.contains("androidblacklistsecuresocketprotocols")) {
this.tlsConfiguration.setBlacklistedProtocols(
this.preferences.getString("androidblacklistsecuresocketprotocols", "").split(",")
);
}
} catch (Exception e) {
Log.e(TAG, "An error occured while loading system's CA certificates", e);
}
}
@Override
public boolean execute(String action, final JSONArray args, final CallbackContext callbackContext)
throws JSONException {
if (action == null) {
return false;
}
if ("get".equals(action)) {
return this.executeHttpRequestWithoutData(action, args, callbackContext);
} else if ("head".equals(action)) {
return this.executeHttpRequestWithoutData(action, args, callbackContext);
} else if ("delete".equals(action)) {
return this.executeHttpRequestWithoutData(action, args, callbackContext);
} else if ("options".equals(action)) {
return this.executeHttpRequestWithoutData(action, args, callbackContext);
} else if ("post".equals(action)) {
return this.executeHttpRequestWithData(action, args, callbackContext);
} else if ("put".equals(action)) {
return this.executeHttpRequestWithData(action, args, callbackContext);
} else if ("patch".equals(action)) {
return this.executeHttpRequestWithData(action, args, callbackContext);
} else if ("uploadFiles".equals(action)) {
return this.uploadFiles(args, callbackContext);
} else if ("downloadFile".equals(action)) {
return this.downloadFile(args, callbackContext);
} else if ("setServerTrustMode".equals(action)) {
return this.setServerTrustMode(args, callbackContext);
} else if ("setClientAuthMode".equals(action)) {
return this.setClientAuthMode(args, callbackContext);
} else if ("abort".equals(action)) {
return this.abort(args, callbackContext);
} else {
return false;
}
}
private boolean executeHttpRequestWithoutData(final String method, final JSONArray args,
final CallbackContext callbackContext) throws JSONException {
String url = args.getString(0);
JSONObject headers = args.getJSONObject(1);
int timeout = args.getInt(2) * 1000;
boolean followRedirect = args.getBoolean(3);
String responseType = args.getString(4);
Integer reqId = args.getInt(5);
CordovaObservableCallbackContext observableCallbackContext = new CordovaObservableCallbackContext(callbackContext, reqId);
CordovaHttpOperation request = new CordovaHttpOperation(method.toUpperCase(), url, headers, timeout, followRedirect,
responseType, this.tlsConfiguration, observableCallbackContext);
startRequest(reqId, observableCallbackContext, request);
return true;
}
private boolean executeHttpRequestWithData(final String method, final JSONArray args,
final CallbackContext callbackContext) throws JSONException {
String url = args.getString(0);
Object data = args.get(1);
String serializer = args.getString(2);
JSONObject headers = args.getJSONObject(3);
int timeout = args.getInt(4) * 1000;
boolean followRedirect = args.getBoolean(5);
String responseType = args.getString(6);
Integer reqId = args.getInt(7);
CordovaObservableCallbackContext observableCallbackContext = new CordovaObservableCallbackContext(callbackContext, reqId);
CordovaHttpOperation request = new CordovaHttpOperation(method.toUpperCase(), url, serializer, data, headers,
timeout, followRedirect, responseType, this.tlsConfiguration, observableCallbackContext);
startRequest(reqId, observableCallbackContext, request);
return true;
}
private boolean uploadFiles(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
String url = args.getString(0);
JSONObject headers = args.getJSONObject(1);
JSONArray filePaths = args.getJSONArray(2);
JSONArray uploadNames = args.getJSONArray(3);
int timeout = args.getInt(4) * 1000;
boolean followRedirect = args.getBoolean(5);
String responseType = args.getString(6);
Integer reqId = args.getInt(7);
CordovaObservableCallbackContext observableCallbackContext = new CordovaObservableCallbackContext(callbackContext, reqId);
CordovaHttpUpload upload = new CordovaHttpUpload(url, headers, filePaths, uploadNames, timeout, followRedirect,
responseType, this.tlsConfiguration, this.cordova.getActivity().getApplicationContext(), observableCallbackContext);
startRequest(reqId, observableCallbackContext, upload);
return true;
}
private boolean downloadFile(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
String url = args.getString(0);
JSONObject headers = args.getJSONObject(1);
String filePath = args.getString(2);
int timeout = args.getInt(3) * 1000;
boolean followRedirect = args.getBoolean(4);
Integer reqId = args.getInt(5);
CordovaObservableCallbackContext observableCallbackContext = new CordovaObservableCallbackContext(callbackContext, reqId);
CordovaHttpDownload download = new CordovaHttpDownload(url, headers, filePath, timeout, followRedirect,
this.tlsConfiguration, observableCallbackContext);
startRequest(reqId, observableCallbackContext, download);
return true;
}
private void startRequest(Integer reqId, CordovaObservableCallbackContext observableCallbackContext, CordovaHttpBase request) {
synchronized (reqMapLock) {
observableCallbackContext.setObserver(this);
Future<?> task = cordova.getThreadPool().submit(request);
this.addReq(reqId, task, observableCallbackContext);
}
}
private boolean setServerTrustMode(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
CordovaServerTrust runnable = new CordovaServerTrust(args.getString(0), this.cordova.getActivity(),
this.tlsConfiguration, callbackContext);
cordova.getThreadPool().execute(runnable);
return true;
}
private boolean setClientAuthMode(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
byte[] pkcs = args.isNull(2) ? null : Base64.decode(args.getString(2), Base64.DEFAULT);
CordovaClientAuth runnable = new CordovaClientAuth(args.getString(0), args.isNull(1) ? null : args.getString(1),
pkcs, args.getString(3), this.cordova.getActivity(), this.cordova.getActivity().getApplicationContext(),
this.tlsConfiguration, callbackContext);
cordova.getThreadPool().execute(runnable);
return true;
}
private boolean abort(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
int reqId = args.getInt(0);
boolean result = false;
// NOTE no synchronized (reqMapLock), since even if the req was already removed from reqMap,
// the worst that would happen calling task.cancel(true) is a result of false
// (i.e. same result as locking & not finding the req in reqMap)
Future<?> task = this.reqMap.get(reqId);
if (task != null && !task.isDone()) {
result = task.cancel(true);
}
callbackContext.success(new JSONObject().put("aborted", result));
return true;
}
private void addReq(final Integer reqId, final Future<?> task, final CordovaObservableCallbackContext observableCallbackContext) {
synchronized (reqMapLock) {
if (!task.isDone()){
this.reqMap.put(reqId, task);
}
}
}
private void removeReq(final Integer reqId) {
synchronized (reqMapLock) {
this.reqMap.remove(reqId);
}
}
@Override
public void update(Observable o, Object arg) {
synchronized (reqMapLock) {
CordovaObservableCallbackContext c = (CordovaObservableCallbackContext) arg;
if (c.getCallbackContext().isFinished()) {
removeReq(c.getRequestId());
}
}
}
}

View File

@@ -0,0 +1,100 @@
package com.silkimen.cordovahttp;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.json.JSONException;
import org.json.JSONObject;
import android.text.TextUtils;
import android.util.Log;
import android.util.Base64;
class CordovaHttpResponse {
private int status;
private String url;
private Map<String, List<String>> headers;
private String body;
private byte[] rawData;
private JSONObject fileEntry;
private boolean hasFailed;
private boolean isFileOperation;
private boolean isRawResponse;
private String error;
public void setStatus(int status) {
this.status = status;
}
public void setUrl(String url) {
this.url = url;
}
public void setHeaders(Map<String, List<String>> headers) {
this.headers = headers;
}
public void setBody(String body) {
this.body = body;
}
public void setData(byte[] rawData) {
this.isRawResponse = true;
this.rawData = rawData;
}
public void setFileEntry(JSONObject entry) {
this.isFileOperation = true;
this.fileEntry = entry;
}
public void setErrorMessage(String message) {
this.hasFailed = true;
this.error = message;
}
public boolean hasFailed() {
return this.hasFailed;
}
public JSONObject toJSON() throws JSONException {
JSONObject json = new JSONObject();
json.put("status", this.status);
json.put("url", this.url);
if (this.headers != null && !this.headers.isEmpty()) {
json.put("headers", new JSONObject(getFilteredHeaders()));
}
if (this.hasFailed) {
json.put("error", this.error);
} else if (this.isFileOperation) {
json.put("file", this.fileEntry);
} else if (this.isRawResponse) {
json.put("data", Base64.encodeToString(this.rawData, Base64.DEFAULT));
} else {
json.put("data", this.body);
}
return json;
}
private Map<String, String> getFilteredHeaders() throws JSONException {
Map<String, String> filteredHeaders = new HashMap<String, String>();
for (Map.Entry<String, List<String>> entry : this.headers.entrySet()) {
String key = entry.getKey();
List<String> value = entry.getValue();
if ((key != null) && (!value.isEmpty())) {
filteredHeaders.put(key.toLowerCase(), TextUtils.join(", ", value));
}
}
return filteredHeaders;
}
}

View File

@@ -0,0 +1,91 @@
package com.silkimen.cordovahttp;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.provider.OpenableColumns;
import android.webkit.MimeTypeMap;
import com.silkimen.http.HttpRequest;
import com.silkimen.http.TLSConfiguration;
import java.io.File;
import java.io.InputStream;
import java.net.URI;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSocketFactory;
import org.json.JSONArray;
import org.json.JSONObject;
class CordovaHttpUpload extends CordovaHttpBase {
private JSONArray filePaths;
private JSONArray uploadNames;
private Context applicationContext;
public CordovaHttpUpload(String url, JSONObject headers, JSONArray filePaths, JSONArray uploadNames, int timeout,
boolean followRedirects, String responseType, TLSConfiguration tlsConfiguration,
Context applicationContext, CordovaObservableCallbackContext callbackContext) {
super("POST", url, headers, timeout, followRedirects, responseType, tlsConfiguration, callbackContext);
this.filePaths = filePaths;
this.uploadNames = uploadNames;
this.applicationContext = applicationContext;
}
@Override
protected void sendBody(HttpRequest request) throws Exception {
for (int i = 0; i < this.filePaths.length(); ++i) {
String uploadName = this.uploadNames.getString(i);
String filePath = this.filePaths.getString(i);
Uri fileUri = Uri.parse(filePath);
// File Scheme
if (ContentResolver.SCHEME_FILE.equals(fileUri.getScheme())) {
File file = new File(new URI(filePath));
String fileName = file.getName().trim();
String mimeType = this.getMimeTypeFromFileName(fileName);
request.part(uploadName, fileName, mimeType, file);
}
// Content Scheme
if (ContentResolver.SCHEME_CONTENT.equals(fileUri.getScheme())) {
InputStream inputStream = this.applicationContext.getContentResolver().openInputStream(fileUri);
String fileName = this.getFileNameFromContentScheme(fileUri, this.applicationContext).trim();
String mimeType = this.getMimeTypeFromFileName(fileName);
request.part(uploadName, fileName, mimeType, inputStream);
}
}
}
private String getFileNameFromContentScheme(Uri contentSchemeUri, Context applicationContext) {
Cursor returnCursor = applicationContext.getContentResolver().query(contentSchemeUri, null, null, null, null);
if (returnCursor == null || !returnCursor.moveToFirst()) {
return null;
}
int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
String fileName = returnCursor.getString(nameIndex);
returnCursor.close();
return fileName;
}
private String getMimeTypeFromFileName(String fileName) {
if (fileName == null || !fileName.contains(".")) {
return null;
}
MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton();
int extIndex = fileName.lastIndexOf('.') + 1;
String extension = fileName.substring(extIndex).toLowerCase();
return mimeTypeMap.getMimeTypeFromExtension(extension);
}
}

View File

@@ -0,0 +1,58 @@
package com.silkimen.cordovahttp;
import org.apache.cordova.CallbackContext;
import org.json.JSONObject;
import java.util.Observer;
public class CordovaObservableCallbackContext {
private CallbackContext callbackContext;
private Integer requestId;
private Observer observer;
public CordovaObservableCallbackContext(CallbackContext callbackContext, Integer requestId) {
this.callbackContext = callbackContext;
this.requestId = requestId;
}
public void success(JSONObject message) {
this.callbackContext.success(message);
this.notifyObserver();
}
public void error(JSONObject message) {
this.callbackContext.error(message);
this.notifyObserver();
}
public Integer getRequestId() {
return this.requestId;
}
public CallbackContext getCallbackContext() {
return callbackContext;
}
public Observer getObserver() {
return observer;
}
protected void notifyObserver() {
if(this.observer != null){
this.observer.update(null, this);
}
}
/**
* Set an observer that is notified, when {@link #success(JSONObject)}
* or {@link #error(JSONObject)} are called.
*
* NOTE the observer is notified with
* <pre>observer.update(null, cordovaObservableCallbackContext)</pre>
* @param observer
*/
public void setObserver(Observer observer) {
this.observer = observer;
}
}

View File

@@ -0,0 +1,124 @@
package com.silkimen.cordovahttp;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import com.silkimen.http.TLSConfiguration;
import org.apache.cordova.CallbackContext;
import android.app.Activity;
import android.util.Log;
import android.content.res.AssetManager;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
class CordovaServerTrust implements Runnable {
private static final String TAG = "Cordova-Plugin-HTTP";
private final TrustManager[] noOpTrustManagers;
private final HostnameVerifier noOpVerifier;
private String mode;
private Activity activity;
private TLSConfiguration tlsConfiguration;
private CallbackContext callbackContext;
public CordovaServerTrust(final String mode, final Activity activity, final TLSConfiguration configContainer,
final CallbackContext callbackContext) {
this.mode = mode;
this.activity = activity;
this.tlsConfiguration = configContainer;
this.callbackContext = callbackContext;
this.noOpTrustManagers = new TrustManager[] { new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
public void checkClientTrusted(X509Certificate[] chain, String authType) {
// intentionally left blank
}
public void checkServerTrusted(X509Certificate[] chain, String authType) {
// intentionally left blank
}
} };
this.noOpVerifier = new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
}
@Override
public void run() {
try {
if ("legacy".equals(this.mode)) {
this.tlsConfiguration.setHostnameVerifier(null);
this.tlsConfiguration.setTrustManagers(null);
} else if ("nocheck".equals(this.mode)) {
this.tlsConfiguration.setHostnameVerifier(this.noOpVerifier);
this.tlsConfiguration.setTrustManagers(this.noOpTrustManagers);
} else if ("pinned".equals(this.mode)) {
this.tlsConfiguration.setHostnameVerifier(null);
this.tlsConfiguration.setTrustManagers(this.getTrustManagers(this.getCertsFromBundle("www/certificates")));
} else {
this.tlsConfiguration.setHostnameVerifier(null);
this.tlsConfiguration.setTrustManagers(this.getTrustManagers(this.getCertsFromKeyStore("AndroidCAStore")));
}
callbackContext.success();
} catch (Exception e) {
Log.e(TAG, "An error occured while configuring SSL cert mode", e);
callbackContext.error("An error occured while configuring SSL cert mode");
}
}
private TrustManager[] getTrustManagers(KeyStore store) throws GeneralSecurityException {
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(store);
return tmf.getTrustManagers();
}
private KeyStore getCertsFromBundle(String path) throws GeneralSecurityException, IOException {
AssetManager assetManager = this.activity.getAssets();
String[] files = assetManager.list(path);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
for (int i = 0; i < files.length; i++) {
int index = files[i].lastIndexOf('.');
if (index == -1 || !files[i].substring(index).equals(".cer")) {
continue;
}
keyStore.setCertificateEntry("CA" + i, cf.generateCertificate(assetManager.open(path + "/" + files[i])));
}
return keyStore;
}
private KeyStore getCertsFromKeyStore(String storeType) throws GeneralSecurityException, IOException {
KeyStore store = KeyStore.getInstance(storeType);
store.load(null);
return store;
}
}

View File

@@ -0,0 +1,55 @@
package com.silkimen.http;
import java.nio.ByteBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CodingErrorAction;
import java.nio.charset.MalformedInputException;
public class HttpBodyDecoder {
private static final String[] ACCEPTED_CHARSETS = new String[] { "UTF-8", "ISO-8859-1" };
public static String decodeBody(byte[] body, String charsetName)
throws CharacterCodingException, MalformedInputException {
return decodeBody(ByteBuffer.wrap(body), charsetName);
}
public static String decodeBody(ByteBuffer body, String charsetName)
throws CharacterCodingException, MalformedInputException {
if (charsetName == null) {
return tryDecodeByteBuffer(body);
} else {
return decodeByteBuffer(body, charsetName);
}
}
private static String tryDecodeByteBuffer(ByteBuffer buffer)
throws CharacterCodingException, MalformedInputException {
for (int i = 0; i < ACCEPTED_CHARSETS.length - 1; i++) {
try {
return decodeByteBuffer(buffer, ACCEPTED_CHARSETS[i]);
} catch (MalformedInputException e) {
continue;
} catch (CharacterCodingException e) {
continue;
}
}
return decodeBody(buffer, ACCEPTED_CHARSETS[ACCEPTED_CHARSETS.length - 1]);
}
private static String decodeByteBuffer(ByteBuffer buffer, String charsetName)
throws CharacterCodingException, MalformedInputException {
return createCharsetDecoder(charsetName).decode(buffer).toString();
}
private static CharsetDecoder createCharsetDecoder(String charsetName) {
return Charset.forName(charsetName).newDecoder().onMalformedInput(CodingErrorAction.REPORT)
.onUnmappableCharacter(CodingErrorAction.REPORT);
}
}

View File

@@ -0,0 +1,58 @@
package com.silkimen.http;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
public class JsonUtils {
public static HashMap<String, String> getStringMap(JSONObject object) throws JSONException {
HashMap<String, String> map = new HashMap<String, String>();
if (object == null) {
return map;
}
Iterator<?> i = object.keys();
while (i.hasNext()) {
String key = (String) i.next();
map.put(key, object.getString(key));
}
return map;
}
public static HashMap<String, Object> getObjectMap(JSONObject object) throws JSONException {
HashMap<String, Object> map = new HashMap<String, Object>();
if (object == null) {
return map;
}
Iterator<?> i = object.keys();
while (i.hasNext()) {
String key = (String) i.next();
Object value = object.get(key);
if (value instanceof JSONArray) {
map.put(key, getObjectList((JSONArray) value));
} else {
map.put(key, object.get(key));
}
}
return map;
}
public static ArrayList<Object> getObjectList(JSONArray array) throws JSONException {
ArrayList<Object> list = new ArrayList<Object>();
for (int i = 0; i < array.length(); i++) {
list.add(array.get(i));
}
return list;
}
}

View File

@@ -0,0 +1,57 @@
package com.silkimen.http;
import android.content.Context;
import android.security.KeyChain;
import java.net.Socket;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.X509ExtendedKeyManager;
public class KeyChainKeyManager extends X509ExtendedKeyManager {
private final String alias;
private final X509Certificate[] chain;
private final PrivateKey key;
public KeyChainKeyManager(String alias, PrivateKey key, X509Certificate[] chain) {
this.alias = alias;
this.key = key;
this.chain = chain;
}
@Override
public String chooseClientAlias(String[] keyTypes, Principal[] issuers, Socket socket) {
return this.alias;
}
@Override
public X509Certificate[] getCertificateChain(String alias) {
return chain;
}
@Override
public PrivateKey getPrivateKey(String alias) {
return key;
}
@Override
public final String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
// not a client SSLSocket callback
throw new UnsupportedOperationException();
}
@Override
public final String[] getClientAliases(String keyType, Principal[] issuers) {
// not a client SSLSocket callback
throw new UnsupportedOperationException();
}
@Override
public final String[] getServerAliases(String keyType, Principal[] issuers) {
// not a client SSLSocket callback
throw new UnsupportedOperationException();
}
}

View File

@@ -0,0 +1,64 @@
package com.silkimen.http;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import com.silkimen.http.TLSSocketFactory;
public class TLSConfiguration {
private TrustManager[] trustManagers = null;
private KeyManager[] keyManagers = null;
private HostnameVerifier hostnameVerifier = null;
private String[] blacklistedProtocols = {};
private SSLSocketFactory socketFactory;
public void setHostnameVerifier(HostnameVerifier hostnameVerifier) {
this.hostnameVerifier = hostnameVerifier;
}
public void setKeyManagers(KeyManager[] keyManagers) {
this.keyManagers = keyManagers;
this.socketFactory = null;
}
public void setTrustManagers(TrustManager[] trustManagers) {
this.trustManagers = trustManagers;
this.socketFactory = null;
}
public void setBlacklistedProtocols(String[] protocols) {
this.blacklistedProtocols = protocols;
this.socketFactory = null;
}
public HostnameVerifier getHostnameVerifier() {
return this.hostnameVerifier;
}
public SSLSocketFactory getTLSSocketFactory() throws IOException {
if (this.socketFactory != null) {
return this.socketFactory;
}
try {
SSLContext context = SSLContext.getInstance("TLS");
context.init(this.keyManagers, this.trustManagers, new SecureRandom());
this.socketFactory = new TLSSocketFactory(context, this.blacklistedProtocols);
return this.socketFactory;
} catch (GeneralSecurityException e) {
IOException ioException = new IOException("Security exception occured while configuring TLS context");
ioException.initCause(e);
throw ioException;
}
}
}

View File

@@ -0,0 +1,77 @@
package com.silkimen.http;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.stream.Stream;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
public class TLSSocketFactory extends SSLSocketFactory {
private SSLSocketFactory delegate;
private String[] blacklistedProtocols;
public TLSSocketFactory(SSLContext context, String[] blacklistedProtocols) {
this.delegate = context.getSocketFactory();
this.blacklistedProtocols = Arrays.stream(blacklistedProtocols).map(String::trim).toArray(String[]::new);
}
@Override
public String[] getDefaultCipherSuites() {
return delegate.getDefaultCipherSuites();
}
@Override
public String[] getSupportedCipherSuites() {
return delegate.getSupportedCipherSuites();
}
@Override
public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException {
return enableTLSOnSocket(delegate.createSocket(socket, host, port, autoClose));
}
@Override
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
return enableTLSOnSocket(delegate.createSocket(host, port));
}
@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort)
throws IOException, UnknownHostException {
return enableTLSOnSocket(delegate.createSocket(host, port, localHost, localPort));
}
@Override
public Socket createSocket(InetAddress host, int port) throws IOException {
return enableTLSOnSocket(delegate.createSocket(host, port));
}
@Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort)
throws IOException {
return enableTLSOnSocket(delegate.createSocket(address, port, localAddress, localPort));
}
private Socket enableTLSOnSocket(Socket socket) {
if (socket == null || !(socket instanceof SSLSocket)) {
return socket;
}
String[] supported = ((SSLSocket) socket).getSupportedProtocols();
String[] filtered = Arrays.stream(supported).filter(
val -> Arrays.stream(this.blacklistedProtocols).noneMatch(val::equals)
).toArray(String[]::new);
((SSLSocket) socket).setEnabledProtocols(filtered);
return socket;
}
}

View File

@@ -1,319 +0,0 @@
/**
* A HTTP plugin for Cordova / Phonegap
*/
package com.synconset.cordovahttp;
import org.apache.cordova.CallbackContext;
import org.json.JSONException;
import org.json.JSONArray;
import org.json.JSONObject;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CodingErrorAction;
import java.nio.charset.MalformedInputException;
import javax.net.ssl.SSLHandshakeException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.Iterator;
import android.text.TextUtils;
import com.github.kevinsawicki.http.HttpRequest;
import com.github.kevinsawicki.http.HttpRequest.HttpRequestException;
abstract class CordovaHttp {
protected static final String TAG = "CordovaHTTP";
protected static final String[] ACCEPTED_CHARSETS = new String[] { HttpRequest.CHARSET_UTF8, HttpRequest.CHARSET_LATIN1 };
private static AtomicBoolean sslPinning = new AtomicBoolean(false);
private static AtomicBoolean acceptAllCerts = new AtomicBoolean(false);
private static AtomicBoolean validateDomainName = new AtomicBoolean(true);
private static AtomicBoolean disableRedirect = new AtomicBoolean(false);
private String urlString;
private Object params;
private String serializerName;
private JSONObject headers;
private int timeoutInMilliseconds;
private CallbackContext callbackContext;
public CordovaHttp(String urlString, Object params, JSONObject headers, int timeout, CallbackContext callbackContext) {
this(urlString, params, "default", headers, timeout, callbackContext);
}
public CordovaHttp(String urlString, Object params, String serializerName, JSONObject headers, int timeout, CallbackContext callbackContext) {
this.urlString = urlString;
this.params = params;
this.serializerName = serializerName;
this.headers = headers;
this.timeoutInMilliseconds = timeout;
this.callbackContext = callbackContext;
}
public static void enableSSLPinning(boolean enable) {
sslPinning.set(enable);
if (enable) {
acceptAllCerts.set(false);
}
}
public static void acceptAllCerts(boolean accept) {
acceptAllCerts.set(accept);
if (accept) {
sslPinning.set(false);
}
}
public static void validateDomainName(boolean accept) {
validateDomainName.set(accept);
}
public static void disableRedirect(boolean disable) {
disableRedirect.set(disable);
}
protected String getUrlString() {
return this.urlString;
}
protected Object getParamsObject() {
return this.params;
}
protected String getSerializerName() {
return this.serializerName;
}
protected HashMap<String, Object> getParamsMap() throws JSONException, Exception {
if (this.params instanceof JSONObject) {
return this.getMapFromJSONObject((JSONObject) this.params);
} else {
throw new Exception("unsupported params type, needs to be a JSON object");
}
}
protected JSONObject getHeadersObject() {
return this.headers;
}
protected HashMap<String, String> getHeadersMap() throws JSONException {
return this.getStringMapFromJSONObject(this.headers);
}
protected int getRequestTimeout() {
return this.timeoutInMilliseconds;
}
protected CallbackContext getCallbackContext() {
return this.callbackContext;
}
protected HttpRequest setupSecurity(HttpRequest request) {
if (acceptAllCerts.get()) {
request.trustAllCerts();
}
if (!validateDomainName.get()) {
request.trustAllHosts();
}
if (sslPinning.get()) {
request.pinToCerts();
}
return request;
}
protected HttpRequest setupRedirect(HttpRequest request) {
if (disableRedirect.get()) {
request.followRedirects(false);
}
return request;
}
protected void setupDataSerializer(HttpRequest request) throws JSONException, Exception {
if ("json".equals(this.getSerializerName())) {
request.contentType(request.CONTENT_TYPE_JSON, request.CHARSET_UTF8);
} else if ("utf8".equals(this.getSerializerName())) {
request.contentType("text/plain", request.CHARSET_UTF8);
}
}
protected void respondWithError(int status, String msg) {
try {
JSONObject response = new JSONObject();
response.put("status", status);
response.put("error", msg);
this.callbackContext.error(response);
} catch (JSONException e) {
this.callbackContext.error(msg);
}
}
protected void respondWithError(String msg) {
this.respondWithError(-1, msg);
}
protected void addResponseHeaders(HttpRequest request, JSONObject response) throws JSONException {
Map<String, List<String>> headers = request.headers();
Map<String, String> filteredHeaders = new HashMap<String, String>();
for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
String key = entry.getKey();
List<String> value = entry.getValue();
if ((key != null) && (!value.isEmpty())) {
filteredHeaders.put(key.toLowerCase(), TextUtils.join(", ", value));
}
}
response.put("headers", new JSONObject(filteredHeaders));
}
protected HashMap<String, String> getStringMapFromJSONObject(JSONObject object) throws JSONException {
HashMap<String, String> map = new HashMap<String, String>();
Iterator<?> i = object.keys();
while (i.hasNext()) {
String key = (String)i.next();
map.put(key, object.getString(key));
}
return map;
}
protected ArrayList<Object> getListFromJSONArray(JSONArray array) throws JSONException {
ArrayList<Object> list = new ArrayList<Object>();
for (int i = 0; i < array.length(); i++) {
list.add(array.get(i));
}
return list;
}
protected HashMap<String, Object> getMapFromJSONObject(JSONObject object) throws JSONException {
HashMap<String, Object> map = new HashMap<String, Object>();
Iterator<?> i = object.keys();
while(i.hasNext()) {
String key = (String)i.next();
Object value = object.get(key);
if (value instanceof JSONArray) {
map.put(key, getListFromJSONArray((JSONArray)value));
} else {
map.put(key, object.get(key));
}
}
return map;
}
protected void prepareRequest(HttpRequest request) throws HttpRequestException, JSONException {
this.setupRedirect(request);
this.setupSecurity(request);
request.readTimeout(this.getRequestTimeout());
request.acceptCharset(ACCEPTED_CHARSETS);
request.headers(this.getHeadersMap());
request.uncompress(true);
}
protected void prepareRequestBody(HttpRequest request) throws JSONException, Exception {
if ("json".equals(this.getSerializerName())) {
request.send(this.getParamsObject().toString());
} else if ("utf8".equals(this.getSerializerName())) {
request.send(this.getParamsMap().get("text").toString());
} else {
request.form(this.getParamsMap());
}
}
private CharsetDecoder createCharsetDecoder(final String charsetName) {
return Charset.forName(charsetName).newDecoder()
.onMalformedInput(CodingErrorAction.REPORT)
.onUnmappableCharacter(CodingErrorAction.REPORT);
}
private String decodeBody(AtomicReference<ByteBuffer> rawOutput, String charsetName)
throws CharacterCodingException, MalformedInputException {
if (charsetName == null) {
return tryDecodeByteBuffer(rawOutput);
}
return decodeByteBuffer(rawOutput, charsetName);
}
private String tryDecodeByteBuffer(AtomicReference<ByteBuffer> rawOutput)
throws CharacterCodingException, MalformedInputException {
for (int i = 0; i < ACCEPTED_CHARSETS.length - 1; i++) {
try {
return decodeByteBuffer(rawOutput, ACCEPTED_CHARSETS[i]);
} catch (MalformedInputException e) {
continue;
} catch (CharacterCodingException e) {
continue;
}
}
return decodeBody(rawOutput, ACCEPTED_CHARSETS[ACCEPTED_CHARSETS.length - 1]);
}
private String decodeByteBuffer(AtomicReference<ByteBuffer> rawOutput, String charsetName)
throws CharacterCodingException, MalformedInputException {
return createCharsetDecoder(charsetName).decode(rawOutput.get()).toString();
}
protected void returnResponseObject(HttpRequest request) throws HttpRequestException {
try {
JSONObject response = new JSONObject();
int code = request.code();
AtomicReference<ByteBuffer> rawOutputReference = new AtomicReference<ByteBuffer>();
request.body(rawOutputReference);
response.put("status", code);
response.put("url", request.url().toString());
this.addResponseHeaders(request, response);
if (code >= 200 && code < 300) {
response.put("data", decodeBody(rawOutputReference, request.charset()));
this.getCallbackContext().success(response);
} else {
response.put("error", decodeBody(rawOutputReference, request.charset()));
this.getCallbackContext().error(response);
}
} catch(JSONException e) {
this.respondWithError("There was an error generating the response");
} catch(MalformedInputException e) {
this.respondWithError("Could not decode response data due to malformed data");
} catch(CharacterCodingException e) {
this.respondWithError("Could not decode response data due to invalid or unknown charset encoding");
}
}
protected void handleHttpRequestException(HttpRequestException e) {
if (e.getCause() instanceof UnknownHostException) {
this.respondWithError(0, "The host could not be resolved");
} else if (e.getCause() instanceof SocketTimeoutException) {
this.respondWithError(1, "The request timed out");
} else if (e.getCause() instanceof SSLHandshakeException) {
this.respondWithError("SSL handshake failed");
} else {
this.respondWithError("There was an error with the request: " + e.getMessage());
}
}
}

View File

@@ -1,37 +0,0 @@
/**
* A HTTP plugin for Cordova / Phonegap
*/
package com.synconset.cordovahttp;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import javax.net.ssl.SSLHandshakeException;
import org.apache.cordova.CallbackContext;
import org.json.JSONException;
import org.json.JSONObject;
import com.github.kevinsawicki.http.HttpRequest;
import com.github.kevinsawicki.http.HttpRequest.HttpRequestException;
class CordovaHttpDelete extends CordovaHttp implements Runnable {
public CordovaHttpDelete(String urlString, Object data, JSONObject headers, int timeout, CallbackContext callbackContext) {
super(urlString, data, headers, timeout, callbackContext);
}
@Override
public void run() {
try {
HttpRequest request = HttpRequest.delete(this.getUrlString(), this.getParamsMap(), false);
this.prepareRequest(request);
this.returnResponseObject(request);
} catch (HttpRequestException e) {
this.handleHttpRequestException(e);
} catch (Exception e) {
this.respondWithError(e.getMessage());
}
}
}

View File

@@ -1,66 +0,0 @@
/**
* A HTTP plugin for Cordova / Phonegap
*/
package com.synconset.cordovahttp;
import com.github.kevinsawicki.http.HttpRequest;
import com.github.kevinsawicki.http.HttpRequest.HttpRequestException;
import java.io.File;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.net.URI;
import java.net.URISyntaxException;
import javax.net.ssl.SSLHandshakeException;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.file.FileUtils;
import org.json.JSONException;
import org.json.JSONObject;
class CordovaHttpDownload extends CordovaHttp implements Runnable {
private String filePath;
public CordovaHttpDownload(String urlString, Object params, JSONObject headers, String filePath, int timeout, CallbackContext callbackContext) {
super(urlString, params, headers, timeout, callbackContext);
this.filePath = filePath;
}
@Override
public void run() {
try {
HttpRequest request = HttpRequest.get(this.getUrlString(), this.getParamsMap(), true);
this.prepareRequest(request);
JSONObject response = new JSONObject();
int code = request.code();
response.put("status", code);
response.put("url", request.url().toString());
this.addResponseHeaders(request, response);
if (code >= 200 && code < 300) {
URI uri = new URI(filePath);
File file = new File(uri);
request.receive(file);
JSONObject fileEntry = FileUtils.getFilePlugin().getEntryForFile(file);
response.put("file", fileEntry);
this.getCallbackContext().success(response);
} else {
response.put("error", "There was an error downloading the file");
this.getCallbackContext().error(response);
}
} catch(URISyntaxException e) {
this.respondWithError("There was an error with the given filePath");
} catch (JSONException e) {
this.respondWithError("There was an error generating the response");
} catch (HttpRequestException e) {
this.handleHttpRequestException(e);
} catch (Exception e) {
this.respondWithError(e.getMessage());
}
}
}

View File

@@ -1,37 +0,0 @@
/**
* A HTTP plugin for Cordova / Phonegap
*/
package com.synconset.cordovahttp;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import javax.net.ssl.SSLHandshakeException;
import org.apache.cordova.CallbackContext;
import org.json.JSONException;
import org.json.JSONObject;
import com.github.kevinsawicki.http.HttpRequest;
import com.github.kevinsawicki.http.HttpRequest.HttpRequestException;
class CordovaHttpGet extends CordovaHttp implements Runnable {
public CordovaHttpGet(String urlString, Object params, JSONObject headers, int timeout, CallbackContext callbackContext) {
super(urlString, params, headers, timeout, callbackContext);
}
@Override
public void run() {
try {
HttpRequest request = HttpRequest.get(this.getUrlString(), this.getParamsMap(), false);
this.prepareRequest(request);
this.returnResponseObject(request);
} catch (HttpRequestException e) {
this.handleHttpRequestException(e);
} catch (Exception e) {
this.respondWithError(e.getMessage());
}
}
}

View File

@@ -1,36 +0,0 @@
/**
* A HTTP plugin for Cordova / Phonegap
*/
package com.synconset.cordovahttp;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import javax.net.ssl.SSLHandshakeException;
import org.apache.cordova.CallbackContext;
import org.json.JSONException;
import org.json.JSONObject;
import com.github.kevinsawicki.http.HttpRequest;
import com.github.kevinsawicki.http.HttpRequest.HttpRequestException;
class CordovaHttpHead extends CordovaHttp implements Runnable {
public CordovaHttpHead(String urlString, Object params, JSONObject headers, int timeout, CallbackContext callbackContext) {
super(urlString, params, headers, timeout, callbackContext);
}
@Override
public void run() {
try {
HttpRequest request = HttpRequest.head(this.getUrlString(), this.getParamsMap(), true);
this.prepareRequest(request);
this.returnResponseObject(request);
} catch (HttpRequestException e) {
this.handleHttpRequestException(e);
} catch (Exception e) {
this.respondWithError(e.getMessage());
}
}
}

View File

@@ -1,38 +0,0 @@
/**
* A HTTP plugin for Cordova / Phonegap
*/
package com.synconset.cordovahttp;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import org.apache.cordova.CallbackContext;
import org.json.JSONException;
import org.json.JSONObject;
import javax.net.ssl.SSLHandshakeException;
import com.github.kevinsawicki.http.HttpRequest;
import com.github.kevinsawicki.http.HttpRequest.HttpRequestException;
class CordovaHttpPatch extends CordovaHttp implements Runnable {
public CordovaHttpPatch(String urlString, Object params, String serializerName, JSONObject headers, int timeout, CallbackContext callbackContext) {
super(urlString, params, serializerName, headers, timeout, callbackContext);
}
@Override
public void run() {
try {
HttpRequest request = HttpRequest.patch(this.getUrlString());
this.setupDataSerializer(request);
this.prepareRequest(request);
this.prepareRequestBody(request);
this.returnResponseObject(request);
} catch (HttpRequestException e) {
this.handleHttpRequestException(e);
} catch (Exception e) {
this.respondWithError(e.getMessage());
}
}
}

View File

@@ -1,169 +0,0 @@
/**
* A HTTP plugin for Cordova / Phonegap
*/
package com.synconset.cordovahttp;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.CordovaWebView;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.content.res.AssetManager;
import com.github.kevinsawicki.http.HttpRequest;
public class CordovaHttpPlugin extends CordovaPlugin {
private static final String TAG = "CordovaHTTP";
@Override
public void initialize(CordovaInterface cordova, CordovaWebView webView) {
super.initialize(cordova, webView);
}
@Override
public boolean execute(String action, final JSONArray args, final CallbackContext callbackContext) throws JSONException {
if (action.equals("post")) {
String urlString = args.getString(0);
Object params = args.get(1);
String serializerName = args.getString(2);
JSONObject headers = args.getJSONObject(3);
int timeoutInMilliseconds = args.getInt(4) * 1000;
CordovaHttpPost post = new CordovaHttpPost(urlString, params, serializerName, headers, timeoutInMilliseconds, callbackContext);
cordova.getThreadPool().execute(post);
} else if (action.equals("get")) {
String urlString = args.getString(0);
Object params = args.get(1);
JSONObject headers = args.getJSONObject(2);
int timeoutInMilliseconds = args.getInt(3) * 1000;
CordovaHttpGet get = new CordovaHttpGet(urlString, params, headers, timeoutInMilliseconds, callbackContext);
cordova.getThreadPool().execute(get);
} else if (action.equals("put")) {
String urlString = args.getString(0);
Object params = args.get(1);
String serializerName = args.getString(2);
JSONObject headers = args.getJSONObject(3);
int timeoutInMilliseconds = args.getInt(4) * 1000;
CordovaHttpPut put = new CordovaHttpPut(urlString, params, serializerName, headers, timeoutInMilliseconds, callbackContext);
cordova.getThreadPool().execute(put);
} else if (action.equals("patch")) {
String urlString = args.getString(0);
Object params = args.get(1);
String serializerName = args.getString(2);
JSONObject headers = args.getJSONObject(3);
int timeoutInMilliseconds = args.getInt(4) * 1000;
CordovaHttpPatch patch = new CordovaHttpPatch(urlString, params, serializerName, headers, timeoutInMilliseconds, callbackContext);
cordova.getThreadPool().execute(patch);
}
else if (action.equals("delete")) {
String urlString = args.getString(0);
Object params = args.get(1);
JSONObject headers = args.getJSONObject(2);
int timeoutInMilliseconds = args.getInt(3) * 1000;
CordovaHttpDelete delete = new CordovaHttpDelete(urlString, params, headers, timeoutInMilliseconds, callbackContext);
cordova.getThreadPool().execute(delete);
} else if (action.equals("head")) {
String urlString = args.getString(0);
Object params = args.get(1);
JSONObject headers = args.getJSONObject(2);
int timeoutInMilliseconds = args.getInt(3) * 1000;
CordovaHttpHead head = new CordovaHttpHead(urlString, params, headers, timeoutInMilliseconds, callbackContext);
cordova.getThreadPool().execute(head);
} else if (action.equals("enableSSLPinning")) {
try {
boolean enable = args.getBoolean(0);
this.enableSSLPinning(enable);
callbackContext.success();
} catch(Exception e) {
e.printStackTrace();
callbackContext.error("There was an error setting up ssl pinning");
}
} else if (action.equals("acceptAllCerts")) {
boolean accept = args.getBoolean(0);
CordovaHttp.acceptAllCerts(accept);
CordovaHttp.validateDomainName(!accept);
callbackContext.success();
} else if (action.equals("uploadFile")) {
String urlString = args.getString(0);
Object params = args.get(1);
JSONObject headers = args.getJSONObject(2);
String filePath = args.getString(3);
String name = args.getString(4);
int timeoutInMilliseconds = args.getInt(5) * 1000;
CordovaHttpUpload upload = new CordovaHttpUpload(urlString, params, headers, filePath, name, timeoutInMilliseconds, callbackContext);
cordova.getThreadPool().execute(upload);
} else if (action.equals("downloadFile")) {
String urlString = args.getString(0);
Object params = args.get(1);
JSONObject headers = args.getJSONObject(2);
String filePath = args.getString(3);
int timeoutInMilliseconds = args.getInt(4) * 1000;
CordovaHttpDownload download = new CordovaHttpDownload(urlString, params, headers, filePath, timeoutInMilliseconds, callbackContext);
cordova.getThreadPool().execute(download);
} else if (action.equals("disableRedirect")) {
boolean disable = args.getBoolean(0);
CordovaHttp.disableRedirect(disable);
callbackContext.success();
} else {
return false;
}
return true;
}
private void enableSSLPinning(boolean enable) throws GeneralSecurityException, IOException {
if (enable) {
AssetManager assetManager = cordova.getActivity().getAssets();
String[] files = assetManager.list("");
int index;
ArrayList<String> cerFiles = new ArrayList<String>();
for (int i = 0; i < files.length; i++) {
index = files[i].lastIndexOf('.');
if (index != -1) {
if (files[i].substring(index).equals(".cer")) {
cerFiles.add(files[i]);
}
}
}
// scan the www/certificates folder for .cer files as well
files = assetManager.list("www/certificates");
for (int i = 0; i < files.length; i++) {
index = files[i].lastIndexOf('.');
if (index != -1) {
if (files[i].substring(index).equals(".cer")) {
cerFiles.add("www/certificates/" + files[i]);
}
}
}
for (int i = 0; i < cerFiles.size(); i++) {
InputStream in = cordova.getActivity().getAssets().open(cerFiles.get(i));
InputStream caInput = new BufferedInputStream(in);
HttpRequest.addCert(caInput);
}
CordovaHttp.enableSSLPinning(true);
} else {
CordovaHttp.enableSSLPinning(false);
}
}
}

View File

@@ -1,38 +0,0 @@
/**
* A HTTP plugin for Cordova / Phonegap
*/
package com.synconset.cordovahttp;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import org.apache.cordova.CallbackContext;
import org.json.JSONException;
import org.json.JSONObject;
import javax.net.ssl.SSLHandshakeException;
import com.github.kevinsawicki.http.HttpRequest;
import com.github.kevinsawicki.http.HttpRequest.HttpRequestException;
class CordovaHttpPost extends CordovaHttp implements Runnable {
public CordovaHttpPost(String urlString, Object params, String serializerName, JSONObject headers, int timeout, CallbackContext callbackContext) {
super(urlString, params, serializerName, headers, timeout, callbackContext);
}
@Override
public void run() {
try {
HttpRequest request = HttpRequest.post(this.getUrlString());
this.setupDataSerializer(request);
this.prepareRequest(request);
this.prepareRequestBody(request);
this.returnResponseObject(request);
} catch (HttpRequestException e) {
this.handleHttpRequestException(e);
} catch (Exception e) {
this.respondWithError(e.getMessage());
}
}
}

View File

@@ -1,38 +0,0 @@
/**
* A HTTP plugin for Cordova / Phonegap
*/
package com.synconset.cordovahttp;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import org.apache.cordova.CallbackContext;
import org.json.JSONException;
import org.json.JSONObject;
import javax.net.ssl.SSLHandshakeException;
import com.github.kevinsawicki.http.HttpRequest;
import com.github.kevinsawicki.http.HttpRequest.HttpRequestException;
class CordovaHttpPut extends CordovaHttp implements Runnable {
public CordovaHttpPut(String urlString, Object data, String serializerName, JSONObject headers, int timeout, CallbackContext callbackContext) {
super(urlString, data, serializerName, headers, timeout, callbackContext);
}
@Override
public void run() {
try {
HttpRequest request = HttpRequest.put(this.getUrlString());
this.setupDataSerializer(request);
this.prepareRequest(request);
this.prepareRequestBody(request);
this.returnResponseObject(request);
} catch (HttpRequestException e) {
this.handleHttpRequestException(e);
} catch (Exception e) {
this.respondWithError(e.getMessage());
}
}
}

View File

@@ -1,87 +0,0 @@
/**
* A HTTP plugin for Cordova / Phonegap
*/
package com.synconset.cordovahttp;
import java.io.File;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.Set;
import org.apache.cordova.CallbackContext;
import org.json.JSONException;
import org.json.JSONObject;
import javax.net.ssl.SSLHandshakeException;
import android.webkit.MimeTypeMap;
import com.github.kevinsawicki.http.HttpRequest;
import com.github.kevinsawicki.http.HttpRequest.HttpRequestException;
class CordovaHttpUpload extends CordovaHttp implements Runnable {
private String filePath;
private String name;
public CordovaHttpUpload(String urlString, Object params, JSONObject headers, String filePath, String name, int timeout, CallbackContext callbackContext) {
super(urlString, params, headers, timeout, callbackContext);
this.filePath = filePath;
this.name = name;
}
@Override
public void run() {
try {
HttpRequest request = HttpRequest.post(this.getUrlString());
this.prepareRequest(request);
URI uri = new URI(filePath);
int filenameIndex = filePath.lastIndexOf('/');
String filename = filePath.substring(filenameIndex + 1);
int extIndex = filePath.lastIndexOf('.');
String ext = filePath.substring(extIndex + 1);
MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton();
String mimeType = mimeTypeMap.getMimeTypeFromExtension(ext);
Set<?> set = (Set<?>)this.getParamsMap().entrySet();
Iterator<?> i = set.iterator();
while (i.hasNext()) {
Entry<?, ?> e = (Entry<?, ?>)i.next();
String key = (String)e.getKey();
Object value = e.getValue();
if (value instanceof Number) {
request.part(key, (Number)value);
} else if (value instanceof String) {
request.part(key, (String)value);
} else {
this.respondWithError("All parameters must be Numbers or Strings");
return;
}
}
request.part(this.name, filename, mimeType, new File(uri));
this.returnResponseObject(request);
} catch (URISyntaxException e) {
this.respondWithError("There was an error loading the file");
} catch (JSONException e) {
this.respondWithError("There was an error generating the response");
} catch (HttpRequestException e) {
this.handleHttpRequestException(e);
} catch (Exception e) {
this.respondWithError(e.getMessage());
}
}
}

323
src/browser/cordova-http-plugin.js vendored Normal file
View File

@@ -0,0 +1,323 @@
var pluginId = module.id.slice(0, module.id.lastIndexOf('.'));
var cordovaProxy = require('cordova/exec/proxy');
var jsUtil = require(pluginId + '.js-util');
var reqMap = {};
function serializeJsonData(data) {
try {
return JSON.stringify(data);
} catch (err) {
return null;
}
}
function serializePrimitive(key, value) {
if (value === null || value === undefined) {
return encodeURIComponent(key) + '=';
}
return encodeURIComponent(key) + '=' + encodeURIComponent(value);
}
function serializeArray(key, values) {
return values.map(function (value) {
return encodeURIComponent(key) + '[]=' + encodeURIComponent(value);
}).join('&');
}
function serializeParams(params) {
if (params === null) return '';
return Object.keys(params).map(function (key) {
if (jsUtil.getTypeOf(params[key]) === 'Array') {
return serializeArray(key, params[key]);
}
return serializePrimitive(key, params[key]);
}).join('&');
}
function decodeB64(dataString) {
var binaryString = atob(dataString);
var bytes = new Uint8Array(binaryString.length);
for (var i = 0; i < binaryString.length; ++i) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes.buffer;
}
function processMultipartData(data) {
if (!data) return null;
var fd = new FormData();
for (var i = 0; i < data.buffers.length; ++i) {
var buffer = data.buffers[i];
var name = data.names[i];
var fileName = data.fileNames[i];
var type = data.types[i];
if (fileName) {
fd.append(name, new Blob([decodeB64(buffer)], { type: type }), fileName);
} else {
// we assume it's plain text if no filename was given
fd.append(name, atob(buffer));
}
}
return fd;
}
function deserializeResponseHeaders(headers) {
var headerMap = {};
var arr = headers.trim().split(/[\r\n]+/);
arr.forEach(function (line) {
var parts = line.split(': ');
var header = parts.shift().toLowerCase();
var value = parts.join(': ');
headerMap[header] = value;
});
return headerMap;
}
function getResponseData(xhr) {
if (xhr.responseType !== 'text' || jsUtil.getTypeOf(xhr.responseText) !== 'String') {
return xhr.response;
}
return xhr.responseText;
}
function createXhrSuccessObject(xhr) {
return {
url: xhr.responseURL,
status: xhr.status,
data: getResponseData(xhr),
headers: deserializeResponseHeaders(xhr.getAllResponseHeaders())
};
}
function createXhrFailureObject(xhr) {
var obj = {};
obj.headers = xhr.getAllResponseHeaders();
obj.error = getResponseData(xhr);
obj.error = obj.error || 'advanced-http: please check browser console for error messages';
if (xhr.responseURL) obj.url = xhr.responseURL;
if (xhr.status) obj.status = xhr.status;
return obj;
}
function injectRequestIdHandler(reqId, cb) {
return function (response) {
delete reqMap[reqId];
cb(response);
}
}
function getHeaderValue(headers, headerName) {
let result = null;
Object.keys(headers).forEach(function (key) {
if (key.toLowerCase() === headerName.toLowerCase()) {
result = headers[key];
}
});
return result;
}
function setDefaultContentType(headers, contentType) {
if (getHeaderValue(headers, 'Content-Type') === null) {
headers['Content-Type'] = contentType;
}
}
function setHeaders(xhr, headers) {
Object.keys(headers).forEach(function (key) {
if (key.toLowerCase() === 'cookie') return;
xhr.setRequestHeader(key, headers[key]);
});
}
function sendRequest(method, withData, opts, success, failure) {
var data, serializer, headers, timeout, followRedirect, responseType, reqId;
var url = opts[0];
if (withData) {
data = opts[1];
serializer = opts[2];
headers = opts[3];
timeout = opts[4];
followRedirect = opts[5];
responseType = opts[6];
reqId = opts[7];
} else {
headers = opts[1];
timeout = opts[2];
followRedirect = opts[3];
responseType = opts[4];
reqId = opts[5];
}
var onSuccess = injectRequestIdHandler(reqId, success);
var onFail = injectRequestIdHandler(reqId, failure);
var processedData = null;
var xhr = new XMLHttpRequest();
reqMap[reqId] = xhr;
xhr.open(method, url);
if (headers.Cookie && headers.Cookie.length > 0) {
return onFail('advanced-http: custom cookies not supported on browser platform');
}
if (!followRedirect) {
return onFail('advanced-http: disabling follow redirect not supported on browser platform');
}
switch (serializer) {
case 'json':
setDefaultContentType(headers, 'application/json; charset=utf8');
processedData = serializeJsonData(data);
if (processedData === null) {
return onFail('advanced-http: failed serializing data');
}
break;
case 'utf8':
setDefaultContentType(headers, 'text/plain; charset=utf8');
processedData = data.text;
break;
case 'urlencoded':
setDefaultContentType(headers, 'application/x-www-form-urlencoded');
processedData = serializeParams(data);
break;
case 'multipart':
const contentType = getHeaderValue(headers, 'Content-Type');
// intentionally don't set a default content type
// it's set by the browser together with the content disposition string
if (contentType) {
headers['Content-Type'] = contentType;
}
processedData = processMultipartData(data);
break;
case 'raw':
setDefaultContentType(headers, 'application/octet-stream');
processedData = data;
break;
}
// requesting text instead of JSON because it's parsed in the response handler
xhr.responseType = responseType === 'json' ? 'text' : responseType;
xhr.timeout = timeout * 1000;
setHeaders(xhr, headers);
xhr.onerror = function () {
return onFail(createXhrFailureObject(xhr));
};
xhr.onabort = function () {
return onFail({
status: -8,
error: 'Request was aborted',
url: url,
headers: {}
});
};
xhr.ontimeout = function () {
return onFail({
status: -4,
error: 'Request timed out',
url: url,
headers: {}
});
};
xhr.onload = function () {
if (xhr.readyState !== xhr.DONE) return;
if (xhr.status < 200 || xhr.status > 299) {
return onFail(createXhrFailureObject(xhr));
}
return onSuccess(createXhrSuccessObject(xhr));
};
xhr.send(processedData);
}
function abort(opts, success, failure) {
var reqId = opts[0];
var result = false;
var xhr = reqMap[reqId];
if(xhr && xhr.readyState !== xhr.DONE){
xhr.abort();
result = true;
}
success({aborted: result});
}
var browserInterface = {
get: function (success, failure, opts) {
return sendRequest('get', false, opts, success, failure);
},
head: function (success, failure, opts) {
return sendRequest('head', false, opts, success, failure);
},
delete: function (success, failure, opts) {
return sendRequest('delete', false, opts, success, failure);
},
post: function (success, failure, opts) {
return sendRequest('post', true, opts, success, failure);
},
put: function (success, failure, opts) {
return sendRequest('put', true, opts, success, failure);
},
patch: function (success, failure, opts) {
return sendRequest('patch', true, opts, success, failure);
},
abort: function (success, failure, opts) {
return abort(opts, success, failure);
},
uploadFile: function (success, failure, opts) {
return failure('advanced-http: function "uploadFile" not supported on browser platform');
},
uploadFiles: function (success, failure, opts) {
return failure('advanced-http: function "uploadFiles" not supported on browser platform');
},
downloadFile: function (success, failure, opts) {
return failure('advanced-http: function "downloadFile" not supported on browser platform');
},
setServerTrustMode: function (success, failure, opts) {
return failure('advanced-http: function "setServerTrustMode" not supported on browser platform');
},
setClientAuthMode: function (success, failure, opts) {
return failure('advanced-http: function "setClientAuthMode" not supported on browser platform');
}
};
module.exports = browserInterface;
cordovaProxy.add('CordovaHttpPlugin', browserInterface);

View File

@@ -290,6 +290,64 @@ NS_ASSUME_NONNULL_BEGIN
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
/**
Creates and runs an `NSURLSessionDataTask` with a multipart request using given HTTP method.
@param HTTPMethod The HTTP method used to create the request.
@param URLString The URL string used to create the request URL.
@param parameters The parameters to be encoded according to the client request serializer.
@param block A block that takes a single argument and appends data to the HTTP body. The block argument is an object adopting the `AFMultipartFormData` protocol.
@param uploadProgress A block object to be executed when the upload progress is updated. Note this block is called on the session queue, not the main queue.
@param success A block object to be executed when the task finishes successfully. This block has no return value and takes two arguments: the data task, and the response object created by the client response serializer.
@param failure A block object to be executed when the task finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the data task and the error describing the network or parsing error that occurred.
@see -dataTaskWithRequest:uploadProgress:downloadProgress:completionHandler:
*/
- (nullable NSURLSessionDataTask *)uploadTaskWithHTTPMethod:(NSString *)HTTPMethod
URLString:(NSString *)URLString
parameters:(nullable id)parameters
constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block
progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgress
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
/**
Creates and runs an `NSURLSessionDataTask` with given HTTP method.
@param URLString The URL string used to create the request URL.
@param parameters The parameters to be encoded according to the client request serializer.
@param uploadProgress A block object to be executed when the upload progress is updated. Note this block is called on the session queue, not the main queue.
@param success A block object to be executed when the task finishes successfully. This block has no return value and takes two arguments: the data task, and the response object created by the client response serializer.
@param failure A block object to be executed when the task finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the data task and the error describing the network or parsing error that occurred.
@see -dataTaskWithRequest:uploadProgress:downloadProgress:completionHandler:
*/
- (nullable NSURLSessionDataTask *)uploadTaskWithHTTPMethod:(NSString *)HTTPMethod
URLString:(NSString *)URLString
parameters:(nullable id)parameters
progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgress
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
/**
Creates and runs an `NSURLSessionDataTask` with a given HTTP method.
@param HTTPMethod The HTTP method used to create the request.
@param URLString The URL string used to create the request URL.
@param parameters The parameters to be encoded according to the client request serializer.
@param downloadProgress A block object to be executed when the download progress is updated. Note this block is called on the session queue, not the main queue.
@param success A block object to be executed when the task finishes successfully. This block has no return value and takes two arguments: the data task, and the response object created by the client response serializer.
@param failure A block object to be executed when the task finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the data task and the error describing the network or parsing error that occurred.
@see -dataTaskWithRequest:uploadProgress:downloadProgress:completionHandler:
*/
- (nullable NSURLSessionDataTask *)downloadTaskWithHTTPMethod:(NSString *)HTTPMethod
URLString:(NSString *)URLString
parameters:(nullable id)parameters
progress:(nullable void (^)(NSProgress *downloadProgress))downloadProgress
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
@end
NS_ASSUME_NONNULL_END

View File

@@ -184,6 +184,8 @@
{
NSError *serializationError = nil;
NSMutableURLRequest *request = [self.requestSerializer multipartFormRequestWithMethod:@"POST" URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters constructingBodyWithBlock:block error:&serializationError];
[request setHTTPShouldHandleCookies:NO];
if (serializationError) {
if (failure) {
dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
@@ -223,6 +225,44 @@
return dataTask;
}
- (NSURLSessionDataTask *)PUT:(NSString *)URLString
parameters:(id)parameters
constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block
progress:(nullable void (^)(NSProgress * _Nonnull))uploadProgress
success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure
{
NSError *serializationError = nil;
NSMutableURLRequest *request = [self.requestSerializer multipartFormRequestWithMethod:@"POST" URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters constructingBodyWithBlock:block error:&serializationError];
[request setHTTPShouldHandleCookies:NO];
if (serializationError) {
if (failure) {
dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
failure(nil, serializationError);
});
}
return nil;
}
__block NSURLSessionDataTask *task = [self uploadTaskWithStreamedRequest:request progress:uploadProgress completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
if (error) {
if (failure) {
failure(task, error);
}
} else {
if (success) {
success(task, responseObject);
}
}
}];
[task resume];
return task;
}
- (NSURLSessionDataTask *)PATCH:(NSString *)URLString
parameters:(id)parameters
success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
@@ -247,6 +287,73 @@
return dataTask;
}
- (NSURLSessionDataTask *)uploadTaskWithHTTPMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(id)parameters
constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block
progress:(nullable void (^)(NSProgress * _Nonnull))uploadProgress
success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure
{
NSError *serializationError = nil;
NSMutableURLRequest *request = [self.requestSerializer multipartFormRequestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters constructingBodyWithBlock:block error:&serializationError];
[request setHTTPShouldHandleCookies:NO];
if (serializationError) {
if (failure) {
dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
failure(nil, serializationError);
});
}
return nil;
}
__block NSURLSessionDataTask *task = [self uploadTaskWithStreamedRequest:request progress:uploadProgress completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
if (error) {
if (failure) {
failure(task, error);
}
} else {
if (success) {
success(task, responseObject);
}
}
}];
[task resume];
return task;
}
- (NSURLSessionDataTask *)uploadTaskWithHTTPMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(id)parameters
progress:(void (^)(NSProgress * _Nonnull))uploadProgress
success:(void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success
failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure
{
NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:method URLString:URLString parameters:parameters uploadProgress:uploadProgress downloadProgress:nil success:success failure:failure];
[dataTask resume];
return dataTask;
}
- (NSURLSessionDataTask *)downloadTaskWithHTTPMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(id)parameters
progress:(nullable void (^)(NSProgress * _Nonnull))downloadProgress
success:(void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success
failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure
{
NSURLSessionDataTask *task = [self dataTaskWithHTTPMethod:method URLString:URLString parameters:parameters uploadProgress:nil downloadProgress:downloadProgress success:success failure:failure];
[task resume];
return task;
}
- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(id)parameters
@@ -257,6 +364,8 @@
{
NSError *serializationError = nil;
NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
[request setHTTPShouldHandleCookies:NO];
if (serializationError) {
if (failure) {
dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{

View File

@@ -156,16 +156,9 @@ static NSArray * AFPublicKeyTrustChainForServerTrust(SecTrustRef serverTrust) {
@implementation AFSecurityPolicy
+ (NSSet *)certificatesInBundle:(NSBundle *)bundle {
NSArray *paths = [bundle pathsForResourcesOfType:@"cer" inDirectory:@"."];
NSArray *paths = [bundle pathsForResourcesOfType:@"cer" inDirectory:@"www/certificates"];
NSMutableSet *certificates = [NSMutableSet setWithCapacity:[paths count]];
for (NSString *path in paths) {
NSData *certificateData = [NSData dataWithContentsOfFile:path];
[certificates addObject:certificateData];
}
// also add certs from www/certificates
paths = [bundle pathsForResourcesOfType:@"cer" inDirectory:@"www/certificates"];
for (NSString *path in paths) {
NSData *certificateData = [NSData dataWithContentsOfFile:path];
[certificates addObject:certificateData];
@@ -284,13 +277,13 @@ static NSArray * AFPublicKeyTrustChainForServerTrust(SecTrustRef serverTrust) {
// obtain the chain after being validated, which *should* contain the pinned certificate in the last position (if it's the Root CA)
NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust);
for (NSData *trustChainCertificate in [serverCertificates reverseObjectEnumerator]) {
if ([self.pinnedCertificates containsObject:trustChainCertificate]) {
return YES;
}
}
return NO;
}
case AFSSLPinningModePublicKey: {
@@ -307,7 +300,7 @@ static NSArray * AFPublicKeyTrustChainForServerTrust(SecTrustRef serverTrust) {
return trustedPublicKeyCount > 0;
}
}
return NO;
}

View File

@@ -308,4 +308,11 @@ FOUNDATION_EXPORT NSString * const AFNetworkingOperationFailingURLResponseErrorK
FOUNDATION_EXPORT NSString * const AFNetworkingOperationFailingURLResponseDataErrorKey;
/**
`AFNetworkingOperationFailingURLResponseBodyErrorKey`
The corresponding value is an `NSString` containing the decoded error message.
*/
FOUNDATION_EXPORT NSString * const AFNetworkingOperationFailingURLResponseBodyErrorKey;
NS_ASSUME_NONNULL_END

View File

@@ -34,6 +34,7 @@
NSString * const AFURLResponseSerializationErrorDomain = @"com.alamofire.error.serialization.response";
NSString * const AFNetworkingOperationFailingURLResponseErrorKey = @"com.alamofire.serialization.response.error.response";
NSString * const AFNetworkingOperationFailingURLResponseDataErrorKey = @"com.alamofire.serialization.response.error.data";
NSString * const AFNetworkingOperationFailingURLResponseBodyErrorKey = @"com.alamofire.serialization.response.error.body";
static NSError * AFErrorWithUnderlyingError(NSError *error, NSError *underlyingError) {
if (!error) {
@@ -525,7 +526,7 @@ static NSLock* imageLock = nil;
dispatch_once(&onceToken, ^{
imageLock = [[NSLock alloc] init];
});
[imageLock lock];
image = [UIImage imageWithData:data];
[imageLock unlock];
@@ -539,7 +540,7 @@ static UIImage * AFImageWithDataAtScale(NSData *data, CGFloat scale) {
if (image.images) {
return image;
}
return [[UIImage alloc] initWithCGImage:[image CGImage] scale:scale orientation:image.imageOrientation];
}

View File

@@ -392,11 +392,11 @@ static NSString * const AFNSURLSessionTaskDidSuspendNotification = @"com.alamofi
- On iOS 7, `__NSCFLocalSessionTask` and `__NSCFURLSessionTask` are the only two classes that have their own implementations of `resume` and `suspend`, and `__NSCFLocalSessionTask` DOES NOT CALL SUPER. This means both classes need to be swizzled.
- On iOS 8, `NSURLSessionTask` is the only class that implements `resume` and `suspend`. This means this is the only class that needs to be swizzled.
- Because `NSURLSessionTask` is not involved in the class hierarchy for every version of iOS, its easier to add the swizzled methods to a dummy class and manage them there.
Some Assumptions:
- No implementations of `resume` or `suspend` call super. If this were to change in a future version of iOS, we'd need to handle it.
- No background task classes override `resume` or `suspend`
The current solution:
1) Grab an instance of `__NSCFLocalDataTask` by asking an instance of `NSURLSession` for a data task.
2) Grab a pointer to the original implementation of `af_resume`
@@ -415,7 +415,7 @@ static NSString * const AFNSURLSessionTaskDidSuspendNotification = @"com.alamofi
#pragma clang diagnostic pop
IMP originalAFResumeIMP = method_getImplementation(class_getInstanceMethod([self class], @selector(af_resume)));
Class currentClass = [localDataTask class];
while (class_getInstanceMethod(currentClass, @selector(resume))) {
Class superClass = [currentClass superclass];
IMP classResumeIMP = method_getImplementation(class_getInstanceMethod(currentClass, @selector(resume)));
@@ -426,7 +426,7 @@ static NSString * const AFNSURLSessionTaskDidSuspendNotification = @"com.alamofi
}
currentClass = [currentClass superclass];
}
[localDataTask cancel];
[session finishTasksAndInvalidate];
}
@@ -454,7 +454,7 @@ static NSString * const AFNSURLSessionTaskDidSuspendNotification = @"com.alamofi
NSAssert([self respondsToSelector:@selector(state)], @"Does not respond to state");
NSURLSessionTaskState state = [self state];
[self af_resume];
if (state != NSURLSessionTaskStateRunning) {
[[NSNotificationCenter defaultCenter] postNotificationName:AFNSURLSessionTaskDidResumeNotification object:self];
}
@@ -464,7 +464,7 @@ static NSString * const AFNSURLSessionTaskDidSuspendNotification = @"com.alamofi
NSAssert([self respondsToSelector:@selector(state)], @"Does not respond to state");
NSURLSessionTaskState state = [self state];
[self af_suspend];
if (state != NSURLSessionTaskStateSuspended) {
[[NSNotificationCenter defaultCenter] postNotificationName:AFNSURLSessionTaskDidSuspendNotification object:self];
}
@@ -978,7 +978,7 @@ didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
} else {
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
disposition = NSURLSessionAuthChallengeRejectProtectionSpace;
}
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
@@ -1025,7 +1025,7 @@ didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
disposition = NSURLSessionAuthChallengeUseCredential;
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
} else {
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
disposition = NSURLSessionAuthChallengeRejectProtectionSpace;
}
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;

View File

@@ -0,0 +1,8 @@
#import <Foundation/Foundation.h>
#import "AFURLRequestSerialization.h"
@interface BinaryRequestSerializer : AFHTTPRequestSerializer
+ (instancetype)serializer;
@end

View File

@@ -0,0 +1,53 @@
#import "BinaryRequestSerializer.h"
@implementation BinaryRequestSerializer
+ (instancetype)serializer
{
BinaryRequestSerializer *serializer = [[self alloc] init];
return serializer;
}
#pragma mark - AFURLRequestSerialization
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(id)parameters
error:(NSError *__autoreleasing *)error
{
NSParameterAssert(request);
if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
return [super requestBySerializingRequest:request withParameters:parameters error:error];
}
NSMutableURLRequest *mutableRequest = [request mutableCopy];
[self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
if (![request valueForHTTPHeaderField:field]) {
[mutableRequest setValue:value forHTTPHeaderField:field];
}
}];
if (parameters) {
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
[mutableRequest setValue:@"application/octet-stream" forHTTPHeaderField:@"Content-Type"];
}
[mutableRequest setHTTPBody: parameters];
}
return mutableRequest;
}
#pragma mark - NSSecureCoding
- (instancetype)initWithCoder:(NSCoder *)decoder {
self = [super initWithCoder:decoder];
if (!self) {
return nil;
}
return self;
}
@end

View File

@@ -0,0 +1,8 @@
#import <Foundation/Foundation.h>
#import "AFURLResponseSerialization.h"
@interface BinaryResponseSerializer : AFHTTPResponseSerializer
+ (instancetype)serializer;
@end

View File

@@ -0,0 +1,126 @@
#import "BinaryResponseSerializer.h"
static NSError * AFErrorWithUnderlyingError(NSError *error, NSError *underlyingError) {
if (!error) {
return underlyingError;
}
if (!underlyingError || error.userInfo[NSUnderlyingErrorKey]) {
return error;
}
NSMutableDictionary *mutableUserInfo = [error.userInfo mutableCopy];
mutableUserInfo[NSUnderlyingErrorKey] = underlyingError;
return [[NSError alloc] initWithDomain:error.domain code:error.code userInfo:mutableUserInfo];
}
static BOOL AFErrorOrUnderlyingErrorHasCodeInDomain(NSError *error, NSInteger code, NSString *domain) {
if ([error.domain isEqualToString:domain] && error.code == code) {
return YES;
} else if (error.userInfo[NSUnderlyingErrorKey]) {
return AFErrorOrUnderlyingErrorHasCodeInDomain(error.userInfo[NSUnderlyingErrorKey], code, domain);
}
return NO;
}
@implementation BinaryResponseSerializer
+ (instancetype)serializer {
BinaryResponseSerializer *serializer = [[self alloc] init];
return serializer;
}
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
self.acceptableContentTypes = nil;
return self;
}
- (NSString*)decodeResponseData:(NSData*)rawResponseData withEncoding:(CFStringEncoding)cfEncoding {
NSStringEncoding nsEncoding;
NSString* decoded = nil;
if (cfEncoding != kCFStringEncodingInvalidId) {
nsEncoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding);
}
NSStringEncoding supportedEncodings[6] = {
NSUTF8StringEncoding, NSWindowsCP1252StringEncoding, NSISOLatin1StringEncoding,
NSISOLatin2StringEncoding, NSASCIIStringEncoding, NSUnicodeStringEncoding
};
for (int i = 0; i < sizeof(supportedEncodings) / sizeof(NSStringEncoding) && !decoded; ++i) {
if (cfEncoding == kCFStringEncodingInvalidId || nsEncoding == supportedEncodings[i]) {
decoded = [[NSString alloc] initWithData:rawResponseData encoding:supportedEncodings[i]];
}
}
return decoded;
}
- (CFStringEncoding) getEncoding:(NSURLResponse *)response {
CFStringEncoding encoding = kCFStringEncodingInvalidId;
if (response.textEncodingName) {
encoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName);
}
return encoding;
}
#pragma mark -
- (BOOL)validateResponse:(NSHTTPURLResponse *)response
data:(NSData *)data
error:(NSError * __autoreleasing *)error
{
if (response && [response isKindOfClass:[NSHTTPURLResponse class]]) {
if (self.acceptableStatusCodes && ![self.acceptableStatusCodes containsIndex:(NSUInteger)response.statusCode] && [response URL]) {
NSMutableDictionary *mutableUserInfo = [@{
NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedStringFromTable(@"Request failed: %@ (%ld)", @"AFNetworking", nil), [NSHTTPURLResponse localizedStringForStatusCode:response.statusCode], (long)response.statusCode],
NSURLErrorFailingURLErrorKey: [response URL],
AFNetworkingOperationFailingURLResponseErrorKey: response,
} mutableCopy];
if (data) {
mutableUserInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] = data;
// trying to decode error message in body
mutableUserInfo[AFNetworkingOperationFailingURLResponseBodyErrorKey] = [self decodeResponseData:data withEncoding:[self getEncoding:response]];
}
if (error) {
*error = [NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorBadServerResponse userInfo:mutableUserInfo];
}
return NO;
}
}
return YES;
}
#pragma mark - AFURLResponseSerialization
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error
{
if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) {
if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) {
return nil;
}
}
return [data base64EncodedStringWithOptions:0];
}
@end

View File

@@ -4,15 +4,17 @@
@interface CordovaHttpPlugin : CDVPlugin
- (void)enableSSLPinning:(CDVInvokedUrlCommand*)command;
- (void)acceptAllCerts:(CDVInvokedUrlCommand*)command;
- (void)disableRedirect:(CDVInvokedUrlCommand*)command;
- (void)setServerTrustMode:(CDVInvokedUrlCommand*)command;
- (void)setClientAuthMode:(CDVInvokedUrlCommand*)command;
- (void)post:(CDVInvokedUrlCommand*)command;
- (void)get:(CDVInvokedUrlCommand*)command;
- (void)put:(CDVInvokedUrlCommand*)command;
- (void)patch:(CDVInvokedUrlCommand*)command;
- (void)get:(CDVInvokedUrlCommand*)command;
- (void)delete:(CDVInvokedUrlCommand*)command;
- (void)uploadFile:(CDVInvokedUrlCommand*)command;
- (void)head:(CDVInvokedUrlCommand*)command;
- (void)options:(CDVInvokedUrlCommand*)command;
- (void)uploadFiles:(CDVInvokedUrlCommand*)command;
- (void)downloadFile:(CDVInvokedUrlCommand*)command;
- (void)abort:(CDVInvokedUrlCommand*)command;
@end

View File

@@ -1,29 +1,43 @@
#import "CordovaHttpPlugin.h"
#import "CDVFile.h"
#import "BinaryRequestSerializer.h"
#import "BinaryResponseSerializer.h"
#import "TextResponseSerializer.h"
#import "TextRequestSerializer.h"
#import "AFHTTPSessionManager.h"
#import "SDNetworkActivityIndicator.h"
@interface CordovaHttpPlugin()
- (void)addRequest:(NSNumber*)reqId forTask:(NSURLSessionDataTask*)task;
- (void)removeRequest:(NSNumber*)reqId;
- (void)setRequestHeaders:(NSDictionary*)headers forManager:(AFHTTPSessionManager*)manager;
- (void)handleSuccess:(NSMutableDictionary*)dictionary withResponse:(NSHTTPURLResponse*)response andData:(id)data;
- (void)handleError:(NSMutableDictionary*)dictionary withResponse:(NSHTTPURLResponse*)response error:(NSError*)error;
- (NSNumber*)getStatusCode:(NSError*) error;
- (NSMutableDictionary*)copyHeaderFields:(NSDictionary*)headerFields;
- (void)setTimeout:(NSTimeInterval)timeout forManager:(AFHTTPSessionManager*)manager;
- (void)setRedirect:(AFHTTPSessionManager*)manager;
- (void)setRedirect:(bool)redirect forManager:(AFHTTPSessionManager*)manager;
@end
@implementation CordovaHttpPlugin {
AFSecurityPolicy *securityPolicy;
bool redirect;
NSURLCredential *x509Credential;
NSMutableDictionary *reqDict;
}
- (void)pluginInitialize {
securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeNone];
redirect = true;
reqDict = [NSMutableDictionary dictionary];
}
- (void)addRequest:(NSNumber*)reqId forTask:(NSURLSessionDataTask*)task {
[reqDict setObject:task forKey:reqId];
}
- (void)removeRequest:(NSNumber*)reqId {
[reqDict removeObjectForKey:reqId];
}
- (void)setRequestSerializer:(NSString*)serializerName forManager:(AFHTTPSessionManager*)manager {
@@ -31,20 +45,51 @@
manager.requestSerializer = [AFJSONRequestSerializer serializer];
} else if ([serializerName isEqualToString:@"utf8"]) {
manager.requestSerializer = [TextRequestSerializer serializer];
} else if ([serializerName isEqualToString:@"raw"]) {
manager.requestSerializer = [BinaryRequestSerializer serializer];
} else {
manager.requestSerializer = [AFHTTPRequestSerializer serializer];
}
}
- (void)setupAuthChallengeBlock:(AFHTTPSessionManager*)manager {
[manager setSessionDidReceiveAuthenticationChallengeBlock:^NSURLSessionAuthChallengeDisposition(
NSURLSession * _Nonnull session,
NSURLAuthenticationChallenge * _Nonnull challenge,
NSURLCredential * _Nullable __autoreleasing * _Nullable credential
) {
if ([challenge.protectionSpace.authenticationMethod isEqualToString: NSURLAuthenticationMethodServerTrust]) {
*credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
if (![self->securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
return NSURLSessionAuthChallengeRejectProtectionSpace;
}
if (credential) {
return NSURLSessionAuthChallengeUseCredential;
}
}
if ([challenge.protectionSpace.authenticationMethod isEqualToString: NSURLAuthenticationMethodClientCertificate] && self->x509Credential) {
*credential = self->x509Credential;
return NSURLSessionAuthChallengeUseCredential;
}
return NSURLSessionAuthChallengePerformDefaultHandling;
}];
}
- (void)setRequestHeaders:(NSDictionary*)headers forManager:(AFHTTPSessionManager*)manager {
[headers enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
[manager.requestSerializer setValue:obj forHTTPHeaderField:key];
}];
}
- (void)setRedirect:(AFHTTPSessionManager*)manager {
[manager setTaskWillPerformHTTPRedirectionBlock:^NSURLRequest * _Nonnull(NSURLSession * _Nonnull session, NSURLSessionTask * _Nonnull task, NSURLResponse * _Nonnull response, NSURLRequest * _Nonnull request) {
if (redirect) {
- (void)setRedirect:(bool)followRedirect forManager:(AFHTTPSessionManager*)manager {
[manager setTaskWillPerformHTTPRedirectionBlock:^NSURLRequest * _Nonnull(NSURLSession * _Nonnull session,
NSURLSessionTask * _Nonnull task, NSURLResponse * _Nonnull response, NSURLRequest * _Nonnull request) {
if (followRedirect) {
return request;
} else {
return nil;
@@ -52,6 +97,19 @@
}];
}
- (void)setTimeout:(NSTimeInterval)timeout forManager:(AFHTTPSessionManager*)manager {
[manager.requestSerializer setTimeoutInterval:timeout];
}
- (void)setResponseSerializer:(NSString*)responseType forManager:(AFHTTPSessionManager*)manager {
if ([responseType isEqualToString: @"text"] || [responseType isEqualToString: @"json"]) {
manager.responseSerializer = [TextResponseSerializer serializer];
} else {
manager.responseSerializer = [BinaryResponseSerializer serializer];
}
}
- (void)handleSuccess:(NSMutableDictionary*)dictionary withResponse:(NSHTTPURLResponse*)response andData:(id)data {
if (response != nil) {
[dictionary setValue:response.URL.absoluteString forKey:@"url"];
@@ -65,14 +123,21 @@
}
- (void)handleError:(NSMutableDictionary*)dictionary withResponse:(NSHTTPURLResponse*)response error:(NSError*)error {
bool aborted = error.code == NSURLErrorCancelled;
if(aborted){
[dictionary setObject:[NSNumber numberWithInt:-8] forKey:@"status"];
[dictionary setObject:@"Request was aborted" forKey:@"error"];
}
if (response != nil) {
[dictionary setValue:response.URL.absoluteString forKey:@"url"];
[dictionary setObject:[NSNumber numberWithInt:(int)response.statusCode] forKey:@"status"];
[dictionary setObject:[self copyHeaderFields:response.allHeaderFields] forKey:@"headers"];
if (error.userInfo[AFNetworkingOperationFailingURLResponseBodyKey]) {
[dictionary setObject:error.userInfo[AFNetworkingOperationFailingURLResponseBodyKey] forKey:@"error"];
if(!aborted){
[dictionary setObject:[NSNumber numberWithInt:(int)response.statusCode] forKey:@"status"];
if (error.userInfo[AFNetworkingOperationFailingURLResponseBodyErrorKey]) {
[dictionary setObject:error.userInfo[AFNetworkingOperationFailingURLResponseBodyErrorKey] forKey:@"error"];
}
}
} else {
} else if(!aborted) {
[dictionary setObject:[self getStatusCode:error] forKey:@"status"];
[dictionary setObject:[error localizedDescription] forKey:@"error"];
}
@@ -93,16 +158,23 @@
switch ([error code]) {
case -1001:
// timeout
return [NSNumber numberWithInt:1];
return [NSNumber numberWithInt:-4];
case -1002:
// unsupported URL
return [NSNumber numberWithInt:2];
return [NSNumber numberWithInt:-5];
case -1003:
// server not found
return [NSNumber numberWithInt:0];
return [NSNumber numberWithInt:-3];
case -1009:
// no connection
return [NSNumber numberWithInt:3];
return [NSNumber numberWithInt:-6];
case -1200: // secure connection failed
case -1201: // certificate has bad date
case -1202: // certificate untrusted
case -1203: // certificate has unknown root
case -1204: // certificate is not yet valid
// configuring SSL failed
return [NSNumber numberWithInt:-2];
default:
return [NSNumber numberWithInt:-1];
}
@@ -120,344 +192,349 @@
return headerFieldsCopy;
}
- (void)setTimeout:(NSTimeInterval)timeout forManager:(AFHTTPSessionManager*)manager {
[manager.requestSerializer setTimeoutInterval:timeout];
- (void)executeRequestWithoutData:(CDVInvokedUrlCommand*)command withMethod:(NSString*) method {
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
NSString *url = [command.arguments objectAtIndex:0];
NSDictionary *headers = [command.arguments objectAtIndex:1];
NSTimeInterval timeoutInSeconds = [[command.arguments objectAtIndex:2] doubleValue];
bool followRedirect = [[command.arguments objectAtIndex:3] boolValue];
NSString *responseType = [command.arguments objectAtIndex:4];
NSNumber *reqId = [command.arguments objectAtIndex:5];
[self setRequestSerializer: @"default" forManager: manager];
[self setupAuthChallengeBlock: manager];
[self setRequestHeaders: headers forManager: manager];
[self setTimeout:timeoutInSeconds forManager:manager];
[self setRedirect:followRedirect forManager:manager];
[self setResponseSerializer:responseType forManager:manager];
CordovaHttpPlugin* __weak weakSelf = self;
[[SDNetworkActivityIndicator sharedActivityIndicator] startActivity];
@try {
void (^onSuccess)(NSURLSessionTask *, id) = ^(NSURLSessionTask *task, id responseObject) {
[weakSelf removeRequest:reqId];
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
// no 'body' for HEAD request, omitting 'data'
if ([method isEqualToString:@"HEAD"]) {
[self handleSuccess:dictionary withResponse:(NSHTTPURLResponse*)task.response andData:nil];
} else {
[self handleSuccess:dictionary withResponse:(NSHTTPURLResponse*)task.response andData:responseObject];
}
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary];
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
[manager invalidateSessionCancelingTasks:YES];
};
void (^onFailure)(NSURLSessionTask *, NSError *) = ^(NSURLSessionTask *task, NSError *error) {
[weakSelf removeRequest:reqId];
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[self handleError:dictionary withResponse:(NSHTTPURLResponse*)task.response error:error];
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary];
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
[manager invalidateSessionCancelingTasks:YES];
};
NSURLSessionDataTask *task = [manager downloadTaskWithHTTPMethod:method URLString:url parameters:nil progress:nil success:onSuccess failure:onFailure];
[self addRequest:reqId forTask:task];
}
@catch (NSException *exception) {
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
[self handleException:exception withCommand:command];
}
}
- (void)enableSSLPinning:(CDVInvokedUrlCommand*)command {
bool enable = [[command.arguments objectAtIndex:0] boolValue];
- (void)executeRequestWithData:(CDVInvokedUrlCommand*)command withMethod:(NSString*)method {
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
if (enable) {
securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
} else {
NSString *url = [command.arguments objectAtIndex:0];
NSDictionary *data = [command.arguments objectAtIndex:1];
NSString *serializerName = [command.arguments objectAtIndex:2];
NSDictionary *headers = [command.arguments objectAtIndex:3];
NSTimeInterval timeoutInSeconds = [[command.arguments objectAtIndex:4] doubleValue];
bool followRedirect = [[command.arguments objectAtIndex:5] boolValue];
NSString *responseType = [command.arguments objectAtIndex:6];
NSNumber *reqId = [command.arguments objectAtIndex:7];
[self setRequestSerializer: serializerName forManager: manager];
[self setupAuthChallengeBlock: manager];
[self setRequestHeaders: headers forManager: manager];
[self setTimeout:timeoutInSeconds forManager:manager];
[self setRedirect:followRedirect forManager:manager];
[self setResponseSerializer:responseType forManager:manager];
CordovaHttpPlugin* __weak weakSelf = self;
[[SDNetworkActivityIndicator sharedActivityIndicator] startActivity];
@try {
void (^constructBody)(id<AFMultipartFormData>) = ^(id<AFMultipartFormData> formData) {
NSArray *buffers = [data mutableArrayValueForKey:@"buffers"];
NSArray *fileNames = [data mutableArrayValueForKey:@"fileNames"];
NSArray *names = [data mutableArrayValueForKey:@"names"];
NSArray *types = [data mutableArrayValueForKey:@"types"];
NSError *error;
for (int i = 0; i < [buffers count]; ++i) {
NSData *decodedBuffer = [[NSData alloc] initWithBase64EncodedString:[buffers objectAtIndex:i] options:0];
NSString *fileName = [fileNames objectAtIndex:i];
NSString *partName = [names objectAtIndex:i];
NSString *partType = [types objectAtIndex:i];
if (![fileName isEqual:[NSNull null]]) {
[formData appendPartWithFileData:decodedBuffer name:partName fileName:fileName mimeType:partType];
} else {
[formData appendPartWithFormData:decodedBuffer name:[names objectAtIndex:i]];
}
}
if (error) {
[weakSelf removeRequest:reqId];
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[dictionary setObject:[NSNumber numberWithInt:400] forKey:@"status"];
[dictionary setObject:@"Could not add part to multipart request body." forKey:@"error"];
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary];
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
return;
}
};
void (^onSuccess)(NSURLSessionTask *, id) = ^(NSURLSessionTask *task, id responseObject) {
[weakSelf removeRequest:reqId];
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[self handleSuccess:dictionary withResponse:(NSHTTPURLResponse*)task.response andData:responseObject];
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary];
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
[manager invalidateSessionCancelingTasks:YES];
};
void (^onFailure)(NSURLSessionTask *, NSError *) = ^(NSURLSessionTask *task, NSError *error) {
[weakSelf removeRequest:reqId];
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[self handleError:dictionary withResponse:(NSHTTPURLResponse*)task.response error:error];
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary];
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
[manager invalidateSessionCancelingTasks:YES];
};
NSURLSessionDataTask *task;
if ([serializerName isEqualToString:@"multipart"]) {
task = [manager uploadTaskWithHTTPMethod:method URLString:url parameters:nil constructingBodyWithBlock:constructBody progress:nil success:onSuccess failure:onFailure];
} else {
task = [manager uploadTaskWithHTTPMethod:method URLString:url parameters:data progress:nil success:onSuccess failure:onFailure];
}
[self addRequest:reqId forTask:task];
}
@catch (NSException *exception) {
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
[self handleException:exception withCommand:command];
}
}
- (void)setServerTrustMode:(CDVInvokedUrlCommand*)command {
NSString *certMode = [command.arguments objectAtIndex:0];
if ([certMode isEqualToString: @"default"] || [certMode isEqualToString: @"legacy"]) {
securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeNone];
securityPolicy.allowInvalidCertificates = NO;
securityPolicy.validatesDomainName = YES;
} else if ([certMode isEqualToString: @"nocheck"]) {
securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeNone];
securityPolicy.allowInvalidCertificates = YES;
securityPolicy.validatesDomainName = NO;
} else if ([certMode isEqualToString: @"pinned"]) {
securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
securityPolicy.allowInvalidCertificates = NO;
securityPolicy.validatesDomainName = YES;
}
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
- (void)disableRedirect:(CDVInvokedUrlCommand*)command {
CDVPluginResult* pluginResult = nil;
bool disable = [[command.arguments objectAtIndex:0] boolValue];
- (void)setClientAuthMode:(CDVInvokedUrlCommand*)command {
CDVPluginResult* pluginResult;
NSString *mode = [command.arguments objectAtIndex:0];
redirect = !disable;
if ([mode isEqualToString:@"none"]) {
x509Credential = nil;
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
}
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
if ([mode isEqualToString:@"systemstore"]) {
NSString *alias = [command.arguments objectAtIndex:1];
- (void)acceptAllCerts:(CDVInvokedUrlCommand*)command {
CDVPluginResult* pluginResult = nil;
bool allow = [[command.arguments objectAtIndex:0] boolValue];
// TODO
securityPolicy.allowInvalidCertificates = allow;
securityPolicy.validatesDomainName = !allow;
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"mode 'systemstore' is not supported on iOS"];
}
if ([mode isEqualToString:@"buffer"]) {
CFDataRef container = (__bridge CFDataRef) [command.arguments objectAtIndex:2];
CFStringRef password = (__bridge CFStringRef) [command.arguments objectAtIndex:3];
const void *keys[] = { kSecImportExportPassphrase };
const void *values[] = { password };
CFDictionaryRef options = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);
CFArrayRef items;
OSStatus securityError = SecPKCS12Import(container, options, &items);
CFRelease(options);
if (securityError != noErr) {
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR];
} else {
CFDictionaryRef identityDict = CFArrayGetValueAtIndex(items, 0);
SecIdentityRef identity = (SecIdentityRef)CFDictionaryGetValue(identityDict, kSecImportItemIdentity);
self->x509Credential = [NSURLCredential credentialWithIdentity:identity certificates: nil persistence:NSURLCredentialPersistenceForSession];
CFRelease(items);
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
}
}
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
- (void)post:(CDVInvokedUrlCommand*)command {
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.securityPolicy = securityPolicy;
NSString *url = [command.arguments objectAtIndex:0];
NSDictionary *parameters = [command.arguments objectAtIndex:1];
NSString *serializerName = [command.arguments objectAtIndex:2];
NSDictionary *headers = [command.arguments objectAtIndex:3];
NSTimeInterval timeoutInSeconds = [[command.arguments objectAtIndex:4] doubleValue];
[self setRequestSerializer: serializerName forManager: manager];
[self setRequestHeaders: headers forManager: manager];
[self setTimeout:timeoutInSeconds forManager:manager];
[self setRedirect: manager];
CordovaHttpPlugin* __weak weakSelf = self;
manager.responseSerializer = [TextResponseSerializer serializer];
@try {
[manager POST:url parameters:parameters progress:nil success:^(NSURLSessionTask *task, id responseObject) {
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[self handleSuccess:dictionary withResponse:(NSHTTPURLResponse*)task.response andData:responseObject];
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary];
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
} failure:^(NSURLSessionTask *task, NSError *error) {
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[self handleError:dictionary withResponse:(NSHTTPURLResponse*)task.response error:error];
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary];
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}];
}
@catch (NSException *exception) {
[self handleException:exception withCommand:command];
}
}
- (void)get:(CDVInvokedUrlCommand*)command {
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.securityPolicy = securityPolicy;
NSString *url = [command.arguments objectAtIndex:0];
NSDictionary *parameters = [command.arguments objectAtIndex:1];
NSDictionary *headers = [command.arguments objectAtIndex:2];
NSTimeInterval timeoutInSeconds = [[command.arguments objectAtIndex:3] doubleValue];
[self setRequestSerializer: @"default" forManager: manager];
[self setRequestHeaders: headers forManager: manager];
[self setTimeout:timeoutInSeconds forManager:manager];
[self setRedirect: manager];
CordovaHttpPlugin* __weak weakSelf = self;
manager.responseSerializer = [TextResponseSerializer serializer];
@try {
[manager GET:url parameters:parameters progress:nil success:^(NSURLSessionTask *task, id responseObject) {
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[self handleSuccess:dictionary withResponse:(NSHTTPURLResponse*)task.response andData:responseObject];
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary];
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
} failure:^(NSURLSessionTask *task, NSError *error) {
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[self handleError:dictionary withResponse:(NSHTTPURLResponse*)task.response error:error];
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary];
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}];
}
@catch (NSException *exception) {
[self handleException:exception withCommand:command];
}
[self executeRequestWithData: command withMethod:@"POST"];
}
- (void)put:(CDVInvokedUrlCommand*)command {
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.securityPolicy = securityPolicy;
NSString *url = [command.arguments objectAtIndex:0];
NSDictionary *parameters = [command.arguments objectAtIndex:1];
NSString *serializerName = [command.arguments objectAtIndex:2];
NSDictionary *headers = [command.arguments objectAtIndex:3];
NSTimeInterval timeoutInSeconds = [[command.arguments objectAtIndex:4] doubleValue];
[self setRequestSerializer: serializerName forManager: manager];
[self setRequestHeaders: headers forManager: manager];
[self setTimeout:timeoutInSeconds forManager:manager];
[self setRedirect: manager];
CordovaHttpPlugin* __weak weakSelf = self;
manager.responseSerializer = [TextResponseSerializer serializer];
@try {
[manager PUT:url parameters:parameters success:^(NSURLSessionTask *task, id responseObject) {
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[self handleSuccess:dictionary withResponse:(NSHTTPURLResponse*)task.response andData:responseObject];
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary];
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
} failure:^(NSURLSessionTask *task, NSError *error) {
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[self handleError:dictionary withResponse:(NSHTTPURLResponse*)task.response error:error];
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary];
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}];
}
@catch (NSException *exception) {
[self handleException:exception withCommand:command];
}
[self executeRequestWithData: command withMethod:@"PUT"];
}
- (void)patch:(CDVInvokedUrlCommand*)command {
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.securityPolicy = securityPolicy;
[self executeRequestWithData: command withMethod:@"PATCH"];
}
NSString *url = [command.arguments objectAtIndex:0];
NSDictionary *parameters = [command.arguments objectAtIndex:1];
NSString *serializerName = [command.arguments objectAtIndex:2];
NSDictionary *headers = [command.arguments objectAtIndex:3];
NSTimeInterval timeoutInSeconds = [[command.arguments objectAtIndex:4] doubleValue];
[self setRequestSerializer: serializerName forManager: manager];
[self setRequestHeaders: headers forManager: manager];
[self setTimeout:timeoutInSeconds forManager:manager];
[self setRedirect: manager];
CordovaHttpPlugin* __weak weakSelf = self;
manager.responseSerializer = [TextResponseSerializer serializer];
@try {
[manager PATCH:url parameters:parameters success:^(NSURLSessionTask *task, id responseObject) {
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[self handleSuccess:dictionary withResponse:(NSHTTPURLResponse*)task.response andData:responseObject];
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary];
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
} failure:^(NSURLSessionTask *task, NSError *error) {
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[self handleError:dictionary withResponse:(NSHTTPURLResponse*)task.response error:error];
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary];
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}];
}
@catch (NSException *exception) {
[self handleException:exception withCommand:command];
}
- (void)get:(CDVInvokedUrlCommand*)command {
[self executeRequestWithoutData: command withMethod:@"GET"];
}
- (void)delete:(CDVInvokedUrlCommand*)command {
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.securityPolicy = securityPolicy;
NSString *url = [command.arguments objectAtIndex:0];
NSDictionary *parameters = [command.arguments objectAtIndex:1];
NSDictionary *headers = [command.arguments objectAtIndex:2];
NSTimeInterval timeoutInSeconds = [[command.arguments objectAtIndex:3] doubleValue];
[self setRequestSerializer: @"default" forManager: manager];
[self setRequestHeaders: headers forManager: manager];
[self setTimeout:timeoutInSeconds forManager:manager];
[self setRedirect: manager];
CordovaHttpPlugin* __weak weakSelf = self;
manager.responseSerializer = [TextResponseSerializer serializer];
@try {
[manager DELETE:url parameters:parameters success:^(NSURLSessionTask *task, id responseObject) {
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[self handleSuccess:dictionary withResponse:(NSHTTPURLResponse*)task.response andData:responseObject];
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary];
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
} failure:^(NSURLSessionTask *task, NSError *error) {
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[self handleError:dictionary withResponse:(NSHTTPURLResponse*)task.response error:error];
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary];
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}];
}
@catch (NSException *exception) {
[self handleException:exception withCommand:command];
}
[self executeRequestWithoutData: command withMethod:@"DELETE"];
}
- (void)head:(CDVInvokedUrlCommand*)command {
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.securityPolicy = securityPolicy;
NSString *url = [command.arguments objectAtIndex:0];
NSDictionary *parameters = [command.arguments objectAtIndex:1];
NSDictionary *headers = [command.arguments objectAtIndex:2];
NSTimeInterval timeoutInSeconds = [[command.arguments objectAtIndex:3] doubleValue];
[self setRequestHeaders: headers forManager: manager];
[self setTimeout:timeoutInSeconds forManager:manager];
[self setRedirect: manager];
CordovaHttpPlugin* __weak weakSelf = self;
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
@try {
[manager HEAD:url parameters:parameters success:^(NSURLSessionTask *task) {
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
// no 'body' for HEAD request, omitting 'data'
[self handleSuccess:dictionary withResponse:(NSHTTPURLResponse*)task.response andData:nil];
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary];
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
} failure:^(NSURLSessionTask *task, NSError *error) {
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[self handleError:dictionary withResponse:(NSHTTPURLResponse*)task.response error:error];
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary];
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}];
}
@catch (NSException *exception) {
[self handleException:exception withCommand:command];
}
[self executeRequestWithoutData: command withMethod:@"HEAD"];
}
- (void)uploadFile:(CDVInvokedUrlCommand*)command {
- (void)options:(CDVInvokedUrlCommand*)command {
[self executeRequestWithoutData: command withMethod:@"OPTIONS"];
}
- (void)uploadFiles:(CDVInvokedUrlCommand*)command {
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.securityPolicy = securityPolicy;
NSString *url = [command.arguments objectAtIndex:0];
NSDictionary *parameters = [command.arguments objectAtIndex:1];
NSDictionary *headers = [command.arguments objectAtIndex:2];
NSString *filePath = [command.arguments objectAtIndex: 3];
NSString *name = [command.arguments objectAtIndex: 4];
NSTimeInterval timeoutInSeconds = [[command.arguments objectAtIndex:5] doubleValue];
NSURL *fileURL = [NSURL URLWithString: filePath];
NSDictionary *headers = [command.arguments objectAtIndex:1];
NSArray *filePaths = [command.arguments objectAtIndex: 2];
NSArray *names = [command.arguments objectAtIndex: 3];
NSTimeInterval timeoutInSeconds = [[command.arguments objectAtIndex:4] doubleValue];
bool followRedirect = [[command.arguments objectAtIndex:5] boolValue];
NSString *responseType = [command.arguments objectAtIndex:6];
NSNumber *reqId = [command.arguments objectAtIndex:7];
[self setRequestHeaders: headers forManager: manager];
[self setupAuthChallengeBlock: manager];
[self setTimeout:timeoutInSeconds forManager:manager];
[self setRedirect: manager];
[self setRedirect:followRedirect forManager:manager];
[self setResponseSerializer:responseType forManager:manager];
CordovaHttpPlugin* __weak weakSelf = self;
manager.responseSerializer = [TextResponseSerializer serializer];
[[SDNetworkActivityIndicator sharedActivityIndicator] startActivity];
@try {
[manager POST:url parameters:parameters constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
NSURLSessionDataTask *task = [manager POST:url parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
NSError *error;
[formData appendPartWithFileURL:fileURL name:name error:&error];
for (int i = 0; i < [filePaths count]; i++) {
NSString *filePath = (NSString *) [filePaths objectAtIndex:i];
NSString *uploadName = (NSString *) [names objectAtIndex:i];
NSURL *fileURL = [NSURL URLWithString: filePath];
[formData appendPartWithFileURL:fileURL name:uploadName error:&error];
}
if (error) {
[weakSelf removeRequest:reqId];
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[dictionary setObject:[NSNumber numberWithInt:500] forKey:@"status"];
[dictionary setObject:@"Could not add file to post body." forKey:@"error"];
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary];
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
return;
}
} progress:nil success:^(NSURLSessionTask *task, id responseObject) {
[weakSelf removeRequest:reqId];
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[self handleSuccess:dictionary withResponse:(NSHTTPURLResponse*)task.response andData:responseObject];
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary];
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
} failure:^(NSURLSessionTask *task, NSError *error) {
[weakSelf removeRequest:reqId];
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[self handleError:dictionary withResponse:(NSHTTPURLResponse*)task.response error:error];
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary];
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
}];
[self addRequest:reqId forTask:task];
}
@catch (NSException *exception) {
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
[self handleException:exception withCommand:command];
}
}
- (void)downloadFile:(CDVInvokedUrlCommand*)command {
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.securityPolicy = securityPolicy;
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
NSString *url = [command.arguments objectAtIndex:0];
NSDictionary *parameters = [command.arguments objectAtIndex:1];
NSDictionary *headers = [command.arguments objectAtIndex:2];
NSString *filePath = [command.arguments objectAtIndex: 3];
NSTimeInterval timeoutInSeconds = [[command.arguments objectAtIndex:4] doubleValue];
NSDictionary *headers = [command.arguments objectAtIndex:1];
NSString *filePath = [command.arguments objectAtIndex: 2];
NSTimeInterval timeoutInSeconds = [[command.arguments objectAtIndex:3] doubleValue];
bool followRedirect = [[command.arguments objectAtIndex:4] boolValue];
NSNumber *reqId = [command.arguments objectAtIndex:5];
[self setRequestHeaders: headers forManager: manager];
[self setupAuthChallengeBlock: manager];
[self setTimeout:timeoutInSeconds forManager:manager];
[self setRedirect: manager];
[self setRedirect:followRedirect forManager:manager];
if ([filePath hasPrefix:@"file://"]) {
filePath = [filePath substringFromIndex:7];
}
CordovaHttpPlugin* __weak weakSelf = self;
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
[[SDNetworkActivityIndicator sharedActivityIndicator] startActivity];
@try {
[manager GET:url parameters:parameters progress:nil success:^(NSURLSessionTask *task, id responseObject) {
NSURLSessionDataTask *task = [manager GET:url parameters:nil progress: nil success:^(NSURLSessionTask *task, id responseObject) {
[weakSelf removeRequest:reqId];
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
@@ -495,6 +572,7 @@
}
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary];
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
return;
}
NSData *data = (NSData *)responseObject;
@@ -504,6 +582,7 @@
[dictionary setObject:@"Could not write the data to the given filePath." forKey:@"error"];
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary];
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
return;
}
@@ -514,18 +593,51 @@
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary];
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
} failure:^(NSURLSessionTask *task, NSError *error) {
[weakSelf removeRequest:reqId];
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[self handleError:dictionary withResponse:(NSHTTPURLResponse*)task.response error:error];
[dictionary setObject:@"There was an error downloading the file" forKey:@"error"];
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary];
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
}];
[self addRequest:reqId forTask:task];
}
@catch (NSException *exception) {
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
[self handleException:exception withCommand:command];
}
}
- (void)abort:(CDVInvokedUrlCommand*)command {
NSNumber *reqId = [command.arguments objectAtIndex:0];
CDVPluginResult *pluginResult;
bool removed = false;
NSURLSessionDataTask *task = [reqDict objectForKey:reqId];
if(task){
@try{
[task cancel];
removed = true;
} @catch (NSException *exception) {
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[dictionary setValue:exception.userInfo forKey:@"error"];
[dictionary setObject:[NSNumber numberWithInt:-1] forKey:@"status"];
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary];
}
}
if(!pluginResult){
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[dictionary setObject:[NSNumber numberWithBool:removed] forKey:@"aborted"];
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary];
}
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
@end

View File

@@ -0,0 +1,20 @@
Copyright (c) 2010 Olivier Poitrey <rs@dailymotion.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,52 @@
# SDNetworkActivityIndicator
Handle showing / hiding of the iOS network activity indicator to allow multiple concurrent threads to show / hide the indicator such that the indicator remains visible until all the requests have completed and requested the indicator to be hidden.
## Requirements
* iOS 5.0 or later.
* ARC memory management.
## Installation
The easiest way to install it is by copying the following files to your project:
* SDNetworkActivityIndicator.h
* SDNetworkActivityIndicator.m
## Usage
* When you start a network activity (will show the network activity indicator):
[[SDNetworkActivityIndicator sharedActivityIndicator] startActivity];
* When you finish a network activity (will hide the network activity indicator only if the number of calls to `stopActivity` matches the number of calls to `startActivity`):
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
* To hide the network activity indicator regardless of whether all activities have finished (without having to call `stopActivity` for each `startActivity` called):
[[SDNetworkActivityIndicator sharedActivityIndicator] stopAllActivity];
## License
Copyright (c) 2010 Olivier Poitrey <rs@dailymotion.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,18 @@
/*
* This file is part of the SDNetworkActivityIndicator package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import <Foundation/Foundation.h>
@interface SDNetworkActivityIndicator : NSObject
+ (id)sharedActivityIndicator;
- (void)startActivity;
- (void)stopActivity;
- (void)stopAllActivity;
@end

View File

@@ -0,0 +1,70 @@
/*
* This file is part of the SDNetworkActivityIndicator package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "SDNetworkActivityIndicator.h"
@interface SDNetworkActivityIndicator()
{
@private NSUInteger counter;
}
@end
@implementation SDNetworkActivityIndicator
+ (instancetype) sharedActivityIndicator
{
static id _sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedInstance = [[self alloc] init];
});
return _sharedInstance;
}
- (id)init
{
if ((self = [super init]))
{
counter = 0;
}
return self;
}
- (void)startActivity
{
@synchronized(self)
{
counter++;
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
}
}
- (void)stopActivity
{
@synchronized(self)
{
if (counter > 0 && --counter == 0)
{
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
}
}
}
- (void)stopAllActivity
{
@synchronized(self)
{
counter = 0;
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
}
}
@end

View File

@@ -5,6 +5,4 @@
+ (instancetype)serializer;
FOUNDATION_EXPORT NSString * const AFNetworkingOperationFailingURLResponseBodyKey;
@end

View File

@@ -1,8 +1,5 @@
#import "TextResponseSerializer.h"
NSString * const AFNetworkingOperationFailingURLResponseBodyKey = @"com.alamofire.serialization.response.error.body";
NSStringEncoding const SupportedEncodings[6] = { NSUTF8StringEncoding, NSWindowsCP1252StringEncoding, NSISOLatin1StringEncoding, NSISOLatin2StringEncoding, NSASCIIStringEncoding, NSUnicodeStringEncoding };
static NSError * AFErrorWithUnderlyingError(NSError *error, NSError *underlyingError) {
if (!error) {
return underlyingError;
@@ -55,9 +52,14 @@ static BOOL AFErrorOrUnderlyingErrorHasCodeInDomain(NSError *error, NSInteger co
nsEncoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding);
}
for (int i = 0; i < sizeof(SupportedEncodings) / sizeof(NSStringEncoding) && !decoded; ++i) {
if (cfEncoding == kCFStringEncodingInvalidId || nsEncoding == SupportedEncodings[i]) {
decoded = [[NSString alloc] initWithData:rawResponseData encoding:SupportedEncodings[i]];
NSStringEncoding supportedEncodings[6] = {
NSUTF8StringEncoding, NSWindowsCP1252StringEncoding, NSISOLatin1StringEncoding,
NSISOLatin2StringEncoding, NSASCIIStringEncoding, NSUnicodeStringEncoding
};
for (int i = 0; i < sizeof(supportedEncodings) / sizeof(NSStringEncoding) && !decoded; ++i) {
if (cfEncoding == kCFStringEncodingInvalidId || nsEncoding == supportedEncodings[i]) {
decoded = [[NSString alloc] initWithData:rawResponseData encoding:supportedEncodings[i]];
}
}
@@ -94,7 +96,7 @@ static BOOL AFErrorOrUnderlyingErrorHasCodeInDomain(NSError *error, NSInteger co
NSURLErrorFailingURLErrorKey:[response URL],
AFNetworkingOperationFailingURLResponseErrorKey: response,
AFNetworkingOperationFailingURLResponseDataErrorKey: data,
AFNetworkingOperationFailingURLResponseBodyKey: @"Could not decode response data due to invalid or unknown charset encoding",
AFNetworkingOperationFailingURLResponseBodyErrorKey: @"Could not decode response data due to invalid or unknown charset encoding",
} mutableCopy];
validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorBadServerResponse userInfo:mutableUserInfo], validationError);
@@ -108,7 +110,7 @@ static BOOL AFErrorOrUnderlyingErrorHasCodeInDomain(NSError *error, NSInteger co
if (data) {
mutableUserInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] = data;
mutableUserInfo[AFNetworkingOperationFailingURLResponseBodyKey] = *decoded;
mutableUserInfo[AFNetworkingOperationFailingURLResponseBodyErrorKey] = *decoded;
}
validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorBadServerResponse userInfo:mutableUserInfo], validationError);

View File

@@ -1,10 +0,0 @@
const path = require('path');
if (process.env.SAUCE_USERNAME) {
exports.iosTestApp = 'sauce-storage:HttpDemo.app.zip';
exports.androidTestApp = 'sauce-storage:HttpDemo.apk';
} else {
// these paths are relative to working directory
exports.iosTestApp = path.resolve('temp/platforms/ios/build/emulator/HttpDemo.app');
exports.androidTestApp = path.resolve('temp/platforms/android/build/outputs/apk/android-debug.apk');
}

View File

@@ -1,65 +0,0 @@
const local = {
iosDevice: {
browserName: '',
'appium-version': '1.7.1',
platformName: 'iOS',
platformVersion: '10.3',
deviceName: 'iPhone 6',
autoWebview: true,
app: undefined // will be set later
},
iosEmulator: {
browserName: '',
'appium-version': '1.7.1',
platformName: 'iOS',
platformVersion: '11.0',
deviceName: 'iPhone Simulator',
autoWebview: true,
app: undefined // will be set later
},
androidEmulator: {
browserName: '',
'appium-version': '1.7.1',
platformName: 'Android',
platformVersion: '5.1',
deviceName: 'Android Emulator',
autoWebview: true,
app: undefined // will be set later
}
};
const sauce = {
iosDevice: {
browserName: '',
'appium-version': '1.7.1',
platformName: 'iOS',
platformVersion: '10.3',
deviceName: 'iPhone 6',
autoWebview: true,
app: undefined // will be set later
},
iosEmulator: {
browserName: '',
'appium-version': '1.7.1',
platformName: 'iOS',
platformVersion: '10.3',
deviceName: 'iPhone Simulator',
autoWebview: true,
app: undefined // will be set later
},
androidEmulator: {
browserName: '',
'appium-version': '1.7.1',
platformName: 'Android',
platformVersion: '5.1',
deviceName: 'Android Emulator',
autoWebview: true,
app: undefined // will be set later
}
};
if (process.env.SAUCE_USERNAME) {
module.exports = sauce;
} else {
module.exports = local;
}

View File

@@ -1,16 +0,0 @@
const local = {
host: 'localhost',
port: 4723
};
const sauce = {
host: 'ondemand.saucelabs.com',
port: 80,
auth: process.env.SAUCE_USERNAME + ":" + process.env.SAUCE_ACCESS_KEY
};
if (process.env.SAUCE_USERNAME) {
module.exports = sauce;
} else {
module.exports = local;
}

View File

@@ -1,12 +0,0 @@
const wd = require("wd");
require('colors');
const chai = require('chai');
const chaiAsPromised = require('chai-as-promised');
chai.use(chaiAsPromised);
const should = chai.should();
chaiAsPromised.transferPromiseness = wd.transferPromiseness;
exports.should = should;

View File

@@ -1,94 +0,0 @@
require('./helpers/setup');
const wd = require('wd');
const apps = require('./helpers/apps');
const caps = Object.assign({}, require('./helpers/caps'));
const serverConfig = require('./helpers/server');
const testDefinitions = require('../app-test-definitions');
const pkgjson = require('../../package.json');
describe('Advanced HTTP', function() {
const isDevice = process.argv.includes('--device');
const isAndroid = process.argv.includes('--android');
const targetInfo = { isDevice, isAndroid };
let driver = null;
let allPassed = true;
this.timeout(900000);
const getCaps = appName => {
const desiredOs = isAndroid ? 'android' : 'ios';
const desiredCaps = caps[desiredOs + (isDevice ? 'Device' : 'Emulator')];
const desiredApp = apps[desiredOs + appName];
desiredCaps.name = pkgjson.name + ` (${desiredOs})`;
desiredCaps.app = desiredApp;
return desiredCaps;
};
const validateTestIndex = number => driver
.elementById('descriptionLbl')
.text()
.then(text => parseInt(text.match(/(\d+):/)[1], 10))
.should.eventually.become(number, 'Test index is not matching!');
const validateTestTitle = testTitle => driver
.elementById('descriptionLbl')
.text()
.then(text => text.match(/\d+:\ (.*)/)[1])
.should.eventually.become(testTitle, 'Test description is not matching!');
const waitToBeFinished = timeout => new Promise((resolve, reject) => {
const timeoutTimestamp = Date.now() + timeout;
const checkIfFinished = () => driver
.elementById('statusInput')
.getValue()
.then(value => {
if (value === 'finished') {
resolve();
} else if (Date.now() > timeoutTimestamp) {
reject('Test function timed out!');
} else {
setTimeout(checkIfFinished, 500);
}
});
checkIfFinished();
});
const validateResult = testDefinition => driver
.safeExecute('app.lastResult')
.then(result => testDefinition.validationFunc(driver, result, targetInfo));
const clickNext = () => driver
.elementById('nextBtn')
.click()
.sleep(1000);
before(() => {
driver = wd.promiseChainRemote(serverConfig);
require('./helpers/logging').configure(driver);
return driver.init(getCaps('TestApp'));
});
after(() => driver
.quit()
.finally(function () {
if (process.env.SAUCE_USERNAME) {
return driver.sauceJobStatus(allPassed);
}
}));
testDefinitions.tests.forEach((definition, index) => {
it(definition.description, function() {
return clickNext()
.then(() => validateTestIndex(index))
.then(() => validateTestTitle(this.test.title))
.then(() => waitToBeFinished(definition.timeout || 10000))
.then(() => validateResult(definition))
});
});
});

View File

@@ -1,25 +0,0 @@
{
"name": "com.ilkimen.http.demo",
"displayName": "HttpDemo",
"version": "1.0.0",
"description": "A sample Apache Cordova application that demonstrates advanced HTTP plugin.",
"main": "index.js",
"scripts": {
"build": "scripts/build.sh",
"test": "npm run build && scripts/test.sh"
},
"author": "Sefa Ilkimen",
"license": "Apache-2.0",
"dependencies": {
"cordova": "7.0.1",
"cordova-android": "6.2.3",
"cordova-ios": "4.4.0"
},
"cordova": {
"platforms": [
"android",
"ios"
]
},
"devDependencies": {}
}

View File

@@ -1,107 +0,0 @@
const app = {
testIndex: -1,
lastResult: null,
initialize: function() {
document.getElementById('nextBtn').addEventListener('click', app.onNextBtnClick);
},
printResult: function(prefix, content) {
const text = prefix + ': ' + JSON.stringify(content);
document.getElementById('resultTextarea').value += text;
},
reject: function(content) {
document.getElementById('statusInput').value = 'finished';
app.printResult('result - rejected', content);
app.lastResult = {
type: 'rejected',
data: content
};
},
resolve: function(content) {
document.getElementById('statusInput').value = 'finished';
app.printResult('result - resolved', content);
app.lastResult = {
type: 'resolved',
data: content
};
},
throw: function(error) {
document.getElementById('statusInput').value = 'finished';
app.printResult('result - throwed', error.message);
app.lastResult = {
type: 'throwed',
message: error.message
};
},
getResult: function(cb) {
cb(app.lastResult);
},
runTest: function(index) {
const testDefinition = tests[index];
const titleText = app.testIndex + ': ' + testDefinition.description;
const expectedText = 'expected - ' + testDefinition.expected;
document.getElementById('statusInput').value = 'running';
document.getElementById('expectedTextarea').value = expectedText;
document.getElementById('resultTextarea').value = '';
document.getElementById('descriptionLbl').innerText = titleText;
try {
testDefinition.func(app.resolve, app.reject);
} catch (error) {
app.throw(error);
}
},
onBeforeTest: function(testIndex, cb) {
app.lastResult = null;
if (hooks && hooks.onBeforeEachTest) {
return hooks.onBeforeEachTest(function() {
const testDefinition = tests[testIndex];
if (testDefinition.before) {
testDefinition.before(cb);
} else {
cb();
}
});
} else {
cb();
}
},
onFinishedAllTests: function() {
const titleText = 'No more tests';
const expectedText = 'You have run all available tests.';
document.getElementById('expectedTextarea').value = expectedText;
document.getElementById('resultTextarea').value = '';
document.getElementById('descriptionLbl').innerText = titleText;
},
onNextBtnClick: function() {
app.testIndex += 1;
if (app.testIndex < tests.length) {
app.onBeforeTest(app.testIndex, function() {
app.runTest(app.testIndex);
});
} else {
app.onFinishedAllTests();
}
}
};
app.initialize();

View File

@@ -1,472 +0,0 @@
const hooks = {
onBeforeEachTest: function(done) {
cordova.plugin.http.clearCookies();
cordova.plugin.http.acceptAllCerts(false, function() {
cordova.plugin.http.enableSSLPinning(false, done, done);
}, done);
}
};
const helpers = {
acceptAllCerts: function(done) { cordova.plugin.http.acceptAllCerts(true, done, done); },
enableSSLPinning: function(done) { cordova.plugin.http.enableSSLPinning(true, done, done); },
setJsonSerializer: function(done) { done(cordova.plugin.http.setDataSerializer('json')); },
setUtf8StringSerializer: function(done) { done(cordova.plugin.http.setDataSerializer('utf8')); },
setUrlEncodedSerializer: function(done) { done(cordova.plugin.http.setDataSerializer('urlencoded')); },
getWithXhr: function(done, url) {
var xhr = new XMLHttpRequest();
xhr.addEventListener('load', function() {
done(this.responseText);
});
xhr.open('GET', url);
xhr.send();
},
writeToFile: function (done, fileName, content) {
window.resolveLocalFileSystemURL(cordova.file.cacheDirectory, function (directoryEntry) {
directoryEntry.getFile(fileName, { create: true, exclusive: false }, function (fileEntry) {
fileEntry.createWriter(function (fileWriter) {
var blob = new Blob([ content ], { type: 'text/plain' });
fileWriter.onwriteend = done;
fileWriter.onerror = done;
fileWriter.write(blob);
}, done);
}, done);
}, done);
}
};
const tests = [
{
description: 'should reject self signed cert (GET)',
expected: 'rejected: {"status":-1,"error":"cancelled"}',
func: function(resolve, reject) { cordova.plugin.http.get('https://self-signed.badssl.com/', {}, {}, resolve, reject); },
validationFunc: function(driver, result, targetInfo) {
result.type.should.be.equal('rejected');
result.data.should.be.eql({ status: -1, error: targetInfo.isAndroid ? 'SSL handshake failed' : 'cancelled' });
}
},{
description: 'should reject self signed cert (PUT)',
expected: 'rejected: {"status":-1,"error":"cancelled"}',
func: function(resolve, reject) { cordova.plugin.http.put('https://self-signed.badssl.com/', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function(driver, result, targetInfo) {
result.type.should.be.equal('rejected');
result.data.should.be.eql({ status: -1, error: targetInfo.isAndroid ? 'SSL handshake failed' : 'cancelled' });
}
},{
description: 'should reject self signed cert (POST)',
expected: 'rejected: {"status":-1,"error":"cancelled"}',
func: function(resolve, reject) { cordova.plugin.http.post('https://self-signed.badssl.com/', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function(driver, result, targetInfo) {
result.type.should.be.equal('rejected');
result.data.should.be.eql({ status: -1, error: targetInfo.isAndroid ? 'SSL handshake failed' : 'cancelled' });
}
},{
description: 'should reject self signed cert (PATCH)',
expected: 'rejected: {"status":-1,"error":"cancelled"}',
func: function(resolve, reject) { cordova.plugin.http.patch('https://self-signed.badssl.com/', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function(driver, result, targetInfo) {
result.type.should.be.equal('rejected');
result.data.should.be.eql({ status: -1, error: targetInfo.isAndroid ? 'SSL handshake failed' : 'cancelled' });
}
},{
description: 'should reject self signed cert (DELETE)',
expected: 'rejected: {"status":-1,"error":"cancelled"}',
func: function(resolve, reject) { cordova.plugin.http.delete('https://self-signed.badssl.com/', {}, {}, resolve, reject); },
validationFunc: function(driver, result, targetInfo) {
result.type.should.be.equal('rejected');
result.data.should.be.eql({ status: -1, error: targetInfo.isAndroid ? 'SSL handshake failed' : 'cancelled' });
}
},{
description: 'should accept bad cert (GET)',
expected: 'resolved: {"status":200, ...',
before: helpers.acceptAllCerts,
func: function(resolve, reject) { cordova.plugin.http.get('https://self-signed.badssl.com/', {}, {}, resolve, reject); },
validationFunc: function(driver, result) {
result.type.should.be.equal('resolved');
result.data.should.include({ status: 200 });
}
},{
description: 'should accept bad cert (PUT)',
expected: 'rejected: {"status":405, ... // will be rejected because PUT is not allowed',
before: helpers.acceptAllCerts,
func: function(resolve, reject) { cordova.plugin.http.put('https://self-signed.badssl.com/', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function(driver, result) {
result.type.should.be.equal('rejected');
result.data.should.include({ status: 405 });
}
},{
description: 'should accept bad cert (POST)',
expected: 'rejected: {"status":405, ... // will be rejected because POST is not allowed',
before: helpers.acceptAllCerts,
func: function(resolve, reject) { cordova.plugin.http.post('https://self-signed.badssl.com/', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function(driver, result) {
result.type.should.be.equal('rejected');
result.data.should.include({ status: 405 });
}
},{
description: 'should accept bad cert (PATCH)',
expected: 'rejected: {"status":405, ... // will be rejected because PATCH is not allowed',
before: helpers.acceptAllCerts,
func: function(resolve, reject) { cordova.plugin.http.patch('https://self-signed.badssl.com/', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function(driver, result) {
result.type.should.be.equal('rejected');
result.data.should.include({ status: 405 });
}
},{
description: 'should accept bad cert (DELETE)',
expected: 'rejected: {"status":405, ... // will be rejected because DELETE is not allowed',
before: helpers.acceptAllCerts,
func: function(resolve, reject) { cordova.plugin.http.delete('https://self-signed.badssl.com/', {}, {}, resolve, reject); },
validationFunc: function(driver, result) {
result.type.should.be.equal('rejected');
result.data.should.include({ status: 405 });
}
},{
description: 'should fetch data from http://httpbin.org/ (GET)',
expected: 'resolved: {"status":200, ...',
before: helpers.acceptAllCerts,
func: function(resolve, reject) { cordova.plugin.http.get('http://httpbin.org/', {}, {}, resolve, reject); },
validationFunc: function(driver, result) {
result.type.should.be.equal('resolved');
result.data.should.include({ status: 200 });
}
},{
description: 'should send JSON object correctly (POST)',
expected: 'resolved: {"status": 200, "data": "{\\"json\\":\\"test\\": \\"testString\\"}\" ...',
before: helpers.setJsonSerializer,
func: function(resolve, reject) { cordova.plugin.http.post('http://httpbin.org/anything', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function(driver, result) {
result.type.should.be.equal('resolved');
JSON.parse(result.data.data).json.should.eql({ test: 'testString' });
}
},{
description: 'should send JSON object correctly (PUT)',
expected: 'resolved: {"status": 200, "data": "{\\"json\\":\\"test\\": \\"testString\\"}\" ...',
before: helpers.setJsonSerializer,
func: function(resolve, reject) { cordova.plugin.http.put('http://httpbin.org/anything', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function(driver, result) {
result.type.should.be.equal('resolved');
JSON.parse(result.data.data).json.should.eql({ test: 'testString' });
}
},{
description: 'should send JSON object correctly (PATCH)',
expected: 'resolved: {"status": 200, "data": "{\\"json\\":\\"test\\": \\"testString\\"}\" ...',
before: helpers.setJsonSerializer,
func: function(resolve, reject) { cordova.plugin.http.patch('http://httpbin.org/anything', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function(driver, result) {
result.type.should.be.equal('resolved');
JSON.parse(result.data.data).json.should.eql({ test: 'testString' });
}
},{
description: 'should send JSON array correctly (POST) #26',
expected: 'resolved: {"status": 200, "data": "[ 1, 2, 3 ]\" ...',
before: helpers.setJsonSerializer,
func: function(resolve, reject) { cordova.plugin.http.post('http://httpbin.org/anything', [ 1, 2, 3 ], {}, resolve, reject); },
validationFunc: function(driver, result) {
result.type.should.be.equal('resolved');
JSON.parse(result.data.data).json.should.eql([ 1, 2, 3 ]);
}
},{
description: 'should send JSON array correctly (PUT) #26',
expected: 'resolved: {"status": 200, "data": "[ 1, 2, 3 ]\" ...',
before: helpers.setJsonSerializer,
func: function(resolve, reject) { cordova.plugin.http.put('http://httpbin.org/anything', [ 1, 2, 3 ], {}, resolve, reject); },
validationFunc: function(driver, result) {
result.type.should.be.equal('resolved');
JSON.parse(result.data.data).json.should.eql([ 1, 2, 3 ]);
}
},{
description: 'should send JSON array correctly (PATCH) #26',
expected: 'resolved: {"status": 200, "data": "[ 1, 2, 3 ]\" ...',
before: helpers.setJsonSerializer,
func: function(resolve, reject) { cordova.plugin.http.patch('http://httpbin.org/anything', [ 1, 2, 3 ], {}, resolve, reject); },
validationFunc: function(driver, result) {
result.type.should.be.equal('resolved');
result.data.data.should.be.a('string');
JSON.parse(result.data.data).json.should.eql([ 1, 2, 3 ]);
}
},{
description: 'should send url encoded data correctly (POST) #41',
expected: 'resolved: {"status": 200, "data": "{\\"form\\":\\"test\\": \\"testString\\"}\" ...',
before: helpers.setUrlEncodedSerializer,
func: function(resolve, reject) { cordova.plugin.http.post('http://httpbin.org/anything', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function(driver, result) {
result.type.should.be.equal('resolved');
JSON.parse(result.data.data).form.should.eql({ test: 'testString' });
}
},{
description: 'should send url encoded data correctly (PUT)',
expected: 'resolved: {"status": 200, "data": "{\\"form\\":\\"test\\": \\"testString\\"}\" ...',
before: helpers.setUrlEncodedSerializer,
func: function(resolve, reject) { cordova.plugin.http.put('http://httpbin.org/anything', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function(driver, result) {
result.type.should.be.equal('resolved');
JSON.parse(result.data.data).form.should.eql({ test: 'testString' });
}
},{
description: 'should send url encoded data correctly (PATCH)',
expected: 'resolved: {"status": 200, "data": "{\\"form\\":\\"test\\": \\"testString\\"}\" ...',
before: helpers.setUrlEncodedSerializer,
func: function(resolve, reject) { cordova.plugin.http.patch('http://httpbin.org/anything', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function(driver, result) {
result.type.should.be.equal('resolved');
JSON.parse(result.data.data).form.should.eql({ test: 'testString' });
}
},{
description: 'should resolve correct URL after redirect (GET) #33',
expected: 'resolved: {"status": 200, url: "http://httpbin.org/anything", ...',
func: function(resolve, reject) { cordova.plugin.http.get('http://httpbin.org/redirect-to?url=http://httpbin.org/anything', {}, {}, resolve, reject); },
validationFunc: function(driver, result) {
result.type.should.be.equal('resolved');
result.data.url.should.be.equal('http://httpbin.org/anything');
}
},{
description: 'should download a file from given URL to given path in local filesystem',
expected: 'resolved: {"content": "<?xml version=\'1.0\' encoding=\'us-ascii\'?>\\n\\n<!-- A SAMPLE set of slides -->" ...',
func: function(resolve, reject) {
var sourceUrl = 'http://httpbin.org/xml';
var targetPath = cordova.file.cacheDirectory + 'test.xml';
cordova.plugin.http.downloadFile(sourceUrl, {}, {}, targetPath, function(entry) {
helpers.getWithXhr(function(content) {
resolve({
sourceUrl: sourceUrl,
targetPath: targetPath,
fullPath: entry.fullPath,
name: entry.name,
content: content
});
}, targetPath);
}, reject);
},
validationFunc: function(driver, result) {
result.type.should.be.equal('resolved');
result.data.name.should.be.equal('test.xml');
result.data.content.should.be.equal("<?xml version='1.0' encoding='us-ascii'?>\n\n<!-- A SAMPLE set of slides -->\n\n<slideshow \n title=\"Sample Slide Show\"\n date=\"Date of publication\"\n author=\"Yours Truly\"\n >\n\n <!-- TITLE SLIDE -->\n <slide type=\"all\">\n <title>Wake up to WonderWidgets!</title>\n </slide>\n\n <!-- OVERVIEW -->\n <slide type=\"all\">\n <title>Overview</title>\n <item>Why <em>WonderWidgets</em> are great</item>\n <item/>\n <item>Who <em>buys</em> WonderWidgets</item>\n </slide>\n\n</slideshow>");
}
},{
description: 'should upload a file from given path in local filesystem to given URL #27',
expected: 'resolved: {"status": 200, "data": "files": {"test-file.txt": "I am a dummy file. I am used ...',
func: function(resolve, reject) {
var fileName = 'test-file.txt';
var fileContent = 'I am a dummy file. I am used for testing purposes!';
var sourcePath = cordova.file.cacheDirectory + fileName;
var targetUrl = 'http://httpbin.org/post';
helpers.writeToFile(function() {
cordova.plugin.http.uploadFile(targetUrl, {}, {}, sourcePath, fileName, resolve, reject);
}, fileName, fileContent);
},
validationFunc: function(driver, result) {
var fileName = 'test-file.txt';
var fileContent = 'I am a dummy file. I am used for testing purposes!';
result.type.should.be.equal('resolved');
result.data.data.should.be.a('string');
JSON
.parse(result.data.data)
.files[fileName]
.should.be.equal(fileContent);
}
},{
description: 'should encode HTTP array params correctly (GET) #45',
expected: 'resolved: {"status": 200, "data": "{\\"url\\":\\"http://httpbin.org/get?myArray[]=val1&myArray[]=val2&myArray[]=val3\\"}\" ...',
func: function(resolve, reject) {
cordova.plugin.http.get('http://httpbin.org/get', { myArray: [ 'val1', 'val2', 'val3' ], myString: 'testString' }, {}, resolve, reject);
},
validationFunc: function(driver, result) {
result.type.should.be.equal('resolved');
result.data.data.should.be.a('string');
JSON
.parse(result.data.data)
.url
.should.be.equal('http://httpbin.org/get?myArray[]=val1&myArray[]=val2&myArray[]=val3&myString=testString');
}
},{
description: 'should reject non-string values in local header object #54',
expected: 'rejected: {"status": 0, "error": "advanced-http: header values must be strings" ...',
func: function(resolve, reject) {
cordova.plugin.http.get('http://httpbin.org/get', {}, { myTestHeader: 1 }, resolve, reject);
},
validationFunc: function(driver, result) {
result.type.should.be.equal('rejected');
result.data.error.should.be.equal('advanced-http: header values must be strings');
}
},{
description: 'should throw an error while setting non-string value as global header #54',
expected: 'throwed: "advanced-http: header values must be strings"',
func: function(resolve, reject) {
cordova.plugin.http.setHeader('myTestHeader', 2);
},
validationFunc: function(driver, result) {
result.type.should.be.equal('throwed');
result.message.should.be.equal('advanced-http: header values must be strings');
}
},{
description: 'should accept content-type "application/xml" #58',
expected: 'resolved: {"status": 200, ...',
func: function(resolve, reject) {
cordova.plugin.http.get('http://httpbin.org/xml', {}, {}, resolve, reject);
},
validationFunc: function(driver, result) {
result.type.should.be.equal('resolved');
result.data.status.should.be.equal(200);
}
},{
description: 'should send programmatically set cookies correctly (GET)',
expected: 'resolved: {"status": 200, ...',
func: function(resolve, reject) {
cordova.plugin.http.setCookie('http://httpbin.org/get', 'myCookie=myValue');
cordova.plugin.http.setCookie('http://httpbin.org/get', 'mySecondCookie=mySecondValue');
cordova.plugin.http.get('http://httpbin.org/get', {}, {}, resolve, reject);
},
validationFunc: function(driver, result) {
result.type.should.be.equal('resolved');
result.data.data.should.be.a('string');
JSON
.parse(result.data.data)
.headers
.Cookie
.should.be.equal('myCookie=myValue; mySecondCookie=mySecondValue');
}
},{
description: 'should not send any cookies after running "clearCookies" (GET) #59',
expected: 'resolved: {"status": 200, "data": "{\"headers\": {\"Cookie\": \"\"...',
func: function(resolve, reject) {
cordova.plugin.http.setCookie('http://httpbin.org/get', 'myCookie=myValue');
cordova.plugin.http.setCookie('http://httpbin.org/get', 'mySecondCookie=mySecondValue');
cordova.plugin.http.clearCookies();
cordova.plugin.http.get('http://httpbin.org/get', {}, {}, resolve, reject);
},
validationFunc: function(driver, result) {
result.type.should.be.equal('resolved');
result.data.data.should.be.a('string');
JSON
.parse(result.data.data)
.headers
.Cookie
.should.be.equal('');
}
},{
description: 'should send programmatically set cookies correctly (DOWNLOAD) #57',
expected: 'resolved: {"content":{"cookies":{"myCookie":"myValue ...',
func: function(resolve, reject) {
var sourceUrl = 'http://httpbin.org/cookies';
var targetPath = cordova.file.cacheDirectory + 'cookies.json';
cordova.plugin.http.setCookie('http://httpbin.org/get', 'myCookie=myValue');
cordova.plugin.http.setCookie('http://httpbin.org/get', 'mySecondCookie=mySecondValue');
cordova.plugin.http.downloadFile(sourceUrl, {}, {}, targetPath, function(entry) {
helpers.getWithXhr(function(content) {
resolve({
sourceUrl: sourceUrl,
targetPath: targetPath,
fullPath: entry.fullPath,
name: entry.name,
content: content
});
}, targetPath);
}, reject);
},
validationFunc: function(driver, result) {
result.type.should.be.equal('resolved');
result.data.name.should.be.equal('cookies.json');
result.data.content.should.be.a('string');
var cookies = JSON.parse(result.data.content).cookies;
cookies.myCookie.should.be.equal('myValue');
cookies.mySecondCookie.should.be.equal('mySecondValue');
}
},{
description: 'should send UTF-8 encoded raw string correctly (POST) #34',
expected: 'resolved: {"status": 200, "data": "{\\"data\\": \\"this is a test string\\"...',
before: helpers.setUtf8StringSerializer,
func: function(resolve, reject) {
cordova.plugin.http.post('http://httpbin.org/anything', 'this is a test string', {}, resolve, reject);
},
validationFunc: function(driver, result) {
result.type.should.be.equal('resolved');
JSON.parse(result.data.data).data.should.be.equal('this is a test string');
}
},{
description: 'should encode spaces in query string (params object) correctly (GET) #71',
expected: 'resolved: {"status": 200, "data": "{\\"args\\": \\"query param\\": \\"and value with spaces\\"...',
func: function(resolve, reject) {
cordova.plugin.http.get('http://httpbin.org/get', { 'query param': 'and value with spaces' }, {}, resolve, reject);
},
validationFunc: function(driver, result) {
result.type.should.be.equal('resolved');
JSON.parse(result.data.data).args['query param'].should.be.equal('and value with spaces');
}
},{
description: 'should decode latin1 (iso-8859-1) encoded body correctly (GET) #72',
expected: 'resolved: {"status": 200, "data": "<!DOCTYPE HTML PUBLIC \\"-//W3C//DTD HTML 4.01 Transitional//EN\\"> ...',
func: function(resolve, reject) {
cordova.plugin.http.get('http://www.columbia.edu/kermit/latin1.html', {}, {}, resolve, reject);
},
validationFunc: function(driver, result) {
result.type.should.be.equal('resolved');
result.data.data.should.include('[¡] 161 10/01 241 A1 INVERTED EXCLAMATION MARK\n[¢] 162 10/02 242 A2 CENT SIGN');
}
},{
description: 'should return empty body string correctly (GET)',
expected: 'resolved: {"status": 200, "data": "" ...',
func: function(resolve, reject) {
cordova.plugin.http.get('http://httpbin.org/stream/0', {}, {}, resolve, reject);
},
validationFunc: function(driver, result) {
result.type.should.be.equal('resolved');
result.data.data.should.be.equal('');
}
},{
description: 'should pin SSL cert correctly (GET)',
expected: 'resolved: {"status": 200 ...',
before: helpers.enableSSLPinning,
func: function(resolve, reject) {
cordova.plugin.http.get('https://httpbin.org', {}, {}, resolve, reject);
},
validationFunc: function(driver, result) {
result.type.should.be.equal('resolved');
}
},{
description: 'should send deeply structured JSON object correctly (POST) #65',
expected: 'resolved: {"status": 200, "data": "{\\"data\\": \\"{\\\\"outerObj\\\\":{\\\\"innerStr\\\\":\\\\"testString\\\\",\\\\"innerArr\\\\":[1,2,3]}}\\" ...',
before: helpers.setJsonSerializer,
func: function(resolve, reject) { cordova.plugin.http.post('http://httpbin.org/anything', { outerObj: { innerStr: 'testString', innerArr: [1, 2, 3] }}, {}, resolve, reject); },
validationFunc: function(driver, result) {
result.type.should.be.equal('resolved');
JSON.parse(result.data.data).json.should.eql({ outerObj: { innerStr: 'testString', innerArr: [1, 2, 3] }});
}
},{
description: 'should override header "content-type" correctly (POST) #78',
expected: 'resolved: {"status": 200, "headers": "{\\"Content-Type\\": \\"text/plain\\" ...',
before: helpers.setJsonSerializer,
func: function(resolve, reject) { cordova.plugin.http.post('http://httpbin.org/anything', {}, { 'Content-Type': 'text/plain' }, resolve, reject); },
validationFunc: function(driver, result) {
result.type.should.be.equal('resolved');
JSON.parse(result.data.data).headers['Content-Type'].should.be.equal('text/plain');
}
},{
description: 'should handle error during file download correctly (DOWNLOAD) #83',
expected: 'rejected: {"status": 403, "error": "There was an error downloading the file" ...',
func: function(resolve, reject) { cordova.plugin.http.downloadFile('http://httpbin.org/status/403', {}, {}, cordova.file.tempDirectory + 'testfile.txt', resolve, reject); },
validationFunc: function(driver, result) {
result.type.should.be.equal('rejected');
result.data.status.should.be.equal(403);
result.data.error.should.be.equal('There was an error downloading the file');
}
}
];
if (typeof module !== 'undefined' && module.exports) {
module.exports = { tests: tests, hooks: hooks };
}

View File

@@ -0,0 +1,14 @@
{
"ios": {
"debug": {
"buildFlag": [
"-UseModernBuildSystem=0"
]
},
"release": {
"buildFlag": [
"-UseModernBuildSystem=0"
]
}
}
}

View File

@@ -16,13 +16,16 @@
<allow-intent href="mailto:*" />
<allow-intent href="geo:*" />
<platform name="android">
<edit-config file="app/src/main/AndroidManifest.xml" mode="merge" target="/manifest/application" xmlns:android="http://schemas.android.com/apk/res/android">
<application android:networkSecurityConfig="@xml/network_security_config" />
</edit-config>
<resource-file src="network_security_config.xml" target="app/src/main/res/xml/network_security_config.xml" />
<allow-intent href="market:*" />
</platform>
<platform name="ios">
<allow-intent href="itms:*" />
<allow-intent href="itms-apps:*" />
</platform>
<engine name="android" spec="6.2.3" />
<engine name="ios" spec="4.4.0" />
<plugin name="cordova-plugin-file" spec="4.3.3" />
<preference name="AndroidPersistentFileLocation" value="Internal" />
<preference name="AndroidBlacklistSecureSocketProtocols" value="SSLv3,TLSv1" />
</widget>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">localhost</domain>
<domain includeSubdomains="true">httpbin.org</domain>
<domain includeSubdomains="true">httpbingo.org</domain>
<domain includeSubdomains="true">www.columbia.edu</domain>
</domain-config>
</network-security-config>

View File

@@ -0,0 +1,35 @@
{
"name": "com.ilkimen.http.demo",
"displayName": "HttpDemo",
"version": "1.0.0",
"description": "A sample Apache Cordova application that demonstrates advanced HTTP plugin.",
"main": "index.js",
"scripts": {
"build": "scripts/build.sh",
"test": "npm run build && scripts/test.sh"
},
"author": "Sefa Ilkimen",
"license": "Apache-2.0",
"dependencies": {
"cordova": "10.0.0",
"cordova-android": "9.0.0",
"cordova-browser": "6.0.0",
"cordova-ios": "6.2.0",
"cordova-plugin-device": "2.0.3",
"cordova-plugin-wkwebview-file-xhr": "3.0.0"
},
"cordova": {
"platforms": [
"android",
"ios",
"browser"
],
"plugins": {
"cordova-plugin-device": {},
"cordova-plugin-wkwebview-file-xhr": {}
}
},
"devDependencies": {
"cordova-plugin-device": "2.0.3"
}
}

View File

@@ -1,7 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Security-Policy" content="default-src 'self' data: gap: https://ssl.gstatic.com 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src *; img-src 'self' data: content:;">
<meta http-equiv="Content-Security-Policy" content="default-src 'self' data: gap: https://ssl.gstatic.com https://self-signed.badssl.com http://httpbin.org http://www.columbia.edu 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src *; img-src 'self' data: content: blob:;">
<meta name="format-detection" content="telephone=no">
<meta name="msapplication-tap-highlight" content="no">
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width">
@@ -15,7 +15,7 @@
<button id="nextBtn">Run next test</button>
<script type="text/javascript" src="cordova.js"></script>
<script type="text/javascript" src="app-test-definitions.js"></script>
<script type="text/javascript" src="e2e-specs.js"></script>
<script type="text/javascript" src="index.js"></script>
</body>
</html>

View File

@@ -0,0 +1,168 @@
const app = {
testIndex: -1,
lastResult: null,
testsFlaggedToRun: [],
initialize: function () {
document.getElementById('nextBtn').addEventListener('click', app.onNextBtnClick);
var onlyFlaggedTests = [];
var enabledTests = [];
tests.forEach(function (test) {
if (test.only) {
onlyFlaggedTests.push(test);
}
if (!test.disabled) {
enabledTests.push(test);
}
});
app.testsFlaggedToRun = onlyFlaggedTests.length ? onlyFlaggedTests : enabledTests;
},
printResult: function (prefix, content) {
const text = prefix + ': ' + JSON.stringify(content);
document.getElementById('resultTextarea').value += text;
},
reject: function (content) {
document.getElementById('statusInput').value = 'finished';
app.printResult('result - rejected', content);
app.lastResult = {
type: 'rejected',
data: content
};
},
resolve: function (content) {
document.getElementById('statusInput').value = 'finished';
app.printResult('result - resolved', content);
app.lastResult = {
type: 'resolved',
data: content
};
},
skip: function (content) {
document.getElementById('statusInput').value = 'finished';
app.printResult('result - skipped', content);
app.lastResult = {
type: 'skipped',
data: content
};
},
throw: function (error) {
document.getElementById('statusInput').value = 'finished';
app.printResult('result - throwed', error.message);
app.lastResult = {
type: 'throwed',
message: error.message
};
},
getResult: function (cb) {
cb(app.lastResult);
},
runTest: function (tests, index) {
const testDefinition = tests[index];
const titleText = index + ': ' + testDefinition.description;
const expectedText = 'expected - ' + testDefinition.expected;
document.getElementById('statusInput').value = 'running';
document.getElementById('expectedTextarea').value = expectedText;
document.getElementById('resultTextarea').value = '';
document.getElementById('descriptionLbl').innerText = titleText;
const onSuccessFactory = function (cbChain) {
return function () {
cbChain.shift()(cbChain);
}
};
const onFailFactory = function (prefix) {
return function (errorMessage) {
app.reject(prefix + ': ' + errorMessage);
}
};
const onThrowedHandler = function (prefix, error) {
app.throw(new Error(prefix + ': ' + error.message));
};
const execBeforeEachTest = function (cbChain) {
const prefix = 'in before each hook';
try {
if (!hooks || !hooks.onBeforeEachTest) {
return onSuccessFactory(cbChain)();
}
hooks.onBeforeEachTest(
onSuccessFactory(cbChain),
onFailFactory(prefix)
);
} catch (error) {
onThrowedHandler(prefix, error);
}
};
const execBeforeTest = function (cbChain) {
const prefix = 'in before hook';
try {
if (!testDefinition.before) {
return onSuccessFactory(cbChain)();
}
testDefinition.before(
onSuccessFactory(cbChain),
onFailFactory(prefix)
);
} catch (error) {
onThrowedHandler(prefix, error);
}
};
const execTest = function () {
try {
testDefinition.func(app.resolve, app.reject, app.skip);
} catch (error) {
app.throw(error);
}
};
onSuccessFactory([execBeforeEachTest, execBeforeTest, execTest])();
},
onFinishedAllTests: function () {
const titleText = 'No more tests';
const expectedText = 'You have run all available tests.';
document.getElementById('expectedTextarea').value = expectedText;
document.getElementById('resultTextarea').value = '';
document.getElementById('descriptionLbl').innerText = titleText;
},
onNextBtnClick: function () {
app.testIndex += 1;
if (app.testIndex < app.testsFlaggedToRun.length) {
app.runTest(app.testsFlaggedToRun, app.testIndex);
} else {
app.onFinishedAllTests();
}
}
};
app.initialize();

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

1170
test/e2e-specs.js Normal file

File diff suppressed because it is too large Load Diff

91
test/e2e-tooling/caps.js Normal file
View File

@@ -0,0 +1,91 @@
module.exports = { getCaps };
const path = require('path');
const configs = {
// testing on local machine
localIosDevice: {
platformName: 'iOS',
platformVersion: '10.3',
automationName: 'XCUITest',
deviceName: 'iPhone 8',
autoWebview: true,
app: path.resolve('temp/platforms/ios/build/emulator/HttpDemo.app')
},
localIosEmulator: {
platformName: 'iOS',
platformVersion: '14.3',
automationName: 'XCUITest',
deviceName: 'iPhone 8',
autoWebview: true,
app: path.resolve('temp/platforms/ios/build/emulator/HttpDemo.app')
},
localAndroidEmulator: {
platformName: 'Android',
platformVersion: '9',
deviceName: 'Android Emulator',
autoWebview: true,
fullReset: true,
app: path.resolve('temp/platforms/android/app/build/outputs/apk/debug/app-debug.apk')
},
// testing on SauceLabs
saucelabsIosDevice: {
browserName: '',
'appium-version': '1.20.1',
platformName: 'iOS',
platformVersion: '14.3',
deviceName: 'iPhone 6',
autoWebview: true,
app: 'sauce-storage:HttpDemo.app.zip'
},
saucelabsIosEmulator: {
browserName: '',
'appium-version': '1.20.1',
platformName: 'iOS',
platformVersion: '14.3',
deviceName: 'iPhone Simulator',
autoWebview: true,
app: 'sauce-storage:HttpDemo.app.zip'
},
saucelabsAndroidEmulator: {
browserName: '',
'appium-version': '1.20.1',
platformName: 'Android',
platformVersion: '8.0',
deviceName: 'Android Emulator',
autoWebview: true,
app: 'sauce-storage:HttpDemo.apk'
},
// testing on BrowserStack
browserstackIosDevice: {
device: 'iPhone 12',
os_version: '14',
project: 'HTTP Test App',
autoWebview: true,
app: 'HttpTestAppAndroid',
'browserstack.networkLogs': true
},
browserstackAndroidDevice: {
device: 'Google Nexus 6',
os_version: '6.0',
project: 'HTTP Test App',
autoWebview: true,
app: 'HttpTestAppAndroid',
'browserstack.networkLogs': true
}
};
function getCaps(environment, os, runtime) {
const key = environment.toLowerCase() + capitalize(os) + capitalize(runtime);
const caps = configs[key];
caps.name = `cordova-plugin-advanced-http (${os})`;
return caps;
};
function capitalize(text) {
return text.charAt(0).toUpperCase() + text.slice(1).toLowerCase();
}

View File

@@ -1,4 +1,8 @@
exports.configure = driver => {
module.exports = { setupLogging };
function setupLogging(driver) {
require('colors');
driver.on('status', info => {
console.log(info.cyan);
});
@@ -10,4 +14,4 @@ exports.configure = driver => {
driver.on('http', (meth, path, data) => {
console.log(' > ' + meth.magenta, path, (data || '').grey);
});
};
}

View File

@@ -0,0 +1,223 @@
'use strict';
const Mocha = require('mocha');
const milliseconds = require('ms');
const Base = Mocha.reporters.Base;
const color = Base.color;
const {
isString,
stringify,
inherits
} = Mocha.utils;
const {
EVENT_RUN_BEGIN,
EVENT_RUN_END,
EVENT_TEST_FAIL,
EVENT_TEST_PASS,
EVENT_TEST_PENDING,
EVENT_SUITE_BEGIN,
EVENT_SUITE_END
} = Mocha.Runner.constants;
function Spec(runner, options) {
Base.call(this, runner, options);
var self = this;
var indents = 0;
var n = 0;
function indent() {
return Array(indents).join(' ');
}
runner.on(EVENT_RUN_BEGIN, function() {
Base.consoleLog();
});
runner.on(EVENT_SUITE_BEGIN, function(suite) {
++indents;
Base.consoleLog(color('suite', '%s%s'), indent(), suite.title);
});
runner.on(EVENT_SUITE_END, function() {
--indents;
if (indents === 1) {
Base.consoleLog();
}
});
runner.on(EVENT_TEST_PENDING, function(test) {
var fmt = indent() + color('pending', ' - %s');
Base.consoleLog(fmt, test.title);
});
runner.on(EVENT_TEST_PASS, function(test) {
var fmt;
if (test.speed === 'fast') {
fmt =
indent() +
color('checkmark', ' ' + Base.symbols.ok) +
color('pass', ' %s');
Base.consoleLog(fmt, test.title);
} else {
fmt =
indent() +
color('checkmark', ' ' + Base.symbols.ok) +
color('pass', ' %s') +
color(test.speed, ' (%dms)');
Base.consoleLog(fmt, test.title, test.duration);
}
});
runner.on(EVENT_TEST_FAIL, function(test) {
Base.consoleLog(indent() + color('fail', ' %d) %s'), ++n, test.title);
});
runner.once(EVENT_RUN_END, self.epilogue.bind(self));
}
/**
* Inherit from `Base.prototype`.
*/
inherits(Spec, Base);
Spec.description = 'custom reporter for HTTP plugin testing';
Spec.prototype.epilogue = function() {
var stats = this.stats;
var fmt;
Base.consoleLog();
// passes
fmt =
color('bright pass', ' ') +
color('green', ' %d passing') +
color('light', ' (%s)');
Base.consoleLog(fmt, stats.passes || 0, milliseconds(stats.duration));
// pending
if (stats.pending) {
fmt = color('pending', ' ') + color('pending', ' %d pending');
Base.consoleLog(fmt, stats.pending);
}
// failures
if (stats.failures) {
fmt = color('fail', ' %d failing');
Base.consoleLog(fmt, stats.failures);
this.showList(this.failures);
Base.consoleLog();
}
Base.consoleLog();
};
Spec.prototype.showList = function(failures) {
var multipleErr, multipleTest;
var self = this;
Base.consoleLog();
failures.forEach(function(test, i) {
// format
var fmt =
color('error title', ' %s) %s:\n') +
color('error message', ' %s') +
color('error stack', '\n%s\n');
// msg
var msg;
var err;
if (test.err && test.err.multiple) {
if (multipleTest !== test) {
multipleTest = test;
multipleErr = [test.err].concat(test.err.multiple);
}
err = multipleErr.shift();
} else {
err = test.err;
}
var message;
if (err.message && typeof err.message.toString === 'function') {
message = err.message + '';
} else if (typeof err.inspect === 'function') {
message = err.inspect() + '';
} else {
message = '';
}
var stack = err.stack || message;
var index = message ? stack.indexOf(message) : -1;
if (index === -1) {
msg = message;
} else {
index += message.length;
msg = stack.slice(0, index);
// remove msg from stack
stack = stack.slice(index + 1);
}
// uncaught
if (err.uncaught) {
msg = 'Uncaught ' + msg;
}
// explicitly show diff
if (Base.showDiff(err)) {
self.stringifyDiffObjs(err);
fmt =
color('error title', ' %s) %s:\n%s') + color('error stack', '\n%s\n');
var match = message.match(/^([^:]+): expected/);
msg = '\n ' + color('error message', match ? match[1] : msg);
msg += Base.generateDiff(err.actual, err.expected);
}
// indent stack trace
stack = stack.replace(/^/gm, ' ');
// indented test title
var testTitle = '';
test.titlePath().forEach(function(str, index) {
if (index !== 0) {
testTitle += '\n ';
}
for (var i = 0; i < index; i++) {
testTitle += ' ';
}
testTitle += str;
});
Base.consoleLog(fmt, i + 1, testTitle, msg, stack);
self.showDetails(err);
});
};
Spec.prototype.stringifyDiffObjs = function(err) {
if (!isString(err.actual) || !isString(err.expected)) {
err.actual = stringify(err.actual);
err.expected = stringify(err.expected);
}
}
Spec.prototype.showDetails = function(err) {
if (!err.details) {
return;
}
const details = JSON
.stringify(err.details, null, 2)
.replace(/^/gm, ' ');
Base.consoleLog(
color('error stack', '\n Details:\n%s'),
details
);
}
exports = module.exports = Spec;

View File

@@ -0,0 +1,22 @@
module.exports = { getServer };
const configs = {
local: {
host: 'localhost',
port: 4723
},
saucelabs: {
host: 'ondemand.saucelabs.com',
port: 80,
auth: process.env.SAUCE_USERNAME + ":" + process.env.SAUCE_ACCESS_KEY
},
browserstack: {
host: 'hub-cloud.browserstack.com',
port: 80,
auth: process.env.BROWSERSTACK_USERNAME + ":" + process.env.BROWSERSTACK_ACCESS_KEY
}
}
function getServer(environment) {
return configs[environment.toLowerCase()];
}

142
test/e2e-tooling/test.js Normal file
View File

@@ -0,0 +1,142 @@
const wd = require('wd');
const chai = require('chai');
const logging = require('./logging');
const capsConfig = require('./caps');
const serverConfig = require('./server');
const testDefinitions = require('../e2e-specs');
global.should = chai.should();
let driver;
let allPassed = true;
describe('Advanced HTTP e2e test suite', function () {
const isSauceLabs = !!process.env.SAUCE_USERNAME;
const isBrowserStack = !!process.env.BROWSERSTACK_USERNAME;
const isVerbose = process.argv.includes('--verbose');
const isDevice = process.argv.includes('--device');
const isAndroid = process.argv.includes('--android');
const targetInfo = { isSauceLabs, isBrowserStack, isDevice, isAndroid };
const environment = isSauceLabs ? 'saucelabs' : isBrowserStack ? 'browserstack' : 'local';
this.timeout(15000);
this.slow(4000);
before(async function () {
// connecting to saucelabs can take some time
this.timeout(300000);
driver = await wd.promiseChainRemote(serverConfig.getServer(environment));
if (isVerbose) {
logging.setupLogging(driver);
}
await driver.init(
capsConfig.getCaps(
environment,
isAndroid ? 'android' : 'ios',
isDevice ? 'device' : 'emulator'
)
);
});
after(async function () {
await driver.quit().finally(
() => isSauceLabs && driver.sauceJobStatus(allPassed)
);
});
const defineTestForMocha = (test, index) => {
it(index + ': ' + test.description, async function () {
await clickNext(driver);
await validateTestIndex(driver, index);
await validateTestTitle(driver, test.description);
await waitToBeFinished(driver, test.timeout || 10000);
const skipped = await checkSkipped(driver);
if (skipped) {
this.skip();
} else {
await validateResult(driver, test.validationFunc, targetInfo);
}
});
};
const onlyFlaggedTests = [];
const enabledTests = [];
testDefinitions.tests.forEach(test => {
if (test.only) {
onlyFlaggedTests.push(test);
}
if (!test.disabled) {
enabledTests.push(test);
}
});
if (onlyFlaggedTests.length) {
onlyFlaggedTests.forEach(defineTestForMocha);
} else {
enabledTests.forEach(defineTestForMocha);
}
});
async function clickNext(driver) {
await driver.elementById('nextBtn').click().sleep(1000);
}
async function validateTestIndex(driver, testIndex) {
const description = await driver.elementById('descriptionLbl').text();
const index = parseInt(description.match(/(\d+):/)[1], 10);
index.should.be.equal(testIndex, 'Test index is not matching!');
}
async function validateTestTitle(driver, testTitle) {
const description = await driver.elementById('descriptionLbl').text();
const title = description.match(/\d+:\ (.*)/)[1];
title.should.be.equal(testTitle, 'Test description is not matching!');
}
async function waitToBeFinished(driver, timeout) {
const timeoutTimestamp = Date.now() + timeout;
while (true) {
if (await driver.elementById('statusInput').getValue() === 'finished') {
return true;
}
if (Date.now() > timeoutTimestamp) {
throw new Error('Test function timed out!');
}
await sleep(500);
}
}
async function validateResult(driver, validationFunc, targetInfo) {
const result = await driver.safeExecute('app.lastResult');
try {
validationFunc(driver, result, targetInfo);
} catch (error) {
allPassed = false;
error.details = result;
throw error;
}
}
async function checkSkipped(driver) {
const result = await driver.safeExecute('app.lastResult');
return result.type === 'skipped';
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}

View File

@@ -1,135 +0,0 @@
const chai = require('chai');
const mock = require('mock-require');
const path = require('path');
const should = chai.should();
const HELPERS_ID = path.resolve(__dirname, '..', 'www', 'helpers');
const PLUGIN_ID = path.resolve(__dirname, '..', 'www', 'advanced-http');
describe('Advanced HTTP www interface', function() {
let http = {};
let helpers = {};
const noop = () => { /* intentionally doing nothing */ };
const loadHttp = () => {
mock(`${PLUGIN_ID}.helpers`, mock.reRequire('../www/helpers'));
http = mock.reRequire('../www/advanced-http');
};
this.timeout(900000);
beforeEach(() => {
// mocked btoa function (base 64 encoding strings)
global.btoa = decoded => new Buffer(decoded).toString('base64');
mock('cordova/exec', noop);
mock(`${PLUGIN_ID}.cookie-handler`, {});
mock(`${HELPERS_ID}.cookie-handler`, {});
mock(`${PLUGIN_ID}.messages`, require('../www/messages'));
mock(`${HELPERS_ID}.messages`, require('../www/messages'));
mock(`${PLUGIN_ID}.angular-integration`, { registerService: noop });
loadHttp();
});
it('sets global headers correctly with two args (old interface)', () => {
http.setHeader('myKey', 'myValue');
http.getHeaders('*').myKey.should.equal('myValue');
});
it('sets global headers correctly with three args (new interface) #24', () => {
http.setHeader('*', 'myKey', 'myValue');
http.getHeaders('*').myKey.should.equal('myValue');
});
it('sets host headers correctly #24', () => {
http.setHeader('www.google.de', 'myKey', 'myValue');
http.getHeaders('www.google.de').myKey.should.equal('myValue');
});
it('resolves global headers correctly #24', () => {
mock(`${HELPERS_ID}.cookie-handler`, {
getCookieString: () => 'fakeCookieString'
});
mock('cordova/exec', (onSuccess, onFail, namespace, method, params) => {
const headers = params[2];
headers.should.eql({
Cookie: 'fakeCookieString',
myKey: 'myValue'
});
});
loadHttp();
http.setHeader('*', 'myKey', 'myValue');
http.get('url', {}, {}, noop, noop);
});
it('resolves host headers correctly (set without port number) #37', () => {
mock(`${HELPERS_ID}.cookie-handler`, {
getCookieString: () => 'fakeCookieString'
});
mock('cordova/exec', (onSuccess, onFail, namespace, method, params) => {
const headers = params[2];
headers.should.eql({
Cookie: 'fakeCookieString',
myKey: 'myValue'
});
});
loadHttp();
http.setHeader('www.google.de', 'myKey', 'myValue');
http.get('https://www.google.de/?gws_rd=ssl', {}, {}, noop, noop);
});
it('resolves host headers correctly (set with port number) #37', () => {
mock(`${HELPERS_ID}.cookie-handler`, {
getCookieString: () => 'fakeCookieString'
});
mock('cordova/exec', (onSuccess, onFail, namespace, method, params) => {
const headers = params[2];
headers.should.eql({
Cookie: 'fakeCookieString',
myKey: 'myValue'
});
});
loadHttp();
http.setHeader('www.google.de:8080', 'myKey', 'myValue');
http.get('https://www.google.de:8080/?gws_rd=ssl', {}, {}, noop, noop);
});
it('resolves request headers correctly', () => {
mock(`${HELPERS_ID}.cookie-handler`, {
getCookieString: () => 'fakeCookieString'
});
mock('cordova/exec', (onSuccess, onFail, namespace, method, params) => {
const headers = params[2];
headers.should.eql({
Cookie: 'fakeCookieString',
myKey: 'myValue'
});
});
loadHttp();
http.get('https://www.google.de/?gws_rd=ssl', {}, { myKey: 'myValue' }, noop, noop);
});
it('sets basic authentication header correctly #36', () => {
http.useBasicAuth('name', 'pass');
http.getHeaders('*').Authorization.should.equal('Basic bmFtZTpwYXNz');
});
it('throws an Error when you try to add a cookie by using "setHeader" #46', () => {
(function() { http.setHeader('*', 'cookie', 'value') }).should.throw();
});
});

846
test/js-specs.js Normal file
View File

@@ -0,0 +1,846 @@
const chai = require('chai');
const util = require('util');
const should = chai.should();
const BlobMock = require('./mocks/Blob.mock');
const ConsoleMock = require('./mocks/Console.mock');
const FileMock = require('./mocks/File.mock');
const FileReaderMock = require('./mocks/FileReader.mock');
const FormDataMock = require('./mocks/FormData.mock');
describe('Advanced HTTP public interface', function () {
const messages = require('../www/messages');
let http = {};
const noop = () => { /* intentionally doing nothing */ };
const getDependenciesBlueprint = () => {
const globalConfigs = require('../www/global-configs');
const jsUtil = require('../www/js-util');
const ToughCookie = require('../www/umd-tough-cookie');
const lodash = require('../www/lodash');
const errorCodes = require('../www/error-codes');
const WebStorageCookieStore = require('../www/local-storage-store')(ToughCookie, lodash);
const cookieHandler = require('../www/cookie-handler')(null, ToughCookie, WebStorageCookieStore);
const helpers = require('../www/helpers')(null, jsUtil, cookieHandler, messages, errorCodes);
const urlUtil = require('../www/url-util')(jsUtil);
return { exec: noop, cookieHandler, urlUtil: urlUtil, helpers, globalConfigs, errorCodes };
};
const loadHttp = (deps) => {
http = require('../www/public-interface')(deps.exec, deps.cookieHandler, deps.urlUtil, deps.helpers, deps.globalConfigs, deps.errorCodes);
};
beforeEach(() => {
// mocked btoa function (base 64 encoding strings)
global.btoa = decoded => Buffer.from(decoded).toString('base64');
loadHttp(getDependenciesBlueprint());
});
it('sets global headers correctly with two args (old interface)', () => {
http.setHeader('myKey', 'myValue');
http.getHeaders('*').myKey.should.equal('myValue');
});
it('sets global headers correctly with three args (new interface) #24', () => {
http.setHeader('*', 'myKey', 'myValue');
http.getHeaders('*').myKey.should.equal('myValue');
});
it('clears global headers correctly when value is undefined', () => {
http.setHeader('*', 'myKey', 'myValue');
http.setHeader('*', 'myKey', null);
should.equal(undefined, http.getHeaders('*').myKey);
Object.keys(http.getHeaders('*')).length.should.be.equal(0);
});
it('sets host headers correctly #24', () => {
http.setHeader('www.google.de', 'myKey', 'myValue');
http.getHeaders('www.google.de').myKey.should.equal('myValue');
});
it('resolves global headers correctly #24', () => {
const deps = getDependenciesBlueprint();
deps.cookieHandler.getCookieString = () => 'fakeCookieString';
deps.exec = (onSuccess, onFail, namespace, method, params) => {
const headers = params[1];
headers.should.eql({
Cookie: 'fakeCookieString',
myKey: 'myValue'
});
};
loadHttp(deps);
http.setHeader('*', 'myKey', 'myValue');
http.get('url', {}, {}, noop, noop);
});
it('resolves host headers correctly (set without port number) #37', () => {
const deps = getDependenciesBlueprint();
deps.cookieHandler.getCookieString = () => 'fakeCookieString';
deps.exec = (onSuccess, onFail, namespace, method, params) => {
const headers = params[1];
headers.should.eql({
Cookie: 'fakeCookieString',
myKey: 'myValue'
});
};
loadHttp(deps);
http.setHeader('www.google.de', 'myKey', 'myValue');
http.get('https://www.google.de/?gws_rd=ssl', {}, {}, noop, noop);
});
it('resolves host headers correctly (set with port number) #37', () => {
const deps = getDependenciesBlueprint();
deps.cookieHandler.getCookieString = () => 'fakeCookieString';
deps.exec = (onSuccess, onFail, namespace, method, params) => {
const headers = params[1];
headers.should.eql({
Cookie: 'fakeCookieString',
myKey: 'myValue'
});
};
loadHttp(deps);
http.setHeader('www.google.de:8080', 'myKey', 'myValue');
http.get('https://www.google.de:8080/?gws_rd=ssl', {}, {}, noop, noop);
});
it('resolves request headers correctly', () => {
const deps = getDependenciesBlueprint();
deps.cookieHandler.getCookieString = () => 'fakeCookieString';
deps.exec = (onSuccess, onFail, namespace, method, params) => {
const headers = params[1];
headers.should.eql({
Cookie: 'fakeCookieString',
myKey: 'myValue'
});
};
loadHttp(deps);
http.get('https://www.google.de/?gws_rd=ssl', {}, { myKey: 'myValue' }, noop, noop);
});
it('sets basic authentication header correctly #36', () => {
http.useBasicAuth('name', 'pass');
http.getHeaders('*').Authorization.should.equal('Basic bmFtZTpwYXNz');
});
it('throws an Error when you try to add a cookie by using "setHeader" #46', () => {
(() => { http.setHeader('*', 'cookie', 'value'); }).should.throw(messages.ADDING_COOKIES_NOT_SUPPORTED);
});
it('configures global timeout value correctly with given valid value', () => {
http.setRequestTimeout(10);
http.getRequestTimeout().should.equal(10);
});
it('throws an Error when you try to configure global timeout with a string', () => {
(() => { http.setRequestTimeout('myString'); }).should.throw(messages.INVALID_TIMEOUT_VALUE);
});
it('sets global option for following redirects correctly', () => {
http.setFollowRedirect(false);
http.getFollowRedirect().should.equal(false);
});
it('throws an Error when you try to configure global option for following redirects with a string', () => {
(() => { http.setFollowRedirect('myString'); }).should.throw(messages.INVALID_FOLLOW_REDIRECT_VALUE);
});
it('exposes an enumeration style object with mappings for the error codes', () => {
Object.keys(http.ErrorCode).forEach(key => http.ErrorCode[key].should.be.a('number'));
});
});
describe('URL util', function () {
const jsUtil = require('../www/js-util');
const util = require('../www/url-util')(jsUtil);
it('parses URL with protocol, hostname and path correctly', () => {
util.parseUrl('http://ilkimen.net/test').should.include({
protocol: 'http:',
host: 'ilkimen.net',
hostname: 'ilkimen.net',
pathname: '/test',
port: '',
search: '',
hash: ''
});
});
it('parses URL with protocol, hostname, port and path correctly', () => {
util.parseUrl('http://ilkimen.net:8080/test').should.include({
protocol: 'http:',
host: 'ilkimen.net:8080',
hostname: 'ilkimen.net',
pathname: '/test',
port: '8080',
search: '',
hash: ''
});
});
it('parses URL with protocol, hostname, port, path and query string correctly', () => {
util.parseUrl('http://ilkimen.net:8080/test?param=value').should.include({
protocol: 'http:',
host: 'ilkimen.net:8080',
hostname: 'ilkimen.net',
pathname: '/test',
port: '8080',
search: '?param=value',
hash: ''
});
});
it('parses URL with protocol, hostname, port, path, query string and hash param correctly', () => {
util.parseUrl('http://ilkimen.net:8080/test?param=value#myHash').should.include({
protocol: 'http:',
host: 'ilkimen.net:8080',
hostname: 'ilkimen.net',
pathname: '/test',
port: '8080',
search: '?param=value',
hash: '#myHash'
});
});
it('serializes query params correctly', () => {
util.serializeQueryParams({
strParam1: 'value with spaces',
strParam2: 'value with special character äöü%',
boolParam: true,
numberParam: 1,
nullParam: null,
}, false).should.equal('strParam1=value with spaces&strParam2=value with special character äöü%&boolParam=true&numberParam=1&nullParam=null');
});
it('serializes query params correctly with URL encoding enabled', () => {
util.serializeQueryParams({
'param 1': 'value with spaces',
'param 2': 'value with special character äöü%&'
}, true).should.equal('param%201=value%20with%20spaces&param%202=value%20with%20special%20character%20%C3%A4%C3%B6%C3%BC%25%26');
});
it('serializes array of query params correctly', () => {
util.serializeQueryParams({
myArray: ['val1', 'val2', 'val3'],
myString: 'testString'
}, false).should.equal('myArray[]=val1&myArray[]=val2&myArray[]=val3&myString=testString');
});
it('serializes deeply structured array of query params correctly', () => {
util.serializeQueryParams({
myArray: [['val1.1', 'val1.2', 'val1.3'], 'val2', 'val3'],
myString: 'testString'
}, false).should.equal('myArray[][]=val1.1&myArray[][]=val1.2&myArray[][]=val1.3&myArray[]=val2&myArray[]=val3&myString=testString');
});
it('serializes deeply structured object of query params correctly', () => {
util.serializeQueryParams({
myObject: { obj1: { 'param1.1': 'val1.1', 'param1.2': 'val1.2' }, param2: 'val2' }
}, false).should.equal('myObject[obj1][param1.1]=val1.1&myObject[obj1][param1.2]=val1.2&myObject[param2]=val2');
});
it('appends query params string correctly to given URL without query parameters', () => {
util.appendQueryParamsString('http://ilkimen.net/', 'param1=value1')
.should.equal('http://ilkimen.net/?param1=value1');
});
it('appends query params string correctly to given URL with existing query parameters', () => {
util.appendQueryParamsString('http://ilkimen.net/?myParam=myValue', 'param1=value1')
.should.equal('http://ilkimen.net/?myParam=myValue&param1=value1');
});
it('appends query params string correctly to given URL with existing query parameters and hash value', () => {
util.appendQueryParamsString('http://ilkimen.net/?myParam=myValue#myHash', 'param1=value1')
.should.equal('http://ilkimen.net/?myParam=myValue&param1=value1#myHash');
});
});
describe('Common helpers', function () {
describe('mergeHeaders(globalHeaders, localHeaders)', function () {
const init = require('../www/helpers');
init.debug = true;
const helpers = init(null, null, null, null, null, null);
it('merges empty header sets correctly', () => {
helpers.mergeHeaders({}, {}).should.eql({});
});
it('merges simple header sets without collision correctly', () => {
helpers.mergeHeaders({ a: 1 }, { b: 2 }).should.eql({ a: 1, b: 2 });
});
it('merges header sets with collision correctly', () => {
helpers.mergeHeaders({ a: 1 }, { a: 2 }).should.eql({ a: 2 });
});
});
describe('getCookieHeader(url)', function () {
it('resolves cookie header correctly when no cookie is set #198', () => {
const helpers = require('../www/helpers')(null, null, { getCookieString: () => '' }, null);
helpers.getCookieHeader('http://ilkimen.net').should.eql({});
});
it('resolves cookie header correctly when a cookie is set', () => {
const helpers = require('../www/helpers')(null, null, { getCookieString: () => 'cookie=value' }, null);
helpers.getCookieHeader('http://ilkimen.net').should.eql({ Cookie: 'cookie=value' });
});
});
describe('checkClientAuthOptions()', function () {
const jsUtil = require('../www/js-util');
const messages = require('../www/messages');
const helpers = require('../www/helpers')(null, jsUtil, null, messages);
it('returns options object with empty values when mode is "none" and no options are given', () => {
helpers.checkClientAuthOptions('none').should.eql({
alias: null,
rawPkcs: null,
pkcsPassword: ''
});
});
it('returns options object with empty values when mode is "none" and random options are given', () => {
helpers.checkClientAuthOptions('none', {
alias: 'myAlias',
pkcsPath: 'myPath'
}).should.eql({
alias: null,
rawPkcs: null,
pkcsPassword: ''
});
});
it('throws an error when mode is "systemstore" and alias is not a string or undefined', () => {
(() => helpers.checkClientAuthOptions('systemstore', { alias: 1 }))
.should.throw(messages.INVALID_CLIENT_AUTH_ALIAS);
(() => helpers.checkClientAuthOptions('systemstore', { alias: undefined }))
.should.not.throw();
});
it('returns an object with null alias when mode is "systemstore" and no options object is given', () => {
helpers.checkClientAuthOptions('systemstore').should.eql({
alias: null,
rawPkcs: null,
pkcsPassword: ''
});
});
it('throws an error when mode is "buffer" and rawPkcs is not an array buffer', () => {
(() => helpers.checkClientAuthOptions('buffer', {
rawPkcs: undefined,
pkcsPassword: 'password'
})).should.throw(messages.INVALID_CLIENT_AUTH_RAW_PKCS);
(() => helpers.checkClientAuthOptions('buffer', {
pkcsPath: 1,
pkcsPassword: 'password'
})).should.throw(messages.INVALID_CLIENT_AUTH_RAW_PKCS);
});
it('throws an error when mode is "buffer" and pkcsPassword is not a string', () => {
(() => helpers.checkClientAuthOptions('buffer', {
rawPkcs: new ArrayBuffer(),
pkcsPassword: undefined
})).should.throw(messages.INVALID_CLIENT_AUTH_PKCS_PASSWORD);
(() => helpers.checkClientAuthOptions('buffer', {
rawPkcs: new ArrayBuffer(),
pkcsPassword: 1
})).should.throw(messages.INVALID_CLIENT_AUTH_PKCS_PASSWORD);
});
});
describe('handleMissingOptions()', function () {
const jsUtil = require('../www/js-util');
const messages = require('../www/messages');
const helpers = require('../www/helpers')(null, jsUtil, null, messages);
const mockGlobals = {
headers: {},
serializer: 'urlencoded',
followRedirect: true,
timeout: 60.0,
}
it('adds missing "followRedirect" option correctly', () => {
helpers.handleMissingOptions({}, mockGlobals).should.include({ followRedirect: true });
});
it('throws an error when "followRedirect" option is not a boolean', () => {
(() => helpers.handleMissingOptions({ followRedirect: 1 }, mockGlobals))
.should.throw(messages.INVALID_FOLLOW_REDIRECT_VALUE);
});
});
describe('injectRawResponseHandler()', function () {
const jsUtil = require('../www/js-util');
const messages = require('../www/messages');
const errorCodes = require('../www/error-codes');
const fakeBase64 = { toArrayBuffer: () => 'fakeArrayBuffer' };
global.Blob = function (array, meta) {
this.isFakeBlob = true;
this.array = array;
this.meta = meta;
};
it('does not change response data if it is an ArrayBuffer', () => {
const helpers = require('../www/helpers')(null, jsUtil, null, messages, null, errorCodes);
const buffer = new ArrayBuffer(5);
const handler = helpers.injectRawResponseHandler(
'arraybuffer',
response => response.data.should.be.equal(buffer)
);
handler({ data: buffer });
});
it('does not change response data if it is a Blob', () => {
const fakeJsUtil = { getTypeOf: () => 'Blob' };
const helpers = require('../www/helpers')(null, fakeJsUtil, null, messages, null, errorCodes);
const handler = helpers.injectRawResponseHandler(
'blob',
response => response.data.should.be.equal('fakeData')
);
handler({ data: 'fakeData' });
});
it('does not change response data if response type is "text"', () => {
const helpers = require('../www/helpers')(null, jsUtil, null, messages, null, errorCodes);
const example = 'exampleText';
const handler = helpers.injectRawResponseHandler(
'text',
response => response.data.should.be.equal(example)
);
handler({ data: example });
});
it('handles response type "json" correctly', () => {
const fakeData = { myString: 'bla', myNumber: 10 };
const helpers = require('../www/helpers')(null, jsUtil, null, messages, null, errorCodes);
const handler = helpers.injectRawResponseHandler(
'json',
response => response.data.should.be.eql(fakeData)
);
handler({ data: JSON.stringify(fakeData) });
});
it('handles empty "json" response correctly', () => {
const emptyData = "";
const helpers = require('../www/helpers')(null, jsUtil, null, messages, null, errorCodes);
const handler = helpers.injectRawResponseHandler(
'json',
response => should.equal(undefined, response.data)
);
handler({ data: emptyData });
});
it('handles response type "arraybuffer" correctly', () => {
const helpers = require('../www/helpers')(null, jsUtil, null, messages, fakeBase64, errorCodes);
const handler = helpers.injectRawResponseHandler(
'arraybuffer',
response => response.data.should.be.equal('fakeArrayBuffer')
);
handler({ data: 'myString' });
});
it('handles empty "arraybuffer" response correctly', () => {
const helpers = require('../www/helpers')(null, jsUtil, null, messages, fakeBase64, errorCodes);
const handler = helpers.injectRawResponseHandler(
'arraybuffer',
response => should.equal(null, response.data)
);
handler({ data: '' });
});
it('handles response type "blob" correctly', () => {
const helpers = require('../www/helpers')(null, jsUtil, null, messages, fakeBase64, errorCodes);
const handler = helpers.injectRawResponseHandler(
'blob',
(response) => {
response.data.isFakeBlob.should.be.equal(true);
response.data.array.should.be.eql(['fakeArrayBuffer']);
response.data.meta.type.should.be.equal('fakeType');
}
);
handler({ data: 'myString', headers: { 'content-type': 'fakeType' } });
});
it('handles empty "blob" response correctly', () => {
const helpers = require('../www/helpers')(null, jsUtil, null, messages, fakeBase64, errorCodes);
const handler = helpers.injectRawResponseHandler(
'blob',
(response) => {
should.equal(null, response.data)
}
);
handler({ data: '', headers: { 'content-type': 'fakeType' } });
});
it('calls failure callback when post-processing fails', () => {
const helpers = require('../www/helpers')(null, jsUtil, null, messages, fakeBase64, errorCodes);
const handler = helpers.injectRawResponseHandler(
'json',
null,
(response) => {
response.status.should.be.equal(errorCodes.POST_PROCESSING_FAILED);
response.error.should.include('Unexpected token N in JSON at position 0');
}
);
handler({ data: 'NotValidJson' });
});
});
describe('checkUploadFileOptions()', function () {
const jsUtil = require('../www/js-util');
const messages = require('../www/messages');
const helpers = require('../www/helpers')(null, jsUtil, null, messages, null, null);
it('checks valid file options correctly', () => {
const opts = {
filePaths: ['file://path/to/file.png'],
names: ['ScreenCapture']
};
// string values
helpers.checkUploadFileOptions(opts.filePaths[0], opts.names[0]).should.be.eql(opts);
// string array values
helpers.checkUploadFileOptions(opts.filePaths, opts.names).should.be.eql(opts);
});
it('throws an error when file options are missing', () => {
(() => helpers.checkUploadFileOptions(undefined, ['ScreenCapture'])).should.throw(messages.FILE_PATHS_TYPE_MISMATCH);
(() => helpers.checkUploadFileOptions(['file://path/to/file.png'], undefined)).should.throw(messages.NAMES_TYPE_MISMATCH);
});
it('throws an error when file options contains empty arrays', () => {
(() => helpers.checkUploadFileOptions([], ['ScreenCapture'])).should.throw(messages.EMPTY_FILE_PATHS);
(() => helpers.checkUploadFileOptions(['file://path/to/file.png'], [])).should.throw(messages.EMPTY_NAMES);
});
it('throws an error when file options contains invalid values', () => {
(() => helpers.checkUploadFileOptions([1], ['ScreenCapture'])).should.throw(messages.FILE_PATHS_TYPE_MISMATCH);
(() => helpers.checkUploadFileOptions(['file://path/to/file.png'], [1])).should.throw(messages.NAMES_TYPE_MISMATCH);
});
});
describe('processData()', function () {
const mockWindow = {
Blob: BlobMock,
File: FileMock,
FileReader: FileReaderMock,
FormData: FormDataMock,
TextEncoder: util.TextEncoder,
}
const base64 = { fromArrayBuffer: ab => Buffer.from(ab).toString('base64') };
const jsUtil = require('../www/js-util');
const messages = require('../www/messages');
const dependencyValidator = require('../www/dependency-validator')(mockWindow, null, messages);
const helpers = require('../www/helpers')(mockWindow, jsUtil, null, messages, base64, null, dependencyValidator, {});
const testString = 'Test String öäüß 👍😉';
const testStringBase64 = Buffer.from(testString).toString('base64');
it('throws an error when given data does not match allowed data types', () => {
(() => helpers.processData('myString', 'urlencoded')).should.throw(messages.TYPE_MISMATCH_DATA);
(() => helpers.processData('myString', 'json')).should.throw(messages.TYPE_MISMATCH_DATA);
(() => helpers.processData({}, 'utf8')).should.throw(messages.TYPE_MISMATCH_DATA);
});
it('throws an error when given data does not match allowed instance types', () => {
(() => helpers.processData('myString', 'multipart')).should.throw(messages.INSTANCE_TYPE_MISMATCH_DATA);
});
it('processes data correctly when serializer "utf8" is configured', (cb) => {
helpers.processData('myString', 'utf8', (data) => {
data.should.be.eql({ text: 'myString' });
cb();
})
});
it('processes data correctly when serializer "multipart" is configured and form data contains string value', (cb) => {
const formData = new FormDataMock();
formData.append('myString', testString);
helpers.processData(formData, 'multipart', (data) => {
data.buffers.length.should.be.equal(1);
data.names.length.should.be.equal(1);
data.fileNames.length.should.be.equal(1);
data.types.length.should.be.equal(1);
data.buffers[0].should.be.eql(testStringBase64);
data.names[0].should.be.equal('myString');
should.equal(data.fileNames[0], null);
data.types[0].should.be.equal('text/plain');
cb();
});
});
it('processes data correctly when serializer "multipart" is configured and form data contains file value', (cb) => {
const formData = new FormDataMock();
formData.append('myFile', new BlobMock([testString], { type: 'application/octet-stream' }));
helpers.processData(formData, 'multipart', (data) => {
data.buffers.length.should.be.equal(1);
data.names.length.should.be.equal(1);
data.fileNames.length.should.be.equal(1);
data.types.length.should.be.equal(1);
data.buffers[0].should.be.eql(testStringBase64);
data.names[0].should.be.equal('myFile');
data.fileNames[0].should.be.equal('blob');
data.types[0].should.be.equal('application/octet-stream');
cb();
});
});
it('processes data correctly when serializer "multipart" is configured and form data contains file value (filename set)', (cb) => {
const formData = new FormDataMock();
formData.append('myFile', new BlobMock([testString], { type: 'application/octet-stream' }), 'file.name');
helpers.processData(formData, 'multipart', (data) => {
data.buffers.length.should.be.equal(1);
data.names.length.should.be.equal(1);
data.fileNames.length.should.be.equal(1);
data.types.length.should.be.equal(1);
data.buffers[0].should.be.eql(testStringBase64);
data.names[0].should.be.equal('myFile');
data.fileNames[0].should.be.equal('file.name');
data.types[0].should.be.equal('application/octet-stream');
cb();
});
});
it('processes data correctly when serializer "multipart" is configured and form data contains file value (filename empty)', (cb) => {
const formData = new FormDataMock();
formData.append('myFile', new BlobMock([testString], { type: 'application/octet-stream' }), '');
helpers.processData(formData, 'multipart', (data) => {
data.buffers.length.should.be.equal(1);
data.names.length.should.be.equal(1);
data.fileNames.length.should.be.equal(1);
data.types.length.should.be.equal(1);
data.buffers[0].should.be.eql(testStringBase64);
data.names[0].should.be.equal('myFile');
data.fileNames[0].should.be.equal('');
data.types[0].should.be.equal('application/octet-stream');
cb();
});
});
it('processes data correctly when serializer "raw" is configured', (cb) => {
const byteArray = new Uint8Array([1, 2, 3]);
helpers.processData(byteArray, 'raw', (data) => {
data.should.be.a('ArrayBuffer');
data.should.be.equal(byteArray.buffer);
cb();
})
});
});
describe('nextRequestId()', function () {
const helpers = require('../www/helpers')(null, null, null, null, null, null);
it('returns number requestIds', () => {
helpers.nextRequestId().should.be.a('number');
});
it('returns unique requestIds', () => {
const ids = [helpers.nextRequestId(), helpers.nextRequestId(), helpers.nextRequestId()];
const set = new Set(ids);
ids.should.to.deep.equal(Array.from(set));
});
});
});
describe('Dependency Validator', function () {
const messages = require('../www/messages');
describe('logWarnings()', function () {
it('logs a warning message if FormData API is not supported', function () {
const console = new ConsoleMock();
require('../www/dependency-validator')({}, console, messages).logWarnings();
console.messageList.length.should.be.equal(1);
console.messageList[0].type.should.be.equal('warn');
console.messageList[0].message.should.be.eql([messages.MISSING_FORMDATA_API]);
});
it('logs a warning message if FormData.entries() API is not supported', function () {
const console = new ConsoleMock();
require('../www/dependency-validator')({ FormData: {} }, console, messages).logWarnings();
console.messageList.length.should.be.equal(1);
console.messageList[0].type.should.be.equal('warn');
console.messageList[0].message.should.be.eql([messages.MISSING_FORMDATA_ENTRIES_API]);
});
});
describe('checkBlobApi()', function () {
it('throws an error if Blob API is not supported', function () {
const console = new ConsoleMock();
const validator = require('../www/dependency-validator')({}, console, messages);
(() => validator.checkBlobApi()).should.throw(messages.MISSING_BLOB_API);
});
});
describe('checkFileReaderApi()', function () {
it('throws an error if FileReader API is not supported', function () {
const console = new ConsoleMock();
const validator = require('../www/dependency-validator')({}, console, messages);
(() => validator.checkFileReaderApi()).should.throw(messages.MISSING_FILE_READER_API);
});
});
describe('checkFormDataInstance()', function () {
it('throws an error if FormData.entries() is not supported on given instance', function () {
const console = new ConsoleMock();
const validator = require('../www/dependency-validator')({ FormData: {} }, console, messages);
(() => validator.checkFormDataInstance({})).should.throw(messages.MISSING_FORMDATA_ENTRIES_API);
});
});
describe('checkTextEncoderApi()', function () {
it('throws an error if TextEncoder API is not supported', function () {
const console = new ConsoleMock();
const validator = require('../www/dependency-validator')({}, console, messages);
(() => validator.checkTextEncoderApi()).should.throw(messages.MISSING_TEXT_ENCODER_API);
});
});
});
describe('Ponyfills', function () {
const mockWindow = {
Blob: BlobMock,
File: FileMock,
};
const init = require('../www/ponyfills');
init.debug = true;
const ponyfills = init(mockWindow);
describe('Iterator', function () {
it('exposes interface correctly', () => {
const iterator = new ponyfills.Iterator([]);
iterator.next.should.be.a('function');
});
describe('next()', function () {
it('returns iteration object correctly when list is empty', () => {
const iterator = new ponyfills.Iterator([]);
iterator.next().should.be.eql({ done: true, value: undefined });
});
it('returns iteration object correctly when end posititon of list is not reached yet', () => {
const iterator = new ponyfills.Iterator([['first', 'this is the first item']]);
iterator.next().should.be.eql({ done: false, value: ['first', 'this is the first item'] });
});
it('returns iteration object correctly when end posititon of list is already reached', () => {
const iterator = new ponyfills.Iterator([['first', 'this is the first item']]);
iterator.next();
iterator.next().should.be.eql({ done: true, value: undefined });
});
});
});
describe('FormData', function () {
it('exposes interface correctly', () => {
const formData = new ponyfills.FormData();
formData.append.should.be.a('function');
formData.entries.should.be.a('function');
});
describe('append()', function () {
it('appends string value correctly', () => {
const formData = new ponyfills.FormData();
formData.append('test', 'myTestString');
formData.__items[0].should.be.eql(['test', 'myTestString']);
});
it('appends numeric value correctly', () => {
const formData = new ponyfills.FormData();
formData.append('test', 10);
formData.__items[0].should.be.eql(['test', '10']);
formData.__items[0][1].should.be.a('string');
});
it('appends Blob value correctly', () => {
const formData = new ponyfills.FormData();
const blob = new BlobMock(['another test'], { type: 'text/plain' });
formData.append('myBlob', blob, 'myFileName.txt');
formData.__items[0].should.be.eql(['myBlob', blob]);
formData.__items[0][1].name.should.be.equal('myFileName.txt');
formData.__items[0][1].lastModifiedDate.should.be.a('Date');
});
it('appends File value correctly', () => {
const formData = new ponyfills.FormData();
const blob = new BlobMock(['another test'], { type: 'text/plain' });
const file = new FileMock(blob, 'myFileName.txt');
formData.append('myFile', file, 'myOverriddenFileName.txt');
formData.__items[0].should.be.eql(['myFile', file]);
formData.__items[0][1].name.should.be.equal('myFileName.txt');
formData.__items[0][1].lastModifiedDate.should.be.eql(file.lastModifiedDate);
});
});
describe('entries()', function () {
it('returns an iterator correctly', () => {
const formData = new ponyfills.FormData();
formData.entries().should.be.an.instanceof(ponyfills.Iterator);
})
});
});
});

35
test/mocks/Blob.mock.js Normal file
View File

@@ -0,0 +1,35 @@
module.exports = class BlobMock {
constructor(blobParts, options) {
if (blobParts instanceof BlobMock) {
this._buffer = blobParts._buffer;
} else {
this._buffer = new Uint8Array(Buffer.concat(blobParts.map(part => Buffer.from(part, 'utf8')))).buffer;
}
this._type = options.type || '';
}
get size() {
return this._buffer.length;
}
get type() {
return this._type;
}
arrayBuffer() {
throw new Error('Not implemented in BlobMock.');
}
slice() {
throw new Error('Not implemented in BlobMock.');
}
stream() {
throw new Error('Not implemented in BlobMock.');
}
text() {
throw new Error('Not implemented in BlobMock.');
}
}

View File

@@ -0,0 +1,11 @@
module.exports = class ConsoleMock {
constructor() {
this.messageList = [];
}
debug(...message) { this.messageList.push({ type: 'debug', message }); }
error(...message) { this.messageList.push({ type: 'error', message }); }
log(...message) { this.messageList.push({ type: 'log', message }); }
info(...message) { this.messageList.push({ type: 'info', message }); }
warn(...message) { this.messageList.push({ type: 'warn', message }); }
}

17
test/mocks/File.mock.js Normal file
View File

@@ -0,0 +1,17 @@
const BlobMock = require('./Blob.mock');
module.exports = class FileMock extends BlobMock {
constructor(blob, fileName) {
super(blob, { type: blob.type });
this._fileName = fileName !== undefined ? fileName : 'blob';
this.__lastModifiedDate = new Date();
}
get name() {
return this._fileName;
}
get lastModifiedDate() {
return this.__lastModifiedDate;
}
}

Some files were not shown because too many files have changed in this diff Show More