mirror of
https://github.com/silkimen/cordova-plugin-advanced-http.git
synced 2026-02-11 00:00:06 +08:00
Compare commits
285 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0bf39650e1 | ||
|
|
d444363a8e | ||
|
|
13976bbe43 | ||
|
|
70c8b9bb32 | ||
|
|
4b964e8b7c | ||
|
|
56f8b41f4f | ||
|
|
e1e720fe2d | ||
|
|
a1b6ea94f0 | ||
|
|
d977392a49 | ||
|
|
8d28f4ab80 | ||
|
|
38b3e6ffb1 | ||
|
|
7d984bcf89 | ||
|
|
2cfac2026b | ||
|
|
8bdbf7ca78 | ||
|
|
d5ce3ed76b | ||
|
|
8a83a940cb | ||
|
|
230de36618 | ||
|
|
0af726d8ce | ||
|
|
ba57d3cba1 | ||
|
|
508b1b4f83 | ||
|
|
7992bd0991 | ||
|
|
a331b57ab2 | ||
|
|
ee30160921 | ||
|
|
e8e1c4273f | ||
|
|
752b2cdcb7 | ||
|
|
314314d7f9 | ||
|
|
a8e3637f27 | ||
|
|
56272b9a5d | ||
|
|
8f859db57f | ||
|
|
e673754b13 | ||
|
|
a0f376233c | ||
|
|
fcd142a70b | ||
|
|
807400bc63 | ||
|
|
1fd857f1d9 | ||
|
|
f801d2a283 | ||
|
|
6033ea4b76 | ||
|
|
dadbf97d0c | ||
|
|
f06788d199 | ||
|
|
a195de409d | ||
|
|
0fade8351d | ||
|
|
ebd6ae9793 | ||
|
|
49f219723d | ||
|
|
f1bb4f36d0 | ||
|
|
e2a869bbd2 | ||
|
|
d2f79352ae | ||
|
|
cca1be9d91 | ||
|
|
b6d8763a18 | ||
|
|
c70ce750ed | ||
|
|
54abb2ba37 | ||
|
|
8d433d050f | ||
|
|
bf616fc6e5 | ||
|
|
c31765bbcd | ||
|
|
b5ec508c30 | ||
|
|
6fcf4dc8eb | ||
|
|
f0e1522982 | ||
|
|
c387e52ea3 | ||
|
|
f87afa2217 | ||
|
|
516aa6b61d | ||
|
|
a6bf9041a5 | ||
|
|
c41fc11888 | ||
|
|
91515d30bd | ||
|
|
c8638ff204 | ||
|
|
0acf2e2574 | ||
|
|
f4674028b5 | ||
|
|
5919d90698 | ||
|
|
27e959800a | ||
|
|
96f45d7274 | ||
|
|
3dbd07c9df | ||
|
|
cb597c0d30 | ||
|
|
60189a68b3 | ||
|
|
8e4bfdbc70 | ||
|
|
2ae4c7cf39 | ||
|
|
32fdf49d31 | ||
|
|
db0f233737 | ||
|
|
2263950de9 | ||
|
|
2a9d3296cc | ||
|
|
55dd751cec | ||
|
|
af239d3194 | ||
|
|
291e8fe547 | ||
|
|
8b3466e3c3 | ||
|
|
73e76f6333 | ||
|
|
3fcac64597 | ||
|
|
4a437f9435 | ||
|
|
2d15b86cb5 | ||
|
|
63d859ad54 | ||
|
|
6b9ed72b9f | ||
|
|
1719334d39 | ||
|
|
8db15c6c96 | ||
|
|
e58a398c33 | ||
|
|
a637d70ec1 | ||
|
|
6f40ed5cd8 | ||
|
|
8b09e159fa | ||
|
|
1d5642c8ae | ||
|
|
a580ce2067 | ||
|
|
35dc0205aa | ||
|
|
2dbf0d452a | ||
|
|
8a9381ffaa | ||
|
|
f1c95ef6fd | ||
|
|
6877fbdb65 | ||
|
|
a52e243e5f | ||
|
|
9c34f47624 | ||
|
|
3f1ee62088 | ||
|
|
c73b15b620 | ||
|
|
86698ffdcc | ||
|
|
4a396c28e5 | ||
|
|
53224ed875 | ||
|
|
3adec712c9 | ||
|
|
a49bcac81b | ||
|
|
2f01d48abb | ||
|
|
53df68f154 | ||
|
|
441268586b | ||
|
|
df86929148 | ||
|
|
f691d09eb0 | ||
|
|
b68cbcd157 | ||
|
|
a9bdca2d6d | ||
|
|
5186b375a4 | ||
|
|
d28eedebd0 | ||
|
|
25b4a283ea | ||
|
|
ebef9a2944 | ||
|
|
c452b29433 | ||
|
|
60e7debbdf | ||
|
|
c7820c8185 | ||
|
|
f6a15190f3 | ||
|
|
419283c5dd | ||
|
|
7a8f9a0dba | ||
|
|
64731a3794 | ||
|
|
73097b9db9 | ||
|
|
a9c3be124e | ||
|
|
5d5a859cfc | ||
|
|
d67e80dfe0 | ||
|
|
55ba423d2f | ||
|
|
7f34201e8d | ||
|
|
01c43c4e29 | ||
|
|
d34196ff4e | ||
|
|
1957cf4ef7 | ||
|
|
681c384b3e | ||
|
|
9d6406277c | ||
|
|
b9bc1b0054 | ||
|
|
d17f44b641 | ||
|
|
11515a8400 | ||
|
|
937296b371 | ||
|
|
b60086df0d | ||
|
|
a48a91dd14 | ||
|
|
e8a72a53cc | ||
|
|
efbdc21221 | ||
|
|
06d7d859e7 | ||
|
|
239b82c08e | ||
|
|
e987652d97 | ||
|
|
068b25b6f9 | ||
|
|
8c55572eb8 | ||
|
|
48afeefbc3 | ||
|
|
292d726b20 | ||
|
|
47ac5d3e0a | ||
|
|
4db3939bbe | ||
|
|
0aee536d5c | ||
|
|
0d8d7735d9 | ||
|
|
208529003d | ||
|
|
77c0a887ac | ||
|
|
8c596d80b0 | ||
|
|
0407a561fc | ||
|
|
ebe6205e50 | ||
|
|
3ea9fd4fd1 | ||
|
|
56791016e6 | ||
|
|
9655c89486 | ||
|
|
15bf6281bc | ||
|
|
d342ca054a | ||
|
|
d7e13b046b | ||
|
|
1375727316 | ||
|
|
d136d90893 | ||
|
|
a8f1d2b1c2 | ||
|
|
c29ce85df3 | ||
|
|
aeefc9f5ef | ||
|
|
4ea9c24d6d | ||
|
|
7037fd2fa7 | ||
|
|
6f210a70fb | ||
|
|
10eb102d43 | ||
|
|
9dc0bd56e0 | ||
|
|
190c44b30a | ||
|
|
807af7adfa | ||
|
|
dd66698f96 | ||
|
|
3476ff7ffb | ||
|
|
52b2efe71b | ||
|
|
8b4c6de4b9 | ||
|
|
3138d423ee | ||
|
|
f0194ad55a | ||
|
|
09da3c9a97 | ||
|
|
52ccfc6aca | ||
|
|
fc89a64675 | ||
|
|
4954385361 | ||
|
|
08554c02cb | ||
|
|
18a2fc0c69 | ||
|
|
a4a6440bcb | ||
|
|
792515e1c6 | ||
|
|
66dc8ff2ca | ||
|
|
91b35d561c | ||
|
|
45c0f8937f | ||
|
|
b1c8427986 | ||
|
|
91470725ee | ||
|
|
7cb41e0c1b | ||
|
|
ee8cae88a9 | ||
|
|
aa8105948e | ||
|
|
325a2e6200 | ||
|
|
8ac8b95246 | ||
|
|
5a7f09bc44 | ||
|
|
308c49635b | ||
|
|
d2a5a113c6 | ||
|
|
2c4ce6f8c7 | ||
|
|
613593ba51 | ||
|
|
a20821378b | ||
|
|
5535464db2 | ||
|
|
7ded66d4b8 | ||
|
|
d84a222dda | ||
|
|
14eef1ca66 | ||
|
|
cf5a684d51 | ||
|
|
c153378b4b | ||
|
|
8092e8b025 | ||
|
|
afb4ea6199 | ||
|
|
2c8c8a14a5 | ||
|
|
8267a02c9d | ||
|
|
a1f678a886 | ||
|
|
69cf4d0939 | ||
|
|
d90cefcbaa | ||
|
|
b3118d5ccd | ||
|
|
6b0f23b2d1 | ||
|
|
d0ece22302 | ||
|
|
a6a1bcaab1 | ||
|
|
4afb375971 | ||
|
|
20e5bd0ee9 | ||
|
|
e84d84c358 | ||
|
|
955c590e42 | ||
|
|
0d627b7a0d | ||
|
|
8fc4c2b9cb | ||
|
|
a0edca1b1d | ||
|
|
d5b2cdaf60 | ||
|
|
0989de20ab | ||
|
|
050ed894d6 | ||
|
|
4552e9c84b | ||
|
|
c8df1ebd8b | ||
|
|
50cda6a5af | ||
|
|
4600fd0555 | ||
|
|
46176500df | ||
|
|
9f515d01d8 | ||
|
|
bf8ded59a2 | ||
|
|
c531ffaa44 | ||
|
|
0e4624f627 | ||
|
|
9a65da55c9 | ||
|
|
4c26dd793e | ||
|
|
beae854526 | ||
|
|
9cd64b1d9e | ||
|
|
d726998e5e | ||
|
|
b6976bef18 | ||
|
|
60220a224b | ||
|
|
ba37200d75 | ||
|
|
50d40e76eb | ||
|
|
99683f1f30 | ||
|
|
e9070461c1 | ||
|
|
04fdbed1fb | ||
|
|
3aab872ec0 | ||
|
|
b2e7878da4 | ||
|
|
6808069a18 | ||
|
|
fbd5c60c68 | ||
|
|
fc220041d8 | ||
|
|
1e61e48628 | ||
|
|
bcf6f6ed90 | ||
|
|
cbe554abe7 | ||
|
|
c35af633a4 | ||
|
|
340e45a381 | ||
|
|
434d8dfd2b | ||
|
|
8e27b5b1dd | ||
|
|
dbe9d003b8 | ||
|
|
c9a9296396 | ||
|
|
9ed1c45e77 | ||
|
|
07e0195c91 | ||
|
|
141f996052 | ||
|
|
cabb0db9de | ||
|
|
43b04a94e2 | ||
|
|
1c1f716cf8 | ||
|
|
125248c267 | ||
|
|
20f39b32f1 | ||
|
|
31c5ad9bd3 | ||
|
|
b08f7b4333 | ||
|
|
6ea22f99e8 | ||
|
|
d03c6a5a48 | ||
|
|
dc123932e3 | ||
|
|
46517ae7f2 |
19
.editorconfig
Normal file
19
.editorconfig
Normal file
@@ -0,0 +1,19 @@
|
||||
# EditorConfig helps developers define and maintain consistent
|
||||
# coding styles between different editors and IDEs
|
||||
# editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
# Change these settings to your own preference
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
# We recommend you to keep these unchanged
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
8
.gitignore
vendored
8
.gitignore
vendored
@@ -1,2 +1,8 @@
|
||||
node_modules/**
|
||||
test/e2e-app-template/www/certificates/*.cer
|
||||
tags
|
||||
.zedstate
|
||||
.zedstate
|
||||
npm-debug.log
|
||||
/temp
|
||||
/android-sdk-macosx.zip
|
||||
/android-sdk-macosx/**
|
||||
|
||||
5
.npmignore
Normal file
5
.npmignore
Normal file
@@ -0,0 +1,5 @@
|
||||
tags
|
||||
.zedstate
|
||||
/scripts
|
||||
/temp
|
||||
/test
|
||||
59
.travis.yml
Normal file
59
.travis.yml
Normal file
@@ -0,0 +1,59 @@
|
||||
notifications:
|
||||
slack:
|
||||
secure: lXE+2AgsxZU5G5dI91LkMAIgo8MAWfdM7DB5UOtn5LpuNln+2FmJo1gOI7tkdmLOqpXTGYnpI2VyQN3H4nOF21YhuouzD1Sh8n2wtQg1iTm353kuQpqiVhSBX8ZJ7Be1e1G8OsnxoYOxbs4Zo9qI40EruwkvqLCBHWM5MRGyd4M7EFWwb9Z29VZN0y1Nt5g/c3bT76kdKmF+JCLur2OeEKxAity7sIKgZekSqeIMwEVLSxXnda6Dbjc/cg0MJ0iDArkD7iu6fz/Fcrrxgm/pUxjcgvqze7Gy5i31mjEfspnrglWV1cshMd48BTDKCJ2AMmxH8O3GPSWE2txjIvGRWUve7iViNylvmQCVz3Eyf99+4EuuVGa+5PSodQ/CqODx/65EwtcN3PE1tNz2puKOK8nrOJcFkcbG8KTHKUlQtHCkjitbykUnj/hvhLK5/oWlQYVOLWWrHwdGUh8FI8aFPVGjRjWbHbhdayjEIqxwr1ns+6mYrP1EFNXbaeZxnLNC59XpJl1ifuezqYAk7YEiU5j4rtC7YKgyQ3ueb7anOHTJoTMyDn8mpZXgwuyhoBaeEYytQVgRyMtL6Y5cP98Jn2kv0+vdne3rkk9/JEBTo32HOjvoij6rsqEvXC0LhUDJSNadOVdHht0jjoN6zBH37HIE5/3zysLlPcAcHAS83ow=
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
|
||||
addons:
|
||||
sauce_connect: true
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- name: "iOS Build & Test"
|
||||
language: objective-c
|
||||
sudo: false
|
||||
os: osx
|
||||
osx_image: xcode10.1
|
||||
|
||||
before_install:
|
||||
- export LANG=en_US.UTF-8
|
||||
|
||||
install:
|
||||
- npm install
|
||||
|
||||
script:
|
||||
- npm run testjs &&
|
||||
npm run updatecert &&
|
||||
scripts/build-test-app.sh --ios --emulator &&
|
||||
scripts/upload-artifact.sh --ios &&
|
||||
scripts/test-app.sh --ios --emulator;
|
||||
|
||||
- name: "Android Build & Test"
|
||||
language: android
|
||||
sudo : required
|
||||
|
||||
android:
|
||||
components:
|
||||
- platform-tools
|
||||
- build-tools-28.0.3
|
||||
- android-27
|
||||
- extra-android-support
|
||||
- extra-android-m2repository
|
||||
- extra-google-m2repository
|
||||
|
||||
before_install:
|
||||
- export LANG=en_US.UTF-8 &&
|
||||
curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash - &&
|
||||
sudo apt-get install -y nodejs
|
||||
|
||||
install:
|
||||
- npm install
|
||||
|
||||
script:
|
||||
- npm run testjs &&
|
||||
npm run updatecert &&
|
||||
scripts/build-test-app.sh --android --emulator &&
|
||||
scripts/upload-artifact.sh --android &&
|
||||
scripts/test-app.sh --android --emulator;
|
||||
266
CHANGELOG.md
Normal file
266
CHANGELOG.md
Normal file
@@ -0,0 +1,266 @@
|
||||
# Changelog
|
||||
|
||||
## 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
|
||||
- Fixed #79: PATCH operation not working on Android API level 19 and older (thanks chax)
|
||||
- Fixed #83: App crashes on error during download operation on iOS (thanks troyanskiy)
|
||||
- Fixed #76: upload sequence is not respecting order of operations needed by some sites (thanks Johny101)
|
||||
|
||||
- :warning: **Deprecation**: AngularJS service is deprecated now and will be removed anytime soon
|
||||
|
||||
## 1.10.1
|
||||
|
||||
- Fixed #71: does not encode query string in URL correctly on Android
|
||||
- Fixed #72: app crashes if response encoding is not UTF-8 (thanks jkfb)
|
||||
|
||||
## 1.10.0
|
||||
|
||||
- Feature #34: add new serializer "utf8" sending utf-8 encoded plain text (thanks robertocapuano)
|
||||
|
||||
## 1.9.1
|
||||
|
||||
- Fixed #45: does not encode arrays correctly as HTTP GET parameter on Android
|
||||
- Fixed #54: requests are not responding on iOS with non-string values in header object
|
||||
- Fixed #58: white-list of allowed content-types should be removed for iOS
|
||||
|
||||
## v1.9.0
|
||||
|
||||
- Feature #44: "getCookieString" method is exposed
|
||||
- Feature #43: added support for content type "application/javascript" on iOS (thanks wh33ler)
|
||||
- Feature #46: "setCookie" allows adding custom cookies
|
||||
|
||||
## v1.8.1
|
||||
|
||||
- Fixed #27: "uploadFile" method doesn't return data object on iOS (thanks Faisalali23 and laiyinjie)
|
||||
- Fixed #40: generic error codes are different on Android and iOS
|
||||
|
||||
## v1.8.0
|
||||
|
||||
- Feature #33: response object contains response url
|
||||
|
||||
## v1.7.1
|
||||
|
||||
- Fixed #36: setting basic authentication not working correctly (thanks jkfb)
|
||||
- Fixed #35: Android headers are not normalized (not returned in lowercase)
|
||||
- Fixed #26: JSON request with array data is not working on Android (JSON error)
|
||||
|
||||
## v1.7.0
|
||||
|
||||
- Feature #24: "setHeader" allows configuring headers for specified host
|
||||
|
||||
## v1.6.2
|
||||
|
||||
- Change #29: removed "validateDomainName" (see info notice)
|
||||
- Fixed #31: request fails throwing error on erroneous cookies
|
||||
- Fixed #28: added support for content type "application/hal+json" on iOS (thanks ryandegruyter)
|
||||
|
||||
#### Important information
|
||||
We've decided to remove the `validateDomainName()` method, because people were complaining that `acceptAllCerts(true)` is not behaving as expected. And also it's not a good idea to disable domain name validation while using valid certs, because it pretends having a secure connection, but it isn't.
|
||||
|
||||
You should either use valid certs with domain name validation enabled (safe for production use) or accept any certs without domain name validation (only for private dev environments). I strongly discourage using fake certs in public networks.
|
||||
|
||||
Therefore we are disabling domain name validation automatically, when you set `acceptAllCerts(true)`. So if you were using `validateDomainName()` function, you need to remove this function call for v1.6.2+.
|
||||
|
||||
## v1.6.1
|
||||
|
||||
- Fixed #23: PATCH method broken on android
|
||||
|
||||
## v1.6.0
|
||||
|
||||
- Feature #18: implemented PATCH method (thanks akhatri for android implementation)
|
||||
- Feature #21: added redirection control (thanks to notsyncing and kesozjura)
|
||||
- Fixed #16: cordova tries to run build script during plugin install
|
||||
|
||||
## v1.5.10
|
||||
|
||||
- Fixed #10: fix gzip decompression when request header accepts gzip compression (thanks to DayBr3ak)
|
||||
- Fixed #13: fix angular integration for `setDataSerializer` (thanks to RangerRick)
|
||||
- Added some missing documentation (thanks to RangerRick)
|
||||
|
||||
## v1.5.9
|
||||
|
||||
- Fixed case-sensitive folder name of Android source files
|
||||
|
||||
## v1.5.8
|
||||
|
||||
- Use the same error codes if a request timed out
|
||||
|
||||
## v1.5.7
|
||||
|
||||
- Fixed a bug in cookie handling (cookies containing an "Expires" string)
|
||||
- Added setRequestTimeout function to set the timeout in seconds for all further requests
|
||||
|
||||
## v1.5.6
|
||||
|
||||
- All response header keys are converted to lowercase (iOS only)
|
||||
|
||||
## v1.5.5
|
||||
|
||||
- added a function to remove all cookies for a URL
|
||||
|
||||
## v1.5.4
|
||||
|
||||
- fixed an error if the response has no "headers" field
|
||||
|
||||
## v1.5.3
|
||||
|
||||
- handles cookies correctly on non-success response from server
|
||||
- throws error when a callback function is missing
|
||||
|
||||
## v1.5.2
|
||||
|
||||
- fixed missing file "umd-tough-cookie.js“ (caused by missing file ".npmignore")
|
||||
|
||||
## v1.5.1
|
||||
|
||||
- fixed case-sensitive path name of android source files ("CordovaHTTP" --> "cordovahttp")
|
||||
|
||||
## v1.5.0
|
||||
|
||||
- added cookie handling
|
||||
- cookies are persisted via web storage API
|
||||
|
||||
## v1.4.0
|
||||
|
||||
- forked from "cordova-plugin-http" v1.2.0 (see https://github.com/wymsee/cordova-HTTP)
|
||||
- added configuration for data serializer
|
||||
- added HTTP methods PUT and DELETE
|
||||
|
||||
# Previous changelog (cordova-plugin-http)
|
||||
|
||||
## v1.2.0
|
||||
|
||||
- Added support for TLSv1.1 and TLSv1.2 for android versions 4.1-4.4 (API levels 16-19)
|
||||
|
||||
### Potentially Breaking Changes that really shouldn't matter because you shouldn't be using SSLv3
|
||||
|
||||
- Dropped SSLv3 support for all API Levels < 20. It will now only work on API Levels 20-22.
|
||||
|
||||
## v1.1.0
|
||||
|
||||
- Fixed the body of errors not being returned in iOS
|
||||
- Updated AFNetworking to 3.1.0
|
||||
|
||||
### Potentially Breaking Changes
|
||||
|
||||
- Disable encoding get() URLS in android (Thanks to devgeeks)
|
||||
|
||||
## v1.0.3
|
||||
|
||||
- Fixed version number in plugin.xml
|
||||
|
||||
## v1.0.2
|
||||
|
||||
- Fixed bug using useBasicAuth and setHeader from angular
|
||||
|
||||
## v1.0.1
|
||||
|
||||
- updated README
|
||||
|
||||
## v1.0.0
|
||||
|
||||
- Added getBasicAuthHeader function
|
||||
- Added necessary iOS framework (Thanks to EddyVerbruggen)
|
||||
- Request internet permission in android (Thanks to mbektchiev)
|
||||
- Fix acceptAllCerts doesn't call callbacks (Thanks to EddyVerbruggen)
|
||||
- Add validateDomainName (Thanks to denisbabineau)
|
||||
- Add HEAD request support (untested) (Thanks to denisbabineau)
|
||||
|
||||
### Potentially Breaking Changes
|
||||
|
||||
- Update cordova file plugin dependency (Thanks to denisbabineau)
|
||||
- useBasicAuthHeader and setHeader are now synchronous functions
|
||||
- updated AFNetworking to 3.0.4 - only iOS 7+ is now supported
|
||||
- updated http-request to 6.0
|
||||
|
||||
## v0.1.4
|
||||
|
||||
- Support for certificates in www/certificates folder (Thanks to EddyVerbruggen)
|
||||
|
||||
## v0.1.3
|
||||
|
||||
- Update AFNetworking to 2.4.1 for iOS bug fix in Xcode 6
|
||||
|
||||
## v0.1.2
|
||||
|
||||
- Fixed plugin.xml for case sensitive filesystems (Thanks to andrey-tsaplin)
|
||||
|
||||
## v0.1.1
|
||||
|
||||
- Fixed a bug that prevented building
|
||||
|
||||
## v0.1.0
|
||||
|
||||
- Initial release
|
||||
|
||||
|
||||
## Contributions not noted above
|
||||
|
||||
- Fixed examples (Thanks to devgeeks)
|
||||
- Reports SSL Handshake errors rather than giving a generic error (Thanks to devgeeks)
|
||||
- Exporting http as a module (Thanks to pvsaikrishna)
|
||||
- Added Limitations section to readme (Thanks to cvillerm)
|
||||
- Fixed examples (Thanks to hideov)
|
||||
82
CONTRIBUTING.md
Normal file
82
CONTRIBUTING.md
Normal file
@@ -0,0 +1,82 @@
|
||||
# Contributing to Advanced HTTP Plugin
|
||||
|
||||
We'd love for you to contribute to our source code and to make Advanced HTTP even better than it is
|
||||
today! Here are the guidelines we'd like you to follow:
|
||||
|
||||
- [Issues and Bugs](#issue)
|
||||
- [Feature Requests](#feature)
|
||||
- [Submission Guidelines](#submit)
|
||||
|
||||
## <a name="issue"></a> Found an Issue?
|
||||
|
||||
If you find a bug in the source code or a mistake in the documentation, you can help us by
|
||||
submitting an issue to our [GitHub Repository](https://github.com/silkimen/cordova-plugin-advanced-http/issues).
|
||||
Even better you can submit a Pull Request with a fix.
|
||||
|
||||
## <a name="feature"></a> Want a Feature?
|
||||
|
||||
You can request a new feature by submitting an issue to our
|
||||
[GitHub Repository](https://github.com/silkimen/cordova-plugin-advanced-http/issues).
|
||||
If you would like to implement a new feature then consider what kind of change it is:
|
||||
|
||||
* **Major Changes** that you wish to contribute to the project should be discussed first so that we
|
||||
can better coordinate our efforts, prevent duplication of work, and help you to craft the change
|
||||
so that it is successfully accepted into the project. Please submit an issue to our GitHub Repository
|
||||
for discussion.
|
||||
* **Small Changes** can be crafted and submitted to the GitHub Repository as a Pull Request.
|
||||
|
||||
## <a name="submit"></a> Submission Guidelines
|
||||
|
||||
### Submitting an Issue
|
||||
Before you submit your issue search the archive, maybe your question was already answered.
|
||||
|
||||
If your issue appears to be a bug, and hasn't been reported, open a new issue. Help us to maximize
|
||||
the effort we can spend fixing issues and adding new features, by not reporting duplicate issues.
|
||||
Providing the following information will increase the chances of your issue being dealt with
|
||||
quickly:
|
||||
|
||||
* **Overview of the Issue** - if an error is being thrown a non-minified stack trace helps
|
||||
* **Motivation for or Use Case** - explain why this is a bug for you
|
||||
* **Advanced HTTP Version(s)** - is it a regression?
|
||||
* **Operating System** - is this a problem with all supported OS or only specific ones?
|
||||
* **Related Issues** - has a similar issue been reported before?
|
||||
* **Suggest a Fix** - if you can't fix the bug yourself, perhaps you can point to what might be
|
||||
causing the problem (line of code or commit)
|
||||
|
||||
**If you get help, help others. Good karma rulez!**
|
||||
|
||||
### Submitting a Pull Request
|
||||
Before you submit your pull request consider the following guidelines:
|
||||
|
||||
* Search [GitHub](https://github.com/silkimen/cordova-plugin-advanced-http/pulls) for an open or
|
||||
closed Pull Request that relates to your submission. You don't want to duplicate effort.
|
||||
* Make your changes in a new git branch:
|
||||
|
||||
```shell
|
||||
git checkout -b my-fix-branch master
|
||||
```
|
||||
* Create your patch
|
||||
* Commit your changes using a descriptive commit message
|
||||
* Push your branch to GitHub:
|
||||
|
||||
```shell
|
||||
git push origin my-fix-branch
|
||||
```
|
||||
|
||||
In GitHub, send a pull request to `cordova-plugin-advanced-http:master`.
|
||||
If we suggest changes or the [CI build fails](#cibuild), then:
|
||||
|
||||
* Make the required updates.
|
||||
* Commit your changes to your branch (e.g. `my-fix-branch`).
|
||||
* Push the changes to your GitHub repository (this will update your Pull Request).
|
||||
|
||||
That's it! Thank you for your contribution!
|
||||
|
||||
### <a name="cibuild"></a> Pull Request Feedback
|
||||
You can always check the results of the latest CI builds on
|
||||
[Travis CI](https://travis-ci.org/silkimen/cordova-plugin-advanced-http/).
|
||||
You can use this information to inspect failing tests in your PR.
|
||||
|
||||
## Attribution
|
||||
This document is adapted from
|
||||
[AngularJS' Contribution Guidelines](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md)
|
||||
3
LICENSE
3
LICENSE
@@ -1,5 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017 Mobisys GmbH
|
||||
Copyright (c) 2014 Wymsee, Inc
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
@@ -18,4 +19,4 @@ 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.
|
||||
SOFTWARE.
|
||||
|
||||
462
README.md
462
README.md
@@ -1,191 +1,375 @@
|
||||
cordovaHTTP
|
||||
==================
|
||||
Cordova Advanced HTTP
|
||||
=====================
|
||||
[](https://badge.fury.io/js/cordova-plugin-advanced-http)
|
||||
[](https://www.npmjs.com/package/cordova-plugin-advanced-http)
|
||||
[](https://opensource.org/licenses/mit-license.php)
|
||||
[](https://travis-ci.org/silkimen/cordova-plugin-advanced-http)
|
||||
|
||||
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).
|
||||
|
||||
## Updates
|
||||
|
||||
Please check [CHANGELOG.md](CHANGELOG.md) for details about updating to a new version.
|
||||
|
||||
## Installation
|
||||
|
||||
The plugin conforms to the Cordova plugin specification, it can be installed
|
||||
using the Cordova / Phonegap command line interface.
|
||||
|
||||
phonegap plugin add https://github.com/wymsee/cordova-HTTP.git
|
||||
```shell
|
||||
phonegap plugin add cordova-plugin-advanced-http
|
||||
|
||||
cordova plugin add https://github.com/wymsee/cordova-HTTP.git
|
||||
cordova plugin add cordova-plugin-advanced-http
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### AngularJS
|
||||
### Plain Cordova
|
||||
|
||||
This plugin creates a cordovaHTTP service inside of a cordovaHTTP module. You must load the module when you create your app's module.
|
||||
This plugin registers a global object located at `cordova.plugin.http`.
|
||||
|
||||
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.
|
||||
### With Ionic-native wrapper
|
||||
|
||||
### Not AngularJS
|
||||
Check the [Ionic docs](https://ionicframework.com/docs/native/http/) for how to use this plugin with Ionic-native.
|
||||
|
||||
This plugin registers a `cordovaHTTP` global on window
|
||||
## Synchronous Functions
|
||||
|
||||
### getBasicAuthHeader
|
||||
This returns an object representing a basic HTTP Authorization header of the form `{'Authorization': 'Basic base64encodedusernameandpassword'}`
|
||||
|
||||
## Functions
|
||||
|
||||
All available functions are documented below. Every function takes a success and error callback function as the last 2 arguments.
|
||||
```js
|
||||
var header = cordova.plugin.http.getBasicAuthHeader('user', 'password');
|
||||
```
|
||||
|
||||
### useBasicAuth
|
||||
This sets up all future requests to use Basic HTTP authentication with the given username and password.
|
||||
|
||||
cordovaHTTP.useBasicAuth("user", "password", function() {
|
||||
console.log('success!');
|
||||
}, function() {
|
||||
console.log('error :(');
|
||||
});
|
||||
|
||||
### setHeader
|
||||
Set a header for all future requests. Takes a header and a value.
|
||||
```js
|
||||
cordova.plugin.http.useBasicAuth('user', 'password');
|
||||
```
|
||||
|
||||
cordovaHTTP.setHeader("Header", "Value", function() {
|
||||
console.log('success!');
|
||||
}, function() {
|
||||
console.log('error :(');
|
||||
});
|
||||
### 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).
|
||||
|
||||
### enableSSLPinning
|
||||
Enable or disable SSL pinning. 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.
|
||||
```js
|
||||
cordova.plugin.http.setHeader('Hostname', 'Header', 'Value');
|
||||
```
|
||||
|
||||
As an alternative, you can store your .cer files in the www/certificates folder.
|
||||
You can also define headers used for all hosts by using wildcard character "\*" or providing only two params.
|
||||
|
||||
cordovaHTTP.enableSSLPinning(true, function() {
|
||||
console.log('success!');
|
||||
}, function() {
|
||||
console.log('error :(');
|
||||
});
|
||||
|
||||
### acceptAllCerts
|
||||
Accept all SSL certificates. Or disable accepting all certificates.
|
||||
```js
|
||||
cordova.plugin.http.setHeader('*', 'Header', 'Value');
|
||||
cordova.plugin.http.setHeader('Header', 'Value');
|
||||
```
|
||||
|
||||
The hostname also includes the port number. If you define a header for `www.example.com` it will not match following URL `http://www.example.com:8080`.
|
||||
|
||||
```js
|
||||
// will match http://www.example.com/...
|
||||
cordova.plugin.http.setHeader('www.example.com', 'Header', 'Value');
|
||||
|
||||
// will match http://www.example.com:8080/...
|
||||
cordova.plugin.http.setHeader('www.example.com:8080', 'Header', 'Value');
|
||||
```
|
||||
|
||||
### 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
|
||||
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")
|
||||
|
||||
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.
|
||||
|
||||
### setRequestTimeout
|
||||
Set how long to wait for a request to respond, in seconds.
|
||||
|
||||
```js
|
||||
cordova.plugin.http.setRequestTimeout(5.0);
|
||||
```
|
||||
|
||||
### getCookieString
|
||||
Returns saved cookies (as string) matching given URL.
|
||||
|
||||
```js
|
||||
cordova.plugin.http.getCookieString(url);
|
||||
```
|
||||
|
||||
### setCookie
|
||||
Add a custom cookie. Takes a URL, a cookie string and an options object. See [ToughCookie documentation](https://github.com/salesforce/tough-cookie#setcookiecookieorstring-currenturl-options-cberrcookie) for allowed options.
|
||||
|
||||
```js
|
||||
cordova.plugin.http.setCookie(url, cookie, options);
|
||||
```
|
||||
|
||||
### clearCookies
|
||||
Clear the cookie store.
|
||||
|
||||
```js
|
||||
cordova.plugin.http.clearCookies();
|
||||
```
|
||||
|
||||
## Asynchronous Functions
|
||||
These functions all take success and error callbacks as their last 2 arguments.
|
||||
|
||||
### setServerTrustMode<a name="setServerTrustMode"></a>
|
||||
Set server trust mode, being one of the following values:
|
||||
|
||||
* `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
|
||||
|
||||
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
|
||||
// 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 :(');
|
||||
});
|
||||
```
|
||||
|
||||
### setSSLCertMode (deprecated)
|
||||
This function was deprecated in 2.0.8. Use ["setServerTrustMode"](#setServerTrustMode) instead.
|
||||
|
||||
### enableSSLPinning (obsolete)
|
||||
This function was removed in 2.0.0. Use ["setServerTrustMode"](#setServerTrustMode) to enable SSL pinning (mode "pinned").
|
||||
|
||||
### acceptAllCerts (obsolete)
|
||||
This function was removed in 2.0.0. Use ["setServerTrustMode"](#setServerTrustMode) to disable checking certs (mode "nocheck").
|
||||
|
||||
### validateDomainName (obsolete)
|
||||
This function was removed in v1.6.2. Domain name validation is disabled automatically when you set server trust mode to "nocheck".
|
||||
|
||||
### disableRedirect
|
||||
If set to `true`, it won't follow redirects automatically. This defaults to false.
|
||||
|
||||
```js
|
||||
cordova.plugin.http.disableRedirect(true, function() {
|
||||
console.log('success!');
|
||||
}, function() {
|
||||
console.log('error :(');
|
||||
});
|
||||
```
|
||||
|
||||
### removeCookies
|
||||
Remove all cookies associated with a given URL.
|
||||
|
||||
```js
|
||||
cordova.plugin.http.removeCookies(url, callback);
|
||||
```
|
||||
|
||||
### sendRequest
|
||||
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.
|
||||
|
||||
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`, `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
|
||||
* `timeout`: timeout value for the request in seconds, defaults to global timeout value
|
||||
* `headers`: headers object (key value pair), will be merged with global values
|
||||
* `filePath`: filePath to be used during upload and download see [uploadFile](#uploadFile) and [downloadFile](#downloadFile) for detailed information
|
||||
* `name`: name 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);
|
||||
});
|
||||
```
|
||||
|
||||
cordovaHTTP.acceptAllCerts(true, function() {
|
||||
console.log('success!');
|
||||
}, function() {
|
||||
console.log('error :(');
|
||||
});
|
||||
|
||||
### post<a name="post"></a>
|
||||
Execute a POST request. Takes a URL, parameters, and headers.
|
||||
Execute a POST request. Takes a URL, data, and headers.
|
||||
|
||||
#### success
|
||||
The success function receives a response object with 2 properties: status and data. Status is the HTTP response code and data is the response from the server as a string. Here's a quick example:
|
||||
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:
|
||||
|
||||
```js
|
||||
{
|
||||
status: 200,
|
||||
data: '{"id": 12, "message": "test"}',
|
||||
url: 'http://example.net/rest'
|
||||
headers: {
|
||||
'content-length': '247'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
{
|
||||
status: 200,
|
||||
data: "{'id': 12, 'message': 'test'}"
|
||||
}
|
||||
|
||||
Most apis will return JSON meaning you'll want to parse the data like in the example below:
|
||||
|
||||
cordovaHTTP.post("https://google.com/, {
|
||||
id: 12,
|
||||
message: "test"
|
||||
}, { Authorization: "OAuth2: token" }, function(response) {
|
||||
// prints 200
|
||||
console.log(response.status);
|
||||
try {
|
||||
response.data = JSON.parse(response.data);
|
||||
// prints test
|
||||
console.log(response.data.message);
|
||||
} catch(e) {
|
||||
console.error("JSON parsing error");
|
||||
}
|
||||
}, function(response) {
|
||||
// prints 403
|
||||
console.log(response.status);
|
||||
|
||||
//prints Permission denied
|
||||
console.log(response.error);
|
||||
});
|
||||
|
||||
|
||||
#### failure
|
||||
The error function receives a response object with 2 properties: status and error. Status is the HTTP response code. Error is the error response from the server as a string. Here's a quick example:
|
||||
```js
|
||||
cordova.plugin.http.post('https://google.com/', {
|
||||
id: 12,
|
||||
message: 'test'
|
||||
}, { Authorization: 'OAuth2: token' }, function(response) {
|
||||
// prints 200
|
||||
console.log(response.status);
|
||||
try {
|
||||
response.data = JSON.parse(response.data);
|
||||
// prints test
|
||||
console.log(response.data.message);
|
||||
} catch(e) {
|
||||
console.error('JSON parsing error');
|
||||
}
|
||||
}, function(response) {
|
||||
// prints 403
|
||||
console.log(response.status);
|
||||
|
||||
{
|
||||
status: 403,
|
||||
error: "Permission denied"
|
||||
}
|
||||
|
||||
### get
|
||||
//prints Permission denied
|
||||
console.log(response.error);
|
||||
});
|
||||
```
|
||||
|
||||
#### failure
|
||||
The error function receives a response object with 4 properties: status, error, url, and headers (url and headers being optional). **status** is the HTTP response code as numeric value. **error** is the error 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:
|
||||
|
||||
```js
|
||||
{
|
||||
status: 403,
|
||||
error: 'Permission denied',
|
||||
url: 'http://example.net/noperm'
|
||||
headers: {
|
||||
'content-length': '247'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 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.
|
||||
|
||||
cordovaHTTP.get("https://google.com/, {
|
||||
id: 12,
|
||||
message: "test"
|
||||
}, { Authorization: "OAuth2: token" }, function(response) {
|
||||
console.log(response.status);
|
||||
}, function(response) {
|
||||
console.error(response.error);
|
||||
});
|
||||
|
||||
### uploadFile
|
||||
```js
|
||||
cordova.plugin.http.get('https://google.com/', {
|
||||
id: '12',
|
||||
message: 'test'
|
||||
}, { Authorization: 'OAuth2: token' }, function(response) {
|
||||
console.log(response.status);
|
||||
}, function(response) {
|
||||
console.error(response.error);
|
||||
});
|
||||
```
|
||||
|
||||
### 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<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<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<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<a name="uploadFile"></a>
|
||||
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.
|
||||
|
||||
cordovaHTTP.uploadFile("https://google.com/, {
|
||||
id: 12,
|
||||
message: "test"
|
||||
}, { Authorization: "OAuth2: token" }, "file:///somepicture.jpg", "picture", function(response) {
|
||||
console.log(response.status);
|
||||
}, function(response) {
|
||||
console.error(response.error);
|
||||
});
|
||||
|
||||
### downloadFile
|
||||
```js
|
||||
cordova.plugin.http.uploadFile("https://google.com/", {
|
||||
id: '12',
|
||||
message: 'test'
|
||||
}, { Authorization: 'OAuth2: token' }, 'file:///somepicture.jpg', 'picture', function(response) {
|
||||
console.log(response.status);
|
||||
}, function(response) {
|
||||
console.error(response.error);
|
||||
});
|
||||
```
|
||||
|
||||
### 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).
|
||||
|
||||
cordovaHTTP.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) {
|
||||
console.error(response.error);
|
||||
});
|
||||
```js
|
||||
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) {
|
||||
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)
|
||||
|
||||
## Libraries
|
||||
|
||||
This plugin utilizes some awesome open source networking libraries. These are both MIT licensed:
|
||||
This plugin utilizes some awesome open source libraries:
|
||||
|
||||
- iOS - [AFNetworking](https://github.com/AFNetworking/AFNetworking)
|
||||
- Android - [http-request](https://github.com/kevinsawicki/http-request)
|
||||
- iOS - [AFNetworking](https://github.com/AFNetworking/AFNetworking) (MIT licensed)
|
||||
- Android - [http-request](https://github.com/kevinsawicki/http-request) (MIT licensed)
|
||||
- Cookie handling - [tough-cookie](https://github.com/salesforce/tough-cookie) (BSD-3-Clause licensed)
|
||||
|
||||
We made a few modifications to http-request. They can be found in a separate repo here: https://github.com/wymsee/http-request
|
||||
We made a few modifications to the networking libraries.
|
||||
|
||||
## Limitations
|
||||
## Contribute & Develop
|
||||
|
||||
This plugin isn't equivalent to using XMLHttpRequest or Ajax calls in Javascript.
|
||||
For instance, the following features are currently not supported:
|
||||
|
||||
- cookies support (a cookie set by a request isn't sent in subsequent requests)
|
||||
- read content of error responses (only the HTTP status code and message are returned)
|
||||
- read returned HTTP headers (e.g. in case security tokens are returned as headers)
|
||||
|
||||
Take this into account when using this plugin into your application.
|
||||
|
||||
## License
|
||||
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2014 Wymsee, Inc
|
||||
|
||||
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.
|
||||
We've set up a separate document for our [contribution guidelines](CONTRIBUTING.md).
|
||||
|
||||
6345
package-lock.json
generated
Normal file
6345
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
70
package.json
Normal file
70
package.json
Normal file
@@ -0,0 +1,70 @@
|
||||
{
|
||||
"name": "cordova-plugin-advanced-http",
|
||||
"version": "2.0.8",
|
||||
"description": "Cordova / Phonegap plugin for communicating with HTTP servers using SSL pinning",
|
||||
"scripts": {
|
||||
"updatecert": "node ./scripts/update-test-cert.js",
|
||||
"buildbrowser": "./scripts/build-test-app.sh --browser",
|
||||
"testandroid": "npm run updatecert && ./scripts/build-test-app.sh --android --emulator && ./scripts/test-app.sh --android --emulator",
|
||||
"testios": "npm run updatecert && ./scripts/build-test-app.sh --ios --emulator && ./scripts/test-app.sh --ios --emulator",
|
||||
"testapp": "npm run testandroid && npm run testios",
|
||||
"testjs": "mocha ./test/js-specs.js",
|
||||
"test": "npm run testjs && npm run testapp",
|
||||
"release": "npm run test && ./scripts/release.sh"
|
||||
},
|
||||
"cordova": {
|
||||
"id": "cordova-plugin-advanced-http",
|
||||
"platforms": [
|
||||
"ios",
|
||||
"android"
|
||||
]
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/silkimen/cordova-plugin-advanced-http.git"
|
||||
},
|
||||
"keywords": [
|
||||
"cordova",
|
||||
"device",
|
||||
"ecosystem:cordova",
|
||||
"cordova-ios",
|
||||
"cordova-android",
|
||||
"ssl",
|
||||
"tls"
|
||||
],
|
||||
"engines": [
|
||||
{
|
||||
"name": "cordova",
|
||||
"version": ">=4.0.0"
|
||||
}
|
||||
],
|
||||
"author": "Wymsee",
|
||||
"contributors": [
|
||||
"devgeeks",
|
||||
"EddyVerbruggen",
|
||||
"mbektchiev",
|
||||
"denisbabineau",
|
||||
"andrey-tsaplin",
|
||||
"pvsaikrishna",
|
||||
"cvillerm",
|
||||
"hideov",
|
||||
"Mobisys"
|
||||
],
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/silkimen/cordova-plugin-advanced-http/issues"
|
||||
},
|
||||
"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": "8.1.2",
|
||||
"mocha": "4.0.0",
|
||||
"mock-require": "2.0.2",
|
||||
"mz": "2.7.0",
|
||||
"umd-tough-cookie": "2.4.3",
|
||||
"wd": "1.4.1",
|
||||
"xml2js": "0.4.19"
|
||||
}
|
||||
}
|
||||
172
plugin.xml
172
plugin.xml
@@ -1,88 +1,90 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<plugin xmlns="http://www.phonegap.com/ns/plugins/1.0"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
id="com.synconset.cordovaHTTP"
|
||||
version="0.1.4">
|
||||
|
||||
<name>SSL Pinning</name>
|
||||
|
||||
<description>
|
||||
<?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="2.0.8">
|
||||
<name>Advanced HTTP plugin</name>
|
||||
<description>
|
||||
Cordova / Phonegap plugin for communicating with HTTP servers using SSL pinning
|
||||
</description>
|
||||
|
||||
<engines>
|
||||
<engine name="cordova" version=">=3.0.0" />
|
||||
</engines>
|
||||
|
||||
<dependency id="org.apache.cordova.file" url="https://github.com/apache/cordova-plugin-file" commit="r0.2.5" />
|
||||
|
||||
<js-module src="www/cordovaHTTP.js" name="CordovaHttpPlugin">
|
||||
<clobbers target="plugins.CordovaHttpPlugin" />
|
||||
</description>
|
||||
<engines>
|
||||
<engine name="cordova" version=">=4.0.0"/>
|
||||
</engines>
|
||||
<dependency id="cordova-plugin-file" version=">=2.0.0"/>
|
||||
<js-module src="www/cookie-handler.js" name="cookie-handler"/>
|
||||
<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/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>
|
||||
<platform name="ios">
|
||||
<config-file target="config.xml" parent="/*">
|
||||
<feature name="CordovaHttpPlugin">
|
||||
<param name="ios-package" value="CordovaHttpPlugin"/>
|
||||
</feature>
|
||||
</config-file>
|
||||
<header-file src="src/ios/CordovaHttpPlugin.h"/>
|
||||
<header-file src="src/ios/TextResponseSerializer.h"/>
|
||||
<header-file src="src/ios/TextRequestSerializer.h"/>
|
||||
<header-file src="src/ios/AFNetworking/AFHTTPSessionManager.h"/>
|
||||
<header-file src="src/ios/AFNetworking/AFNetworking.h"/>
|
||||
<header-file src="src/ios/AFNetworking/AFNetworkReachabilityManager.h"/>
|
||||
<header-file src="src/ios/AFNetworking/AFSecurityPolicy.h"/>
|
||||
<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/TextResponseSerializer.m"/>
|
||||
<source-file src="src/ios/TextRequestSerializer.m"/>
|
||||
<source-file src="src/ios/AFNetworking/AFHTTPSessionManager.m"/>
|
||||
<source-file src="src/ios/AFNetworking/AFNetworkReachabilityManager.m"/>
|
||||
<source-file src="src/ios/AFNetworking/AFSecurityPolicy.m"/>
|
||||
<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.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/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/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/OkConnectionFactory.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"/>
|
||||
<framework src="com.squareup.okhttp3:okhttp-urlconnection:3.10.0"/>
|
||||
</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>
|
||||
|
||||
<!-- ios -->
|
||||
<platform name="ios">
|
||||
<config-file target="config.xml" parent="/*">
|
||||
<feature name="CordovaHttpPlugin">
|
||||
<param name="ios-package" value="CordovaHttpPlugin"/>
|
||||
</feature>
|
||||
</config-file>
|
||||
|
||||
<header-file src="src/ios/CordovaHttpPlugin.h" />
|
||||
<source-file src="src/ios/CordovaHttpPlugin.m" />
|
||||
|
||||
<header-file src="src/ios/HTTPManager.h" />
|
||||
<source-file src="src/ios/HTTPManager.m" />
|
||||
|
||||
<header-file src="src/ios/TextResponseSerializer.h" />
|
||||
<source-file src="src/ios/TextResponseSerializer.m" />
|
||||
|
||||
<header-file src="src/ios/AFNetworking/AFHTTPRequestOperation.h" />
|
||||
<source-file src="src/ios/AFNetworking/AFHTTPRequestOperation.m" />
|
||||
|
||||
<header-file src="src/ios/AFNetworking/AFHTTPRequestOperationManager.h" />
|
||||
<source-file src="src/ios/AFNetworking/AFHTTPRequestOperationManager.m" />
|
||||
|
||||
<header-file src="src/ios/AFNetworking/AFHTTPSessionManager.h" />
|
||||
<source-file src="src/ios/AFNetworking/AFHTTPSessionManager.m" />
|
||||
|
||||
<header-file src="src/ios/AFNetworking/AFNetworking.h" />
|
||||
|
||||
<header-file src="src/ios/AFNetworking/AFNetworkReachabilityManager.h" />
|
||||
<source-file src="src/ios/AFNetworking/AFNetworkReachabilityManager.m" />
|
||||
|
||||
<header-file src="src/ios/AFNetworking/AFSecurityPolicy.h" />
|
||||
<source-file src="src/ios/AFNetworking/AFSecurityPolicy.m" />
|
||||
|
||||
<header-file src="src/ios/AFNetworking/AFURLConnectionOperation.h" />
|
||||
<source-file src="src/ios/AFNetworking/AFURLConnectionOperation.m" />
|
||||
|
||||
<header-file src="src/ios/AFNetworking/AFURLRequestSerialization.h" />
|
||||
<source-file src="src/ios/AFNetworking/AFURLRequestSerialization.m" />
|
||||
|
||||
<header-file src="src/ios/AFNetworking/AFURLResponseSerialization.h" />
|
||||
<source-file src="src/ios/AFNetworking/AFURLResponseSerialization.m" />
|
||||
|
||||
<header-file src="src/ios/AFNetworking/AFURLSessionManager.h" />
|
||||
<source-file src="src/ios/AFNetworking/AFURLSessionManager.m" />
|
||||
|
||||
<framework src="Security.framework" />
|
||||
</platform>
|
||||
|
||||
<!--android -->
|
||||
<platform name="android">
|
||||
<config-file target="res/xml/config.xml" parent="/*">
|
||||
<feature name="CordovaHttpPlugin">
|
||||
<param name="android-package" value="com.synconset.CordovaHttpPlugin"/>
|
||||
</feature>
|
||||
</config-file>
|
||||
|
||||
<source-file src="src/android/com/synconset/CordovaHTTP/CordovaHttp.java" target-dir="src/com/synconset" />
|
||||
<source-file src="src/android/com/synconset/CordovaHTTP/CordovaHttpGet.java" target-dir="src/com/synconset" />
|
||||
<source-file src="src/android/com/synconset/CordovaHTTP/CordovaHttpPost.java" target-dir="src/com/synconset" />
|
||||
<source-file src="src/android/com/synconset/CordovaHTTP/CordovaHttpUpload.java" target-dir="src/com/synconset" />
|
||||
<source-file src="src/android/com/synconset/CordovaHTTP/CordovaHttpDownload.java" target-dir="src/com/synconset" />
|
||||
<source-file src="src/android/com/synconset/CordovaHTTP/CordovaHttpPlugin.java" target-dir="src/com/synconset" />
|
||||
<source-file src="src/android/com/synconset/CordovaHTTP/HttpRequest.java" target-dir="src/com/github/kevinsawicki/http" />
|
||||
</platform>
|
||||
</platform>
|
||||
</plugin>
|
||||
47
scripts/build-test-app.sh
Executable file
47
scripts/build-test-app.sh
Executable file
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
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/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 $WORKINGCOPY
|
||||
$CDV build $PLATFORM --$TARGET --buildConfig build.json
|
||||
13
scripts/release.sh
Executable file
13
scripts/release.sh
Executable file
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )"; cd ..; pwd )"
|
||||
|
||||
pushd $ROOT
|
||||
VERSION=$(node -e "console.log(require('./package.json').version)")
|
||||
./scripts/update-tough-cookie.sh
|
||||
node ./scripts/update-plugin-xml.js $VERSION
|
||||
git commit -a -m "release v$VERSION"
|
||||
git tag "v$VERSION"
|
||||
npm publish
|
||||
popd
|
||||
14
scripts/test-app.sh
Executable file
14
scripts/test-app.sh
Executable file
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
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.";
|
||||
exit 0;
|
||||
fi
|
||||
|
||||
printf 'Running e2e tests\n'
|
||||
pushd $ROOT
|
||||
./node_modules/.bin/mocha ./test/e2e-tooling/test.js "$@"
|
||||
popd
|
||||
31
scripts/update-plugin-xml.js
Normal file
31
scripts/update-plugin-xml.js
Normal file
@@ -0,0 +1,31 @@
|
||||
const args = process.argv.slice(2);
|
||||
const fs = require('mz/fs');
|
||||
const path = require('path');
|
||||
const xml2js = require('xml2js');
|
||||
const xmlPath = path.join(__dirname, '..', 'plugin.xml');
|
||||
|
||||
const parse = xml => new Promise((resolve, reject) => {
|
||||
const parser = new xml2js.Parser();
|
||||
|
||||
parser.parseString(xml, (error, result) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(result);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const stringify = obj => {
|
||||
const builder = new xml2js.Builder();
|
||||
|
||||
return builder.buildObject(obj);
|
||||
};
|
||||
|
||||
fs.readFile(xmlPath, 'utf-8')
|
||||
.then(xml => parse(xml))
|
||||
.then(parsed => {
|
||||
parsed.plugin.$.version = args[0];
|
||||
|
||||
return fs.writeFile(xmlPath, stringify(parsed));
|
||||
});
|
||||
42
scripts/update-test-cert.js
Normal file
42
scripts/update-test-cert.js
Normal file
@@ -0,0 +1,42 @@
|
||||
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 test certificate from ${SOURCE_HOST}`);
|
||||
|
||||
getCert(SOURCE_HOST)
|
||||
.then(cert => {
|
||||
fs.writeFileSync(TARGET_PATH, cert.raw);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(`Updating test cert failed: ${error}`);
|
||||
process.exit(1);
|
||||
});
|
||||
8
scripts/update-tough-cookie.sh
Executable file
8
scripts/update-tough-cookie.sh
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )"; cd ..; pwd )"
|
||||
|
||||
cd $ROOT
|
||||
npm i
|
||||
cp node_modules/umd-tough-cookie/lib/umd-tough-cookie.js www/umd-tough-cookie.js
|
||||
30
scripts/upload-artifact.sh
Executable file
30
scripts/upload-artifact.sh
Executable file
@@ -0,0 +1,30 @@
|
||||
#!/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 $SAUCE_USERNAME ] || [ -z $SAUCE_ACCESS_KEY ]; then
|
||||
echo "Skipping uploading artifact, because Saucelabs credentials are not set.";
|
||||
exit 0;
|
||||
fi
|
||||
|
||||
if [ $PLATFORM = "android" ]; then
|
||||
curl -u $SAUCE_USERNAME:$SAUCE_ACCESS_KEY \
|
||||
-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/app/build/outputs/apk/debug/app-debug.apk
|
||||
else
|
||||
rm -rf $TEMP/HttpDemo.app.zip
|
||||
pushd $TEMP/platforms/ios/build/emulator
|
||||
zip -r $TEMP/HttpDemo.app.zip ./HttpDemo.app
|
||||
popd
|
||||
|
||||
curl -u $SAUCE_USERNAME:$SAUCE_ACCESS_KEY \
|
||||
-X POST \
|
||||
-H "Content-Type: application/octet-stream" \
|
||||
https://saucelabs.com/rest/v1/storage/$SAUCE_USERNAME/HttpDemo.app.zip?overwrite=true \
|
||||
--data-binary @$TEMP/HttpDemo.app.zip
|
||||
fi
|
||||
72
src/android/com/silkimen/cordovahttp/CordovaClientAuth.java
Normal file
72
src/android/com/silkimen/cordovahttp/CordovaClientAuth.java
Normal file
@@ -0,0 +1,72 @@
|
||||
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.security.KeyStore;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import javax.net.ssl.KeyManager;
|
||||
|
||||
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 filePath;
|
||||
private Activity activity;
|
||||
private Context context;
|
||||
private TLSConfiguration tlsConfiguration;
|
||||
private CallbackContext callbackContext;
|
||||
|
||||
public CordovaClientAuth(final String mode, final String filePath, final Activity activity, final Context context,
|
||||
final TLSConfiguration configContainer, final CallbackContext callbackContext) {
|
||||
|
||||
this.mode = mode;
|
||||
this.filePath = filePath;
|
||||
this.activity = activity;
|
||||
this.tlsConfiguration = configContainer;
|
||||
this.context = context;
|
||||
this.callbackContext = callbackContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if ("systemstore".equals(this.mode)) {
|
||||
KeyChain.choosePrivateKeyAlias(this.activity, this, null, null, null, -1, null);
|
||||
} else if ("file".equals(this.mode)) {
|
||||
this.callbackContext.error("Not implemented, yet");
|
||||
} else {
|
||||
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();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Couldn't load private key and certificate pair for authentication", e);
|
||||
this.callbackContext.error("Couldn't load private key and certificate pair for authentication");
|
||||
}
|
||||
}
|
||||
}
|
||||
174
src/android/com/silkimen/cordovahttp/CordovaHttpBase.java
Normal file
174
src/android/com/silkimen/cordovahttp/CordovaHttpBase.java
Normal file
@@ -0,0 +1,174 @@
|
||||
package com.silkimen.cordovahttp;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
||||
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.OkConnectionFactory;
|
||||
import com.silkimen.http.TLSConfiguration;
|
||||
|
||||
import org.apache.cordova.CallbackContext;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
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 Object data;
|
||||
protected JSONObject headers;
|
||||
protected int timeout;
|
||||
protected boolean followRedirects;
|
||||
protected TLSConfiguration tlsConfiguration;
|
||||
protected CallbackContext callbackContext;
|
||||
|
||||
public CordovaHttpBase(String method, String url, String serializer, Object data, JSONObject headers, int timeout,
|
||||
boolean followRedirects, TLSConfiguration tlsConfiguration, CallbackContext callbackContext) {
|
||||
|
||||
this.method = method;
|
||||
this.url = url;
|
||||
this.serializer = serializer;
|
||||
this.data = data;
|
||||
this.headers = headers;
|
||||
this.timeout = timeout;
|
||||
this.followRedirects = followRedirects;
|
||||
this.tlsConfiguration = tlsConfiguration;
|
||||
this.callbackContext = callbackContext;
|
||||
}
|
||||
|
||||
public CordovaHttpBase(String method, String url, JSONObject headers, int timeout, boolean followRedirects,
|
||||
TLSConfiguration tlsConfiguration, CallbackContext callbackContext) {
|
||||
|
||||
this.method = method;
|
||||
this.url = url;
|
||||
this.headers = headers;
|
||||
this.timeout = timeout;
|
||||
this.followRedirects = followRedirects;
|
||||
this.tlsConfiguration = tlsConfiguration;
|
||||
this.callbackContext = callbackContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
CordovaHttpResponse response = new CordovaHttpResponse();
|
||||
|
||||
try {
|
||||
HttpRequest request = this.createRequest();
|
||||
this.prepareRequest(request);
|
||||
this.sendBody(request);
|
||||
this.processResponse(request, response);
|
||||
} catch (HttpRequestException e) {
|
||||
if (e.getCause() 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 (e.getCause() 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 (e.getCause() instanceof SocketTimeoutException) {
|
||||
response.setStatus(-4);
|
||||
response.setErrorMessage("Request timed out: " + e.getMessage());
|
||||
Log.w(TAG, "Request timed out", e);
|
||||
} else {
|
||||
response.setStatus(-1);
|
||||
response.setErrorMessage("There was an error with the request: " + e.getCause().getMessage());
|
||||
Log.w(TAG, "Generic request error", e);
|
||||
}
|
||||
} 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);
|
||||
request.setConnectionFactory(new OkConnectionFactory());
|
||||
|
||||
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 ("urlencoded".equals(this.serializer)) {
|
||||
// intentionally left blank, because content type is set in HttpRequest.form()
|
||||
}
|
||||
}
|
||||
|
||||
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 ("urlencoded".equals(this.serializer)) {
|
||||
request.form(JsonUtils.getObjectMap((JSONObject) this.data));
|
||||
}
|
||||
}
|
||||
|
||||
protected void processResponse(HttpRequest request, CordovaHttpResponse response) throws Exception {
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
request.receive(outputStream);
|
||||
|
||||
ByteBuffer rawOutput = ByteBuffer.wrap(outputStream.toByteArray());
|
||||
String decodedBody = HttpBodyDecoder.decodeBody(rawOutput, request.charset());
|
||||
|
||||
response.setStatus(request.code());
|
||||
response.setUrl(request.url().toString());
|
||||
response.setHeaders(request.headers());
|
||||
|
||||
if (request.code() >= 200 && request.code() < 300) {
|
||||
response.setBody(decodedBody);
|
||||
} else {
|
||||
response.setErrorMessage(decodedBody);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
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.CallbackContext;
|
||||
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, CallbackContext callbackContext) {
|
||||
|
||||
super("GET", url, headers, timeout, followRedirects, 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.silkimen.cordovahttp;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
|
||||
import com.silkimen.http.TLSConfiguration;
|
||||
|
||||
import org.apache.cordova.CallbackContext;
|
||||
import org.json.JSONObject;
|
||||
|
||||
class CordovaHttpOperation extends CordovaHttpBase {
|
||||
public CordovaHttpOperation(String method, String url, String serializer, Object data, JSONObject headers,
|
||||
int timeout, boolean followRedirects, TLSConfiguration tlsConfiguration, CallbackContext callbackContext) {
|
||||
|
||||
super(method, url, serializer, data, headers, timeout, followRedirects, tlsConfiguration, callbackContext);
|
||||
}
|
||||
|
||||
public CordovaHttpOperation(String method, String url, JSONObject headers, int timeout, boolean followRedirects,
|
||||
TLSConfiguration tlsConfiguration, CallbackContext callbackContext) {
|
||||
|
||||
super(method, url, headers, timeout, followRedirects, tlsConfiguration, callbackContext);
|
||||
}
|
||||
}
|
||||
167
src/android/com/silkimen/cordovahttp/CordovaHttpPlugin.java
Normal file
167
src/android/com/silkimen/cordovahttp/CordovaHttpPlugin.java
Normal file
@@ -0,0 +1,167 @@
|
||||
package com.silkimen.cordovahttp;
|
||||
|
||||
import java.security.KeyStore;
|
||||
|
||||
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 javax.net.ssl.TrustManagerFactory;
|
||||
|
||||
public class CordovaHttpPlugin extends CordovaPlugin {
|
||||
private static final String TAG = "Cordova-Plugin-HTTP";
|
||||
|
||||
private boolean followRedirects = true;
|
||||
private TLSConfiguration tlsConfiguration;
|
||||
|
||||
@Override
|
||||
public void initialize(CordovaInterface cordova, CordovaWebView webView) {
|
||||
super.initialize(cordova, webView);
|
||||
|
||||
this.tlsConfiguration = new TLSConfiguration();
|
||||
|
||||
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());
|
||||
} 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 ("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 ("uploadFile".equals(action)) {
|
||||
return this.uploadFile(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 ("disableRedirect".equals(action)) {
|
||||
return this.disableRedirect(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;
|
||||
|
||||
CordovaHttpOperation request = new CordovaHttpOperation(method.toUpperCase(), url, headers, timeout,
|
||||
this.followRedirects, this.tlsConfiguration, callbackContext);
|
||||
|
||||
cordova.getThreadPool().execute(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;
|
||||
|
||||
CordovaHttpOperation request = new CordovaHttpOperation(method.toUpperCase(), url, serializer, data, headers,
|
||||
timeout, this.followRedirects, this.tlsConfiguration, callbackContext);
|
||||
|
||||
cordova.getThreadPool().execute(request);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean uploadFile(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
|
||||
String url = args.getString(0);
|
||||
JSONObject headers = args.getJSONObject(1);
|
||||
String filePath = args.getString(2);
|
||||
String uploadName = args.getString(3);
|
||||
int timeout = args.getInt(4) * 1000;
|
||||
|
||||
CordovaHttpUpload upload = new CordovaHttpUpload(url, headers, filePath, uploadName, timeout, this.followRedirects,
|
||||
this.tlsConfiguration, callbackContext);
|
||||
|
||||
cordova.getThreadPool().execute(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;
|
||||
|
||||
CordovaHttpDownload download = new CordovaHttpDownload(url, headers, filePath, timeout, this.followRedirects,
|
||||
this.tlsConfiguration, callbackContext);
|
||||
|
||||
cordova.getThreadPool().execute(download);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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 {
|
||||
CordovaClientAuth runnable = new CordovaClientAuth(args.getString(0), args.getString(1), this.cordova.getActivity(),
|
||||
this.cordova.getContext(), this.tlsConfiguration, callbackContext);
|
||||
|
||||
cordova.getThreadPool().execute(runnable);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean disableRedirect(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
|
||||
this.followRedirects = !args.getBoolean(0);
|
||||
|
||||
callbackContext.success();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.silkimen.cordovahttp;
|
||||
|
||||
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;
|
||||
|
||||
class CordovaHttpResponse {
|
||||
private int status;
|
||||
private String url;
|
||||
private Map<String, List<String>> headers;
|
||||
private String body;
|
||||
private JSONObject fileEntry;
|
||||
private boolean hasFailed;
|
||||
private boolean isFileOperation;
|
||||
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 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.hasFailed) {
|
||||
json.put("error", this.error);
|
||||
} else if (this.isFileOperation) {
|
||||
json.put("headers", new JSONObject(getFilteredHeaders()));
|
||||
json.put("file", this.fileEntry);
|
||||
} else {
|
||||
json.put("headers", new JSONObject(getFilteredHeaders()));
|
||||
json.put("data", this.body);
|
||||
}
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
private Map<String, String> getFilteredHeaders() throws JSONException {
|
||||
Map<String, String> filteredHeaders = new HashMap<String, String>();
|
||||
|
||||
if (this.headers == null || this.headers.isEmpty()) {
|
||||
return filteredHeaders;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
43
src/android/com/silkimen/cordovahttp/CordovaHttpUpload.java
Normal file
43
src/android/com/silkimen/cordovahttp/CordovaHttpUpload.java
Normal file
@@ -0,0 +1,43 @@
|
||||
package com.silkimen.cordovahttp;
|
||||
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
import com.silkimen.http.HttpRequest;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
|
||||
import com.silkimen.http.TLSConfiguration;
|
||||
|
||||
import org.apache.cordova.CallbackContext;
|
||||
import org.json.JSONObject;
|
||||
|
||||
class CordovaHttpUpload extends CordovaHttpBase {
|
||||
private String filePath;
|
||||
private String uploadName;
|
||||
|
||||
public CordovaHttpUpload(String url, JSONObject headers, String filePath, String uploadName, int timeout,
|
||||
boolean followRedirects, TLSConfiguration tlsConfiguration, CallbackContext callbackContext) {
|
||||
|
||||
super("POST", url, headers, timeout, followRedirects, tlsConfiguration, callbackContext);
|
||||
this.filePath = filePath;
|
||||
this.uploadName = uploadName;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void sendBody(HttpRequest request) throws Exception {
|
||||
int filenameIndex = this.filePath.lastIndexOf('/');
|
||||
String filename = this.filePath.substring(filenameIndex + 1);
|
||||
|
||||
int extIndex = this.filePath.lastIndexOf('.');
|
||||
String ext = this.filePath.substring(extIndex + 1);
|
||||
|
||||
MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton();
|
||||
String mimeType = mimeTypeMap.getMimeTypeFromExtension(ext);
|
||||
|
||||
request.part(this.uploadName, filename, mimeType, new File(new URI(this.filePath)));
|
||||
}
|
||||
}
|
||||
124
src/android/com/silkimen/cordovahttp/CordovaServerTrust.java
Normal file
124
src/android/com/silkimen/cordovahttp/CordovaServerTrust.java
Normal 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;
|
||||
}
|
||||
}
|
||||
48
src/android/com/silkimen/http/HttpBodyDecoder.java
Normal file
48
src/android/com/silkimen/http/HttpBodyDecoder.java
Normal file
@@ -0,0 +1,48 @@
|
||||
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(ByteBuffer rawOutput, String charsetName)
|
||||
throws CharacterCodingException, MalformedInputException {
|
||||
|
||||
if (charsetName == null) {
|
||||
return tryDecodeByteBuffer(rawOutput);
|
||||
}
|
||||
|
||||
return decodeByteBuffer(rawOutput, charsetName);
|
||||
}
|
||||
|
||||
private static String tryDecodeByteBuffer(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 static String decodeByteBuffer(ByteBuffer rawOutput, String charsetName)
|
||||
throws CharacterCodingException, MalformedInputException {
|
||||
|
||||
return createCharsetDecoder(charsetName).decode(rawOutput).toString();
|
||||
}
|
||||
|
||||
private static CharsetDecoder createCharsetDecoder(String charsetName) {
|
||||
return Charset.forName(charsetName).newDecoder().onMalformedInput(CodingErrorAction.REPORT)
|
||||
.onUnmappableCharacter(CodingErrorAction.REPORT);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
58
src/android/com/silkimen/http/JsonUtils.java
Normal file
58
src/android/com/silkimen/http/JsonUtils.java
Normal 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;
|
||||
}
|
||||
}
|
||||
57
src/android/com/silkimen/http/KeyChainKeyManager.java
Normal file
57
src/android/com/silkimen/http/KeyChainKeyManager.java
Normal 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();
|
||||
}
|
||||
}
|
||||
26
src/android/com/silkimen/http/OkConnectionFactory.java
Normal file
26
src/android/com/silkimen/http/OkConnectionFactory.java
Normal file
@@ -0,0 +1,26 @@
|
||||
package com.silkimen.http;
|
||||
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.OkUrlFactory;
|
||||
|
||||
import java.net.URL;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URLStreamHandler;
|
||||
import java.net.Proxy;
|
||||
|
||||
public class OkConnectionFactory implements HttpRequest.ConnectionFactory {
|
||||
private final OkHttpClient client = new OkHttpClient();
|
||||
|
||||
public HttpURLConnection create(URL url) {
|
||||
OkUrlFactory urlFactory = new OkUrlFactory(this.client);
|
||||
|
||||
return (HttpURLConnection) urlFactory.open(url);
|
||||
}
|
||||
|
||||
public HttpURLConnection create(URL url, Proxy proxy) {
|
||||
OkHttpClient clientWithProxy = new OkHttpClient.Builder().proxy(proxy).build();
|
||||
OkUrlFactory urlFactory = new OkUrlFactory(clientWithProxy);
|
||||
|
||||
return (HttpURLConnection) urlFactory.open(url);
|
||||
}
|
||||
}
|
||||
69
src/android/com/silkimen/http/TLSConfiguration.java
Normal file
69
src/android/com/silkimen/http/TLSConfiguration.java
Normal file
@@ -0,0 +1,69 @@
|
||||
package com.silkimen.http;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.KeyManager;
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSession;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
import com.silkimen.http.TLSSocketFactory;
|
||||
|
||||
public class TLSConfiguration {
|
||||
private TrustManager[] trustManagers;
|
||||
private KeyManager[] keyManagers;
|
||||
private HostnameVerifier hostnameVerifier;
|
||||
|
||||
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 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());
|
||||
|
||||
if (android.os.Build.VERSION.SDK_INT < 20) {
|
||||
this.socketFactory = new TLSSocketFactory(context);
|
||||
} else {
|
||||
this.socketFactory = context.getSocketFactory();
|
||||
}
|
||||
|
||||
return this.socketFactory;
|
||||
} catch (GeneralSecurityException e) {
|
||||
IOException ioException = new IOException("Security exception occured while configuring TLS context");
|
||||
ioException.initCause(e);
|
||||
throw ioException;
|
||||
}
|
||||
}
|
||||
}
|
||||
63
src/android/com/silkimen/http/TLSSocketFactory.java
Normal file
63
src/android/com/silkimen/http/TLSSocketFactory.java
Normal file
@@ -0,0 +1,63 @@
|
||||
package com.silkimen.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 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)) {
|
||||
((SSLSocket) socket).setEnabledProtocols(new String[] { "TLSv1", "TLSv1.1", "TLSv1.2" });
|
||||
}
|
||||
return socket;
|
||||
}
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
/**
|
||||
* A HTTP plugin for Cordova / Phonegap
|
||||
*/
|
||||
package com.synconset;
|
||||
|
||||
import org.apache.cordova.CallbackContext;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.BufferedReader;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.github.kevinsawicki.http.HttpRequest;
|
||||
|
||||
public abstract class CordovaHttp {
|
||||
protected static final String TAG = "CordovaHTTP";
|
||||
protected static final String CHARSET = "UTF-8";
|
||||
|
||||
private static AtomicBoolean sslPinning = new AtomicBoolean(false);
|
||||
private static AtomicBoolean acceptAllCerts = new AtomicBoolean(false);
|
||||
|
||||
private String urlString;
|
||||
private Map<?, ?> params;
|
||||
private Map<String, String> headers;
|
||||
private CallbackContext callbackContext;
|
||||
|
||||
public CordovaHttp(String urlString, Map<?, ?> params, Map<String, String> headers, CallbackContext callbackContext) {
|
||||
this.urlString = urlString;
|
||||
this.params = params;
|
||||
this.headers = headers;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
protected String getUrlString() {
|
||||
return this.urlString;
|
||||
}
|
||||
|
||||
protected Map<?, ?> getParams() {
|
||||
return this.params;
|
||||
}
|
||||
|
||||
protected Map<String, String> getHeaders() {
|
||||
return this.headers;
|
||||
}
|
||||
|
||||
protected CallbackContext getCallbackContext() {
|
||||
return this.callbackContext;
|
||||
}
|
||||
|
||||
protected HttpRequest setupSecurity(HttpRequest request) {
|
||||
if (acceptAllCerts.get()) {
|
||||
request.trustAllCerts();
|
||||
request.trustAllHosts();
|
||||
}
|
||||
if (sslPinning.get()) {
|
||||
request.pinToCerts();
|
||||
}
|
||||
return request;
|
||||
}
|
||||
|
||||
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(500, msg);
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
/**
|
||||
* A HTTP plugin for Cordova / Phonegap
|
||||
*/
|
||||
package com.synconset;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.github.kevinsawicki.http.HttpRequest;
|
||||
import com.github.kevinsawicki.http.HttpRequest.HttpRequestException;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.UnknownHostException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.net.ssl.SSLHandshakeException;
|
||||
|
||||
import org.apache.cordova.CallbackContext;
|
||||
import org.apache.cordova.file.FileUtils;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class CordovaHttpDownload extends CordovaHttp implements Runnable {
|
||||
private String filePath;
|
||||
|
||||
public CordovaHttpDownload(String urlString, Map<?, ?> params, Map<String, String> headers, CallbackContext callbackContext, String filePath) {
|
||||
super(urlString, params, headers, callbackContext);
|
||||
this.filePath = filePath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
HttpRequest request = HttpRequest.get(this.getUrlString(), this.getParams(), true);
|
||||
this.setupSecurity(request);
|
||||
request.acceptCharset(CHARSET);
|
||||
request.headers(this.getHeaders());
|
||||
int code = request.code();
|
||||
|
||||
JSONObject response = new JSONObject();
|
||||
response.put("status", code);
|
||||
if (code >= 200 && code < 300) {
|
||||
URI uri = new URI(filePath);
|
||||
File file = new File(uri);
|
||||
request.receive(file);
|
||||
JSONObject fileEntry = FileUtils.getEntry(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) {
|
||||
if (e.getCause() instanceof UnknownHostException) {
|
||||
this.respondWithError(0, "The host could not be resolved");
|
||||
} else if (e.getCause() instanceof SSLHandshakeException) {
|
||||
this.respondWithError("SSL handshake failed");
|
||||
} else {
|
||||
this.respondWithError("There was an error with the request");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
/**
|
||||
* A HTTP plugin for Cordova / Phonegap
|
||||
*/
|
||||
package com.synconset;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
|
||||
import javax.net.ssl.SSLHandshakeException;
|
||||
|
||||
import org.apache.cordova.CallbackContext;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.github.kevinsawicki.http.HttpRequest;
|
||||
import com.github.kevinsawicki.http.HttpRequest.HttpRequestException;
|
||||
|
||||
public class CordovaHttpGet extends CordovaHttp implements Runnable {
|
||||
public CordovaHttpGet(String urlString, Map<?, ?> params, Map<String, String> headers, CallbackContext callbackContext) {
|
||||
super(urlString, params, headers, callbackContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
HttpRequest request = HttpRequest.get(this.getUrlString(), this.getParams(), true);
|
||||
this.setupSecurity(request);
|
||||
request.acceptCharset(CHARSET);
|
||||
request.headers(this.getHeaders());
|
||||
int code = request.code();
|
||||
String body = request.body(CHARSET);
|
||||
JSONObject response = new JSONObject();
|
||||
response.put("status", code);
|
||||
if (code >= 200 && code < 300) {
|
||||
response.put("data", body);
|
||||
this.getCallbackContext().success(response);
|
||||
} else {
|
||||
response.put("error", body);
|
||||
this.getCallbackContext().error(response);
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
this.respondWithError("There was an error generating the response");
|
||||
} catch (HttpRequestException e) {
|
||||
if (e.getCause() instanceof UnknownHostException) {
|
||||
this.respondWithError(0, "The host could not be resolved");
|
||||
} else if (e.getCause() instanceof SSLHandshakeException) {
|
||||
this.respondWithError("SSL handshake failed");
|
||||
} else {
|
||||
this.respondWithError("There was an error with the request");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,184 +0,0 @@
|
||||
/**
|
||||
* A HTTP plugin for Cordova / Phonegap
|
||||
*/
|
||||
package com.synconset;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Iterator;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
|
||||
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 android.util.Base64;
|
||||
import android.util.Log;
|
||||
|
||||
import com.github.kevinsawicki.http.HttpRequest;
|
||||
|
||||
public class CordovaHttpPlugin extends CordovaPlugin {
|
||||
private static final String TAG = "CordovaHTTP";
|
||||
|
||||
private HashMap<String, String> globalHeaders;
|
||||
|
||||
@Override
|
||||
public void initialize(CordovaInterface cordova, CordovaWebView webView) {
|
||||
super.initialize(cordova, webView);
|
||||
this.globalHeaders = new HashMap<String, String>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean execute(String action, final JSONArray args, final CallbackContext callbackContext) throws JSONException {
|
||||
if (action.equals("get")) {
|
||||
String urlString = args.getString(0);
|
||||
JSONObject params = args.getJSONObject(1);
|
||||
JSONObject headers = args.getJSONObject(2);
|
||||
HashMap<?, ?> paramsMap = this.getMapFromJSONObject(params);
|
||||
HashMap<String, String> headersMap = this.addToMap(this.globalHeaders, headers);
|
||||
CordovaHttpGet get = new CordovaHttpGet(urlString, paramsMap, headersMap, callbackContext);
|
||||
cordova.getThreadPool().execute(get);
|
||||
} else if (action.equals("post")) {
|
||||
String urlString = args.getString(0);
|
||||
JSONObject params = args.getJSONObject(1);
|
||||
JSONObject headers = args.getJSONObject(2);
|
||||
HashMap<?, ?> paramsMap = this.getMapFromJSONObject(params);
|
||||
HashMap<String, String> headersMap = this.addToMap(this.globalHeaders, headers);
|
||||
CordovaHttpPost post = new CordovaHttpPost(urlString, paramsMap, headersMap, callbackContext);
|
||||
cordova.getThreadPool().execute(post);
|
||||
} else if (action.equals("useBasicAuth")) {
|
||||
String username = args.getString(0);
|
||||
String password = args.getString(1);
|
||||
this.useBasicAuth(username, password);
|
||||
callbackContext.success();
|
||||
} 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);
|
||||
} else if (action.equals("setHeader")) {
|
||||
String header = args.getString(0);
|
||||
String value = args.getString(1);
|
||||
this.setHeader(header, value);
|
||||
callbackContext.success();
|
||||
} else if (action.equals("uploadFile")) {
|
||||
String urlString = args.getString(0);
|
||||
JSONObject params = args.getJSONObject(1);
|
||||
JSONObject headers = args.getJSONObject(2);
|
||||
HashMap<?, ?> paramsMap = this.getMapFromJSONObject(params);
|
||||
HashMap<String, String> headersMap = this.addToMap(this.globalHeaders, headers);
|
||||
String filePath = args.getString(3);
|
||||
String name = args.getString(4);
|
||||
CordovaHttpUpload upload = new CordovaHttpUpload(urlString, paramsMap, headersMap, callbackContext, filePath, name);
|
||||
cordova.getThreadPool().execute(upload);
|
||||
} else if (action.equals("downloadFile")) {
|
||||
String urlString = args.getString(0);
|
||||
JSONObject params = args.getJSONObject(1);
|
||||
JSONObject headers = args.getJSONObject(2);
|
||||
HashMap<?, ?> paramsMap = this.getMapFromJSONObject(params);
|
||||
HashMap<String, String> headersMap = this.addToMap(this.globalHeaders, headers);
|
||||
String filePath = args.getString(3);
|
||||
CordovaHttpDownload download = new CordovaHttpDownload(urlString, paramsMap, headersMap, callbackContext, filePath);
|
||||
cordova.getThreadPool().execute(download);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void useBasicAuth(String username, String password) {
|
||||
String loginInfo = username + ":" + password;
|
||||
loginInfo = "Basic " + Base64.encodeToString(loginInfo.getBytes(), Base64.NO_WRAP);
|
||||
this.globalHeaders.put("Authorization", loginInfo);
|
||||
}
|
||||
|
||||
private void setHeader(String header, String value) {
|
||||
this.globalHeaders.put(header, value);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
private HashMap<String, String> addToMap(HashMap<String, String> map, JSONObject object) throws JSONException {
|
||||
HashMap<String, String> newMap = (HashMap<String, String>)map.clone();
|
||||
Iterator<?> i = object.keys();
|
||||
|
||||
while (i.hasNext()) {
|
||||
String key = (String)i.next();
|
||||
newMap.put(key, object.getString(key));
|
||||
}
|
||||
return newMap;
|
||||
}
|
||||
|
||||
private 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();
|
||||
map.put(key, object.get(key));
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
/**
|
||||
* A HTTP plugin for Cordova / Phonegap
|
||||
*/
|
||||
package com.synconset;
|
||||
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.cordova.CallbackContext;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import javax.net.ssl.SSLHandshakeException;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.github.kevinsawicki.http.HttpRequest;
|
||||
import com.github.kevinsawicki.http.HttpRequest.HttpRequestException;
|
||||
|
||||
public class CordovaHttpPost extends CordovaHttp implements Runnable {
|
||||
public CordovaHttpPost(String urlString, Map<?, ?> params, Map<String, String> headers, CallbackContext callbackContext) {
|
||||
super(urlString, params, headers, callbackContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
HttpRequest request = HttpRequest.post(this.getUrlString());
|
||||
this.setupSecurity(request);
|
||||
request.acceptCharset(CHARSET);
|
||||
request.headers(this.getHeaders());
|
||||
request.form(this.getParams());
|
||||
int code = request.code();
|
||||
String body = request.body(CHARSET);
|
||||
JSONObject response = new JSONObject();
|
||||
response.put("status", code);
|
||||
if (code >= 200 && code < 300) {
|
||||
response.put("data", body);
|
||||
this.getCallbackContext().success(response);
|
||||
} else {
|
||||
response.put("error", body);
|
||||
this.getCallbackContext().error(response);
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
this.respondWithError("There was an error generating the response");
|
||||
} catch (HttpRequestException e) {
|
||||
if (e.getCause() instanceof UnknownHostException) {
|
||||
this.respondWithError(0, "The host could not be resolved");
|
||||
} else if (e.getCause() instanceof SSLHandshakeException) {
|
||||
this.respondWithError("SSL handshake failed");
|
||||
} else {
|
||||
this.respondWithError("There was an error with the request");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
/**
|
||||
* A HTTP plugin for Cordova / Phonegap
|
||||
*/
|
||||
package com.synconset;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.UnknownHostException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
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.util.Log;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
import com.github.kevinsawicki.http.HttpRequest;
|
||||
import com.github.kevinsawicki.http.HttpRequest.HttpRequestException;
|
||||
|
||||
public class CordovaHttpUpload extends CordovaHttp implements Runnable {
|
||||
private String filePath;
|
||||
private String name;
|
||||
|
||||
public CordovaHttpUpload(String urlString, Map<?, ?> params, Map<String, String> headers, CallbackContext callbackContext, String filePath, String name) {
|
||||
super(urlString, params, headers, callbackContext);
|
||||
this.filePath = filePath;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
HttpRequest request = HttpRequest.post(this.getUrlString());
|
||||
this.setupSecurity(request);
|
||||
request.acceptCharset(CHARSET);
|
||||
request.headers(this.getHeaders());
|
||||
URI uri = new URI(filePath);
|
||||
int index = filePath.lastIndexOf('/');
|
||||
String filename = filePath.substring(index + 1);
|
||||
index = filePath.lastIndexOf('.');
|
||||
String ext = filePath.substring(index + 1);
|
||||
MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton();
|
||||
String mimeType = mimeTypeMap.getMimeTypeFromExtension(ext);
|
||||
request.part(this.name, filename, mimeType, new File(uri));
|
||||
|
||||
Set<?> set = (Set<?>)this.getParams().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;
|
||||
}
|
||||
}
|
||||
|
||||
int code = request.code();
|
||||
String body = request.body(CHARSET);
|
||||
|
||||
JSONObject response = new JSONObject();
|
||||
response.put("status", code);
|
||||
if (code >= 200 && code < 300) {
|
||||
response.put("data", body);
|
||||
this.getCallbackContext().success(response);
|
||||
} else {
|
||||
response.put("error", body);
|
||||
this.getCallbackContext().error(response);
|
||||
}
|
||||
} 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) {
|
||||
if (e.getCause() instanceof UnknownHostException) {
|
||||
this.respondWithError(0, "The host could not be resolved");
|
||||
} else if (e.getCause() instanceof SSLHandshakeException) {
|
||||
this.respondWithError("SSL handshake failed");
|
||||
} else {
|
||||
this.respondWithError("There was an error with the request");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
199
src/browser/cordova-http-plugin.js
vendored
Normal file
199
src/browser/cordova-http-plugin.js
vendored
Normal file
@@ -0,0 +1,199 @@
|
||||
var pluginId = module.id.slice(0, module.id.lastIndexOf('.'));
|
||||
|
||||
var cordovaProxy = require('cordova/exec/proxy');
|
||||
var jsUtil = require(pluginId + '.js-util');
|
||||
|
||||
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 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 createXhrSuccessObject(xhr) {
|
||||
return {
|
||||
url: xhr.responseURL,
|
||||
status: xhr.status,
|
||||
data: jsUtil.getTypeOf(xhr.responseText) === 'String' ? xhr.responseText : xhr.response,
|
||||
headers: deserializeResponseHeaders(xhr.getAllResponseHeaders())
|
||||
};
|
||||
}
|
||||
|
||||
function createXhrFailureObject(xhr) {
|
||||
var obj = {};
|
||||
|
||||
obj.headers = xhr.getAllResponseHeaders();
|
||||
obj.error = jsUtil.getTypeOf(xhr.responseText) === 'String' ? xhr.responseText : xhr.response;
|
||||
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 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 = withData ? opts[1] : null;
|
||||
var params = withData ? null : serializeParams(opts[1]);
|
||||
var serializer = withData ? opts[2] : null;
|
||||
var headers = withData ? opts[3] : opts[2];
|
||||
var timeout = withData ? opts[4] : opts[3];
|
||||
var url = params ? opts[0] + '?' + params : opts[0];
|
||||
|
||||
var processedData = null;
|
||||
var xhr = new XMLHttpRequest();
|
||||
|
||||
xhr.open(method, url);
|
||||
|
||||
if (headers.Cookie && headers.Cookie.length > 0) {
|
||||
return failure('advanced-http: custom cookies not supported on browser platform');
|
||||
}
|
||||
|
||||
switch (serializer) {
|
||||
case 'json':
|
||||
setDefaultContentType(headers, 'application/json; charset=utf8');
|
||||
processedData = serializeJsonData(data);
|
||||
|
||||
if (processedData === null) {
|
||||
return failure('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;
|
||||
}
|
||||
|
||||
xhr.timeout = timeout * 1000;
|
||||
setHeaders(xhr, headers);
|
||||
|
||||
xhr.onerror = xhr.ontimeout = function () {
|
||||
return failure(createXhrFailureObject(xhr));
|
||||
};
|
||||
|
||||
xhr.onload = function () {
|
||||
if (xhr.readyState !== xhr.DONE) return;
|
||||
|
||||
if (xhr.status < 200 || xhr.status > 299) {
|
||||
return failure(createXhrFailureObject(xhr));
|
||||
}
|
||||
|
||||
return success(createXhrSuccessObject(xhr));
|
||||
};
|
||||
|
||||
xhr.send(processedData);
|
||||
}
|
||||
|
||||
var browserInterface = {
|
||||
post: function (success, failure, opts) {
|
||||
return sendRequest('post', true, opts, success, failure);
|
||||
},
|
||||
get: function (success, failure, opts) {
|
||||
return sendRequest('get', false, 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);
|
||||
},
|
||||
delete: function (success, failure, opts) {
|
||||
return sendRequest('delete', false, opts, success, failure);
|
||||
},
|
||||
head: function (success, failure, opts) {
|
||||
return sendRequest('head', false, opts, success, failure);
|
||||
},
|
||||
uploadFile: function (success, failure, opts) {
|
||||
return failure('advanced-http: function "uploadFile" 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');
|
||||
},
|
||||
disableRedirect: function (success, failure, opts) {
|
||||
return failure('advanced-http: function "disableRedirect" not supported on browser platform');
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = browserInterface;
|
||||
cordovaProxy.add('CordovaHttpPlugin', browserInterface);
|
||||
@@ -1,67 +0,0 @@
|
||||
// AFHTTPRequestOperation.h
|
||||
//
|
||||
// Copyright (c) 2013-2014 AFNetworking (http://afnetworking.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.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "AFURLConnectionOperation.h"
|
||||
|
||||
/**
|
||||
`AFHTTPRequestOperation` is a subclass of `AFURLConnectionOperation` for requests using the HTTP or HTTPS protocols. It encapsulates the concept of acceptable status codes and content types, which determine the success or failure of a request.
|
||||
*/
|
||||
@interface AFHTTPRequestOperation : AFURLConnectionOperation
|
||||
|
||||
///------------------------------------------------
|
||||
/// @name Getting HTTP URL Connection Information
|
||||
///------------------------------------------------
|
||||
|
||||
/**
|
||||
The last HTTP response received by the operation's connection.
|
||||
*/
|
||||
@property (readonly, nonatomic, strong) NSHTTPURLResponse *response;
|
||||
|
||||
/**
|
||||
Responses sent from the server in data tasks created with `dataTaskWithRequest:success:failure:` and run using the `GET` / `POST` / et al. convenience methods are automatically validated and serialized by the response serializer. By default, this property is set to an AFHTTPResponse serializer, which uses the raw data as its response object. The serializer validates the status code to be in the `2XX` range, denoting success. If the response serializer generates an error in `-responseObjectForResponse:data:error:`, the `failure` callback of the session task or request operation will be executed; otherwise, the `success` callback will be executed.
|
||||
|
||||
@warning `responseSerializer` must not be `nil`. Setting a response serializer will clear out any cached value
|
||||
*/
|
||||
@property (nonatomic, strong) AFHTTPResponseSerializer <AFURLResponseSerialization> * responseSerializer;
|
||||
|
||||
/**
|
||||
An object constructed by the `responseSerializer` from the response and response data. Returns `nil` unless the operation `isFinished`, has a `response`, and has `responseData` with non-zero content length. If an error occurs during serialization, `nil` will be returned, and the `error` property will be populated with the serialization error.
|
||||
*/
|
||||
@property (readonly, nonatomic, strong) id responseObject;
|
||||
|
||||
///-----------------------------------------------------------
|
||||
/// @name Setting Completion Block Success / Failure Callbacks
|
||||
///-----------------------------------------------------------
|
||||
|
||||
/**
|
||||
Sets the `completionBlock` property with a block that executes either the specified success or failure block, depending on the state of the request on completion. If `error` returns a value, which can be caused by an unacceptable status code or content type, then `failure` is executed. Otherwise, `success` is executed.
|
||||
|
||||
This method should be overridden in subclasses in order to specify the response object passed into the success block.
|
||||
|
||||
@param success The block to be executed on the completion of a successful request. This block has no return value and takes two arguments: the receiver operation and the object constructed from the response data of the request.
|
||||
@param failure The block to be executed on the completion of an unsuccessful request. This block has no return value and takes two arguments: the receiver operation and the error that occurred during the request.
|
||||
*/
|
||||
- (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
|
||||
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure;
|
||||
|
||||
@end
|
||||
@@ -1,206 +0,0 @@
|
||||
// AFHTTPRequestOperation.m
|
||||
//
|
||||
// Copyright (c) 2013-2014 AFNetworking (http://afnetworking.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.
|
||||
|
||||
#import "AFHTTPRequestOperation.h"
|
||||
|
||||
static dispatch_queue_t http_request_operation_processing_queue() {
|
||||
static dispatch_queue_t af_http_request_operation_processing_queue;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
af_http_request_operation_processing_queue = dispatch_queue_create("com.alamofire.networking.http-request.processing", DISPATCH_QUEUE_CONCURRENT);
|
||||
});
|
||||
|
||||
return af_http_request_operation_processing_queue;
|
||||
}
|
||||
|
||||
static dispatch_group_t http_request_operation_completion_group() {
|
||||
static dispatch_group_t af_http_request_operation_completion_group;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
af_http_request_operation_completion_group = dispatch_group_create();
|
||||
});
|
||||
|
||||
return af_http_request_operation_completion_group;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@interface AFURLConnectionOperation ()
|
||||
@property (readwrite, nonatomic, strong) NSURLRequest *request;
|
||||
@property (readwrite, nonatomic, strong) NSURLResponse *response;
|
||||
@end
|
||||
|
||||
@interface AFHTTPRequestOperation ()
|
||||
@property (readwrite, nonatomic, strong) NSHTTPURLResponse *response;
|
||||
@property (readwrite, nonatomic, strong) id responseObject;
|
||||
@property (readwrite, nonatomic, strong) NSError *responseSerializationError;
|
||||
@property (readwrite, nonatomic, strong) NSRecursiveLock *lock;
|
||||
@end
|
||||
|
||||
@implementation AFHTTPRequestOperation
|
||||
@dynamic lock;
|
||||
|
||||
- (instancetype)initWithRequest:(NSURLRequest *)urlRequest {
|
||||
self = [super initWithRequest:urlRequest];
|
||||
if (!self) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
self.responseSerializer = [AFHTTPResponseSerializer serializer];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setResponseSerializer:(AFHTTPResponseSerializer <AFURLResponseSerialization> *)responseSerializer {
|
||||
NSParameterAssert(responseSerializer);
|
||||
|
||||
[self.lock lock];
|
||||
_responseSerializer = responseSerializer;
|
||||
self.responseObject = nil;
|
||||
self.responseSerializationError = nil;
|
||||
[self.lock unlock];
|
||||
}
|
||||
|
||||
- (id)responseObject {
|
||||
[self.lock lock];
|
||||
if (!_responseObject && [self isFinished] && !self.error) {
|
||||
NSError *error = nil;
|
||||
self.responseObject = [self.responseSerializer responseObjectForResponse:self.response data:self.responseData error:&error];
|
||||
if (error) {
|
||||
self.responseSerializationError = error;
|
||||
}
|
||||
}
|
||||
[self.lock unlock];
|
||||
|
||||
return _responseObject;
|
||||
}
|
||||
|
||||
- (NSError *)error {
|
||||
if (_responseSerializationError) {
|
||||
return _responseSerializationError;
|
||||
} else {
|
||||
return [super error];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - AFHTTPRequestOperation
|
||||
|
||||
- (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
|
||||
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
|
||||
{
|
||||
// completionBlock is manually nilled out in AFURLConnectionOperation to break the retain cycle.
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Warc-retain-cycles"
|
||||
#pragma clang diagnostic ignored "-Wgnu"
|
||||
self.completionBlock = ^{
|
||||
if (self.completionGroup) {
|
||||
dispatch_group_enter(self.completionGroup);
|
||||
}
|
||||
|
||||
dispatch_async(http_request_operation_processing_queue(), ^{
|
||||
if (self.error) {
|
||||
if (failure) {
|
||||
dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{
|
||||
failure(self, self.error);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
id responseObject = self.responseObject;
|
||||
if (self.error) {
|
||||
if (failure) {
|
||||
dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{
|
||||
failure(self, self.error);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (success) {
|
||||
dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{
|
||||
success(self, responseObject);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (self.completionGroup) {
|
||||
dispatch_group_leave(self.completionGroup);
|
||||
}
|
||||
});
|
||||
};
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
|
||||
#pragma mark - AFURLRequestOperation
|
||||
|
||||
- (void)pause {
|
||||
[super pause];
|
||||
|
||||
u_int64_t offset = 0;
|
||||
if ([self.outputStream propertyForKey:NSStreamFileCurrentOffsetKey]) {
|
||||
offset = [(NSNumber *)[self.outputStream propertyForKey:NSStreamFileCurrentOffsetKey] unsignedLongLongValue];
|
||||
} else {
|
||||
offset = [(NSData *)[self.outputStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey] length];
|
||||
}
|
||||
|
||||
NSMutableURLRequest *mutableURLRequest = [self.request mutableCopy];
|
||||
if ([self.response respondsToSelector:@selector(allHeaderFields)] && [[self.response allHeaderFields] valueForKey:@"ETag"]) {
|
||||
[mutableURLRequest setValue:[[self.response allHeaderFields] valueForKey:@"ETag"] forHTTPHeaderField:@"If-Range"];
|
||||
}
|
||||
[mutableURLRequest setValue:[NSString stringWithFormat:@"bytes=%llu-", offset] forHTTPHeaderField:@"Range"];
|
||||
self.request = mutableURLRequest;
|
||||
}
|
||||
|
||||
#pragma mark - NSSecureCoding
|
||||
|
||||
+ (BOOL)supportsSecureCoding {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (id)initWithCoder:(NSCoder *)decoder {
|
||||
self = [super initWithCoder:decoder];
|
||||
if (!self) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
self.responseSerializer = [decoder decodeObjectOfClass:[AFHTTPResponseSerializer class] forKey:NSStringFromSelector(@selector(responseSerializer))];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)coder {
|
||||
[super encodeWithCoder:coder];
|
||||
|
||||
[coder encodeObject:self.responseSerializer forKey:NSStringFromSelector(@selector(responseSerializer))];
|
||||
}
|
||||
|
||||
#pragma mark - NSCopying
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone {
|
||||
AFHTTPRequestOperation *operation = [super copyWithZone:zone];
|
||||
|
||||
operation.responseSerializer = [self.responseSerializer copyWithZone:zone];
|
||||
operation.completionQueue = self.completionQueue;
|
||||
operation.completionGroup = self.completionGroup;
|
||||
|
||||
return operation;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,308 +0,0 @@
|
||||
// AFHTTPRequestOperationManager.h
|
||||
//
|
||||
// Copyright (c) 2013-2014 AFNetworking (http://afnetworking.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.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <SystemConfiguration/SystemConfiguration.h>
|
||||
#import <Availability.h>
|
||||
|
||||
#if __IPHONE_OS_VERSION_MIN_REQUIRED
|
||||
#import <MobileCoreServices/MobileCoreServices.h>
|
||||
#else
|
||||
#import <CoreServices/CoreServices.h>
|
||||
#endif
|
||||
|
||||
#import "AFHTTPRequestOperation.h"
|
||||
#import "AFURLResponseSerialization.h"
|
||||
#import "AFURLRequestSerialization.h"
|
||||
#import "AFSecurityPolicy.h"
|
||||
#import "AFNetworkReachabilityManager.h"
|
||||
|
||||
/**
|
||||
`AFHTTPRequestOperationManager` encapsulates the common patterns of communicating with a web application over HTTP, including request creation, response serialization, network reachability monitoring, and security, as well as request operation management.
|
||||
|
||||
## Subclassing Notes
|
||||
|
||||
Developers targeting iOS 7 or Mac OS X 10.9 or later that deal extensively with a web service are encouraged to subclass `AFHTTPSessionManager`, providing a class method that returns a shared singleton object on which authentication and other configuration can be shared across the application.
|
||||
|
||||
For developers targeting iOS 6 or Mac OS X 10.8 or earlier, `AFHTTPRequestOperationManager` may be used to similar effect.
|
||||
|
||||
## Methods to Override
|
||||
|
||||
To change the behavior of all request operation construction for an `AFHTTPRequestOperationManager` subclass, override `HTTPRequestOperationWithRequest:success:failure`.
|
||||
|
||||
## Serialization
|
||||
|
||||
Requests created by an HTTP client will contain default headers and encode parameters according to the `requestSerializer` property, which is an object conforming to `<AFURLRequestSerialization>`.
|
||||
|
||||
Responses received from the server are automatically validated and serialized by the `responseSerializers` property, which is an object conforming to `<AFURLResponseSerialization>`
|
||||
|
||||
## URL Construction Using Relative Paths
|
||||
|
||||
For HTTP convenience methods, the request serializer constructs URLs from the path relative to the `-baseURL`, using `NSURL +URLWithString:relativeToURL:`, when provided. If `baseURL` is `nil`, `path` needs to resolve to a valid `NSURL` object using `NSURL +URLWithString:`.
|
||||
|
||||
Below are a few examples of how `baseURL` and relative paths interact:
|
||||
|
||||
NSURL *baseURL = [NSURL URLWithString:@"http://example.com/v1/"];
|
||||
[NSURL URLWithString:@"foo" relativeToURL:baseURL]; // http://example.com/v1/foo
|
||||
[NSURL URLWithString:@"foo?bar=baz" relativeToURL:baseURL]; // http://example.com/v1/foo?bar=baz
|
||||
[NSURL URLWithString:@"/foo" relativeToURL:baseURL]; // http://example.com/foo
|
||||
[NSURL URLWithString:@"foo/" relativeToURL:baseURL]; // http://example.com/v1/foo
|
||||
[NSURL URLWithString:@"/foo/" relativeToURL:baseURL]; // http://example.com/foo/
|
||||
[NSURL URLWithString:@"http://example2.com/" relativeToURL:baseURL]; // http://example2.com/
|
||||
|
||||
Also important to note is that a trailing slash will be added to any `baseURL` without one. This would otherwise cause unexpected behavior when constructing URLs using paths without a leading slash.
|
||||
|
||||
## Network Reachability Monitoring
|
||||
|
||||
Network reachability status and change monitoring is available through the `reachabilityManager` property. Applications may choose to monitor network reachability conditions in order to prevent or suspend any outbound requests. See `AFNetworkReachabilityManager` for more details.
|
||||
|
||||
## NSSecureCoding & NSCopying Caveats
|
||||
|
||||
`AFHTTPRequestOperationManager` conforms to the `NSSecureCoding` and `NSCopying` protocols, allowing operations to be archived to disk, and copied in memory, respectively. There are a few minor caveats to keep in mind, however:
|
||||
|
||||
- Archives and copies of HTTP clients will be initialized with an empty operation queue.
|
||||
- NSSecureCoding cannot serialize / deserialize block properties, so an archive of an HTTP client will not include any reachability callback block that may be set.
|
||||
*/
|
||||
@interface AFHTTPRequestOperationManager : NSObject <NSSecureCoding, NSCopying>
|
||||
|
||||
/**
|
||||
The URL used to monitor reachability, and construct requests from relative paths in methods like `requestWithMethod:URLString:parameters:`, and the `GET` / `POST` / et al. convenience methods.
|
||||
*/
|
||||
@property (readonly, nonatomic, strong) NSURL *baseURL;
|
||||
|
||||
/**
|
||||
Requests created with `requestWithMethod:URLString:parameters:` & `multipartFormRequestWithMethod:URLString:parameters:constructingBodyWithBlock:` are constructed with a set of default headers using a parameter serialization specified by this property. By default, this is set to an instance of `AFHTTPRequestSerializer`, which serializes query string parameters for `GET`, `HEAD`, and `DELETE` requests, or otherwise URL-form-encodes HTTP message bodies.
|
||||
|
||||
@warning `requestSerializer` must not be `nil`.
|
||||
*/
|
||||
@property (nonatomic, strong) AFHTTPRequestSerializer <AFURLRequestSerialization> * requestSerializer;
|
||||
|
||||
/**
|
||||
Responses sent from the server in data tasks created with `dataTaskWithRequest:success:failure:` and run using the `GET` / `POST` / et al. convenience methods are automatically validated and serialized by the response serializer. By default, this property is set to a JSON serializer, which serializes data from responses with a `application/json` MIME type, and falls back to the raw data object. The serializer validates the status code to be in the `2XX` range, denoting success. If the response serializer generates an error in `-responseObjectForResponse:data:error:`, the `failure` callback of the session task or request operation will be executed; otherwise, the `success` callback will be executed.
|
||||
|
||||
@warning `responseSerializer` must not be `nil`.
|
||||
*/
|
||||
@property (nonatomic, strong) AFHTTPResponseSerializer <AFURLResponseSerialization> * responseSerializer;
|
||||
|
||||
/**
|
||||
The operation queue on which request operations are scheduled and run.
|
||||
*/
|
||||
@property (nonatomic, strong) NSOperationQueue *operationQueue;
|
||||
|
||||
///-------------------------------
|
||||
/// @name Managing URL Credentials
|
||||
///-------------------------------
|
||||
|
||||
/**
|
||||
Whether request operations should consult the credential storage for authenticating the connection. `YES` by default.
|
||||
|
||||
@see AFURLConnectionOperation -shouldUseCredentialStorage
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL shouldUseCredentialStorage;
|
||||
|
||||
/**
|
||||
The credential used by request operations for authentication challenges.
|
||||
|
||||
@see AFURLConnectionOperation -credential
|
||||
*/
|
||||
@property (nonatomic, strong) NSURLCredential *credential;
|
||||
|
||||
///-------------------------------
|
||||
/// @name Managing Security Policy
|
||||
///-------------------------------
|
||||
|
||||
/**
|
||||
The security policy used by created request operations to evaluate server trust for secure connections. `AFHTTPRequestOperationManager` uses the `defaultPolicy` unless otherwise specified.
|
||||
*/
|
||||
@property (nonatomic, strong) AFSecurityPolicy *securityPolicy;
|
||||
|
||||
///------------------------------------
|
||||
/// @name Managing Network Reachability
|
||||
///------------------------------------
|
||||
|
||||
/**
|
||||
The network reachability manager. `AFHTTPRequestOperationManager` uses the `sharedManager` by default.
|
||||
*/
|
||||
@property (readwrite, nonatomic, strong) AFNetworkReachabilityManager *reachabilityManager;
|
||||
|
||||
///-------------------------------
|
||||
/// @name Managing Callback Queues
|
||||
///-------------------------------
|
||||
|
||||
/**
|
||||
The dispatch queue for the `completionBlock` of request operations. If `NULL` (default), the main queue is used.
|
||||
*/
|
||||
@property (nonatomic, strong) dispatch_queue_t completionQueue;
|
||||
|
||||
/**
|
||||
The dispatch group for the `completionBlock` of request operations. If `NULL` (default), a private dispatch group is used.
|
||||
*/
|
||||
@property (nonatomic, strong) dispatch_group_t completionGroup;
|
||||
|
||||
///---------------------------------------------
|
||||
/// @name Creating and Initializing HTTP Clients
|
||||
///---------------------------------------------
|
||||
|
||||
/**
|
||||
Creates and returns an `AFHTTPRequestOperationManager` object.
|
||||
*/
|
||||
+ (instancetype)manager;
|
||||
|
||||
/**
|
||||
Initializes an `AFHTTPRequestOperationManager` object with the specified base URL.
|
||||
|
||||
This is the designated initializer.
|
||||
|
||||
@param url The base URL for the HTTP client.
|
||||
|
||||
@return The newly-initialized HTTP client
|
||||
*/
|
||||
- (instancetype)initWithBaseURL:(NSURL *)url;
|
||||
|
||||
///---------------------------------------
|
||||
/// @name Managing HTTP Request Operations
|
||||
///---------------------------------------
|
||||
|
||||
/**
|
||||
Creates an `AFHTTPRequestOperation`, and sets the response serializers to that of the HTTP client.
|
||||
|
||||
@param request The request object to be loaded asynchronously during execution of the operation.
|
||||
@param success A block object to be executed when the request operation finishes successfully. This block has no return value and takes two arguments: the created request operation and the object created from the response data of request.
|
||||
@param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes two arguments:, the created request operation and the `NSError` object describing the network or parsing error that occurred.
|
||||
*/
|
||||
- (AFHTTPRequestOperation *)HTTPRequestOperationWithRequest:(NSURLRequest *)request
|
||||
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
|
||||
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure;
|
||||
|
||||
///---------------------------
|
||||
/// @name Making HTTP Requests
|
||||
///---------------------------
|
||||
|
||||
/**
|
||||
Creates and runs an `AFHTTPRequestOperation` with a `GET` 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 success A block object to be executed when the request operation finishes successfully. This block has no return value and takes two arguments: the request operation, and the response object created by the client response serializer.
|
||||
@param failure A block object to be executed when the request operation 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 request operation and the error describing the network or parsing error that occurred.
|
||||
|
||||
@see -HTTPRequestOperationWithRequest:success:failure:
|
||||
*/
|
||||
- (AFHTTPRequestOperation *)GET:(NSString *)URLString
|
||||
parameters:(id)parameters
|
||||
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
|
||||
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure;
|
||||
|
||||
/**
|
||||
Creates and runs an `AFHTTPRequestOperation` with a `HEAD` 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 success A block object to be executed when the request operation finishes successfully. This block has no return value and takes a single arguments: the request operation.
|
||||
@param failure A block object to be executed when the request operation 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 request operation and the error describing the network or parsing error that occurred.
|
||||
|
||||
@see -HTTPRequestOperationWithRequest:success:failure:
|
||||
*/
|
||||
- (AFHTTPRequestOperation *)HEAD:(NSString *)URLString
|
||||
parameters:(id)parameters
|
||||
success:(void (^)(AFHTTPRequestOperation *operation))success
|
||||
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure;
|
||||
|
||||
/**
|
||||
Creates and runs an `AFHTTPRequestOperation` with a `POST` 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 success A block object to be executed when the request operation finishes successfully. This block has no return value and takes two arguments: the request operation, and the response object created by the client response serializer.
|
||||
@param failure A block object to be executed when the request operation 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 request operation and the error describing the network or parsing error that occurred.
|
||||
|
||||
@see -HTTPRequestOperationWithRequest:success:failure:
|
||||
*/
|
||||
- (AFHTTPRequestOperation *)POST:(NSString *)URLString
|
||||
parameters:(id)parameters
|
||||
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
|
||||
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure;
|
||||
|
||||
/**
|
||||
Creates and runs an `AFHTTPRequestOperation` with a multipart `POST` 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 success A block object to be executed when the request operation finishes successfully. This block has no return value and takes two arguments: the request operation, and the response object created by the client response serializer.
|
||||
@param failure A block object to be executed when the request operation 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 request operation and the error describing the network or parsing error that occurred.
|
||||
|
||||
@see -HTTPRequestOperationWithRequest:success:failure:
|
||||
*/
|
||||
- (AFHTTPRequestOperation *)POST:(NSString *)URLString
|
||||
parameters:(id)parameters
|
||||
constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block
|
||||
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
|
||||
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure;
|
||||
|
||||
/**
|
||||
Creates and runs an `AFHTTPRequestOperation` with a `PUT` 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 success A block object to be executed when the request operation finishes successfully. This block has no return value and takes two arguments: the request operation, and the response object created by the client response serializer.
|
||||
@param failure A block object to be executed when the request operation 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 request operation and the error describing the network or parsing error that occurred.
|
||||
|
||||
@see -HTTPRequestOperationWithRequest:success:failure:
|
||||
*/
|
||||
- (AFHTTPRequestOperation *)PUT:(NSString *)URLString
|
||||
parameters:(id)parameters
|
||||
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
|
||||
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure;
|
||||
|
||||
/**
|
||||
Creates and runs an `AFHTTPRequestOperation` with a `PATCH` 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 success A block object to be executed when the request operation finishes successfully. This block has no return value and takes two arguments: the request operation, and the response object created by the client response serializer.
|
||||
@param failure A block object to be executed when the request operation 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 request operation and the error describing the network or parsing error that occurred.
|
||||
|
||||
@see -HTTPRequestOperationWithRequest:success:failure:
|
||||
*/
|
||||
- (AFHTTPRequestOperation *)PATCH:(NSString *)URLString
|
||||
parameters:(id)parameters
|
||||
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
|
||||
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure;
|
||||
|
||||
/**
|
||||
Creates and runs an `AFHTTPRequestOperation` with a `DELETE` 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 success A block object to be executed when the request operation finishes successfully. This block has no return value and takes two arguments: the request operation, and the response object created by the client response serializer.
|
||||
@param failure A block object to be executed when the request operation 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 request operation and the error describing the network or parsing error that occurred.
|
||||
|
||||
@see -HTTPRequestOperationWithRequest:success:failure:
|
||||
*/
|
||||
- (AFHTTPRequestOperation *)DELETE:(NSString *)URLString
|
||||
parameters:(id)parameters
|
||||
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
|
||||
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure;
|
||||
|
||||
@end
|
||||
|
||||
@@ -1,253 +0,0 @@
|
||||
// AFHTTPRequestOperationManager.m
|
||||
//
|
||||
// Copyright (c) 2013-2014 AFNetworking (http://afnetworking.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.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "AFHTTPRequestOperationManager.h"
|
||||
#import "AFHTTPRequestOperation.h"
|
||||
|
||||
#import <Availability.h>
|
||||
#import <Security/Security.h>
|
||||
|
||||
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
|
||||
#import <UIKit/UIKit.h>
|
||||
#endif
|
||||
|
||||
@interface AFHTTPRequestOperationManager ()
|
||||
@property (readwrite, nonatomic, strong) NSURL *baseURL;
|
||||
@end
|
||||
|
||||
@implementation AFHTTPRequestOperationManager
|
||||
|
||||
+ (instancetype)manager {
|
||||
return [[self alloc] initWithBaseURL:nil];
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
return [self initWithBaseURL:nil];
|
||||
}
|
||||
|
||||
- (instancetype)initWithBaseURL:(NSURL *)url {
|
||||
self = [super init];
|
||||
if (!self) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Ensure terminal slash for baseURL path, so that NSURL +URLWithString:relativeToURL: works as expected
|
||||
if ([[url path] length] > 0 && ![[url absoluteString] hasSuffix:@"/"]) {
|
||||
url = [url URLByAppendingPathComponent:@""];
|
||||
}
|
||||
|
||||
self.baseURL = url;
|
||||
|
||||
self.requestSerializer = [AFHTTPRequestSerializer serializer];
|
||||
self.responseSerializer = [AFJSONResponseSerializer serializer];
|
||||
|
||||
self.securityPolicy = [AFSecurityPolicy defaultPolicy];
|
||||
|
||||
self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];
|
||||
|
||||
self.operationQueue = [[NSOperationQueue alloc] init];
|
||||
|
||||
self.shouldUseCredentialStorage = YES;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
#ifdef _SYSTEMCONFIGURATION_H
|
||||
#endif
|
||||
|
||||
- (void)setRequestSerializer:(AFHTTPRequestSerializer <AFURLRequestSerialization> *)requestSerializer {
|
||||
NSParameterAssert(requestSerializer);
|
||||
|
||||
_requestSerializer = requestSerializer;
|
||||
}
|
||||
|
||||
- (void)setResponseSerializer:(AFHTTPResponseSerializer <AFURLResponseSerialization> *)responseSerializer {
|
||||
NSParameterAssert(responseSerializer);
|
||||
|
||||
_responseSerializer = responseSerializer;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (AFHTTPRequestOperation *)HTTPRequestOperationWithRequest:(NSURLRequest *)request
|
||||
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
|
||||
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
|
||||
{
|
||||
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
|
||||
operation.responseSerializer = self.responseSerializer;
|
||||
operation.shouldUseCredentialStorage = self.shouldUseCredentialStorage;
|
||||
operation.credential = self.credential;
|
||||
operation.securityPolicy = self.securityPolicy;
|
||||
|
||||
[operation setCompletionBlockWithSuccess:success failure:failure];
|
||||
operation.completionQueue = self.completionQueue;
|
||||
operation.completionGroup = self.completionGroup;
|
||||
|
||||
return operation;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (AFHTTPRequestOperation *)GET:(NSString *)URLString
|
||||
parameters:(id)parameters
|
||||
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
|
||||
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
|
||||
{
|
||||
NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:@"GET" URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:nil];
|
||||
AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithRequest:request success:success failure:failure];
|
||||
|
||||
[self.operationQueue addOperation:operation];
|
||||
|
||||
return operation;
|
||||
}
|
||||
|
||||
- (AFHTTPRequestOperation *)HEAD:(NSString *)URLString
|
||||
parameters:(id)parameters
|
||||
success:(void (^)(AFHTTPRequestOperation *operation))success
|
||||
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
|
||||
{
|
||||
NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:@"HEAD" URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:nil];
|
||||
AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithRequest:request success:^(AFHTTPRequestOperation *requestOperation, __unused id responseObject) {
|
||||
if (success) {
|
||||
success(requestOperation);
|
||||
}
|
||||
} failure:failure];
|
||||
|
||||
[self.operationQueue addOperation:operation];
|
||||
|
||||
return operation;
|
||||
}
|
||||
|
||||
- (AFHTTPRequestOperation *)POST:(NSString *)URLString
|
||||
parameters:(id)parameters
|
||||
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
|
||||
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
|
||||
{
|
||||
NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:@"POST" URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:nil];
|
||||
AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithRequest:request success:success failure:failure];
|
||||
|
||||
[self.operationQueue addOperation:operation];
|
||||
|
||||
return operation;
|
||||
}
|
||||
|
||||
- (AFHTTPRequestOperation *)POST:(NSString *)URLString
|
||||
parameters:(id)parameters
|
||||
constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block
|
||||
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
|
||||
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
|
||||
{
|
||||
NSMutableURLRequest *request = [self.requestSerializer multipartFormRequestWithMethod:@"POST" URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters constructingBodyWithBlock:block error:nil];
|
||||
AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithRequest:request success:success failure:failure];
|
||||
|
||||
[self.operationQueue addOperation:operation];
|
||||
|
||||
return operation;
|
||||
}
|
||||
|
||||
- (AFHTTPRequestOperation *)PUT:(NSString *)URLString
|
||||
parameters:(id)parameters
|
||||
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
|
||||
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
|
||||
{
|
||||
NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:@"PUT" URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:nil];
|
||||
AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithRequest:request success:success failure:failure];
|
||||
|
||||
[self.operationQueue addOperation:operation];
|
||||
|
||||
return operation;
|
||||
}
|
||||
|
||||
- (AFHTTPRequestOperation *)PATCH:(NSString *)URLString
|
||||
parameters:(id)parameters
|
||||
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
|
||||
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
|
||||
{
|
||||
NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:@"PATCH" URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:nil];
|
||||
AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithRequest:request success:success failure:failure];
|
||||
|
||||
[self.operationQueue addOperation:operation];
|
||||
|
||||
return operation;
|
||||
}
|
||||
|
||||
- (AFHTTPRequestOperation *)DELETE:(NSString *)URLString
|
||||
parameters:(id)parameters
|
||||
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
|
||||
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
|
||||
{
|
||||
NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:@"DELETE" URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:nil];
|
||||
AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithRequest:request success:success failure:failure];
|
||||
|
||||
[self.operationQueue addOperation:operation];
|
||||
|
||||
return operation;
|
||||
}
|
||||
|
||||
#pragma mark - NSObject
|
||||
|
||||
- (NSString *)description {
|
||||
return [NSString stringWithFormat:@"<%@: %p, baseURL: %@, operationQueue: %@>", NSStringFromClass([self class]), self, [self.baseURL absoluteString], self.operationQueue];
|
||||
}
|
||||
|
||||
#pragma mark - NSSecureCoding
|
||||
|
||||
+ (BOOL)supportsSecureCoding {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (id)initWithCoder:(NSCoder *)decoder {
|
||||
NSURL *baseURL = [decoder decodeObjectForKey:NSStringFromSelector(@selector(baseURL))];
|
||||
|
||||
self = [self initWithBaseURL:baseURL];
|
||||
if (!self) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
self.requestSerializer = [decoder decodeObjectOfClass:[AFHTTPRequestSerializer class] forKey:NSStringFromSelector(@selector(requestSerializer))];
|
||||
self.responseSerializer = [decoder decodeObjectOfClass:[AFHTTPResponseSerializer class] forKey:NSStringFromSelector(@selector(responseSerializer))];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)coder {
|
||||
[coder encodeObject:self.baseURL forKey:NSStringFromSelector(@selector(baseURL))];
|
||||
[coder encodeObject:self.requestSerializer forKey:NSStringFromSelector(@selector(requestSerializer))];
|
||||
[coder encodeObject:self.responseSerializer forKey:NSStringFromSelector(@selector(responseSerializer))];
|
||||
}
|
||||
|
||||
#pragma mark - NSCopying
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone {
|
||||
AFHTTPRequestOperationManager *HTTPClient = [[[self class] allocWithZone:zone] initWithBaseURL:self.baseURL];
|
||||
|
||||
HTTPClient.requestSerializer = [self.requestSerializer copyWithZone:zone];
|
||||
HTTPClient.responseSerializer = [self.responseSerializer copyWithZone:zone];
|
||||
|
||||
return HTTPClient;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,6 +1,5 @@
|
||||
// AFHTTPSessionManager.h
|
||||
//
|
||||
// Copyright (c) 2013-2014 AFNetworking (http://afnetworking.com)
|
||||
// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ )
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -21,10 +20,12 @@
|
||||
// THE SOFTWARE.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#if !TARGET_OS_WATCH
|
||||
#import <SystemConfiguration/SystemConfiguration.h>
|
||||
#import <Availability.h>
|
||||
#endif
|
||||
#import <TargetConditionals.h>
|
||||
|
||||
#if __IPHONE_OS_VERSION_MIN_REQUIRED
|
||||
#if TARGET_OS_IOS || TARGET_OS_WATCH || TARGET_OS_TV
|
||||
#import <MobileCoreServices/MobileCoreServices.h>
|
||||
#else
|
||||
#import <CoreServices/CoreServices.h>
|
||||
@@ -34,27 +35,27 @@
|
||||
|
||||
/**
|
||||
`AFHTTPSessionManager` is a subclass of `AFURLSessionManager` with convenience methods for making HTTP requests. When a `baseURL` is provided, requests made with the `GET` / `POST` / et al. convenience methods can be made with relative paths.
|
||||
|
||||
|
||||
## Subclassing Notes
|
||||
|
||||
|
||||
Developers targeting iOS 7 or Mac OS X 10.9 or later that deal extensively with a web service are encouraged to subclass `AFHTTPSessionManager`, providing a class method that returns a shared singleton object on which authentication and other configuration can be shared across the application.
|
||||
|
||||
|
||||
For developers targeting iOS 6 or Mac OS X 10.8 or earlier, `AFHTTPRequestOperationManager` may be used to similar effect.
|
||||
|
||||
## Methods to Override
|
||||
|
||||
To change the behavior of all data task operation construction, which is also used in the `GET` / `POST` / et al. convenience methods, override `dataTaskWithRequest:completionHandler:`.
|
||||
|
||||
|
||||
## Serialization
|
||||
|
||||
|
||||
Requests created by an HTTP client will contain default headers and encode parameters according to the `requestSerializer` property, which is an object conforming to `<AFURLRequestSerialization>`.
|
||||
|
||||
|
||||
Responses received from the server are automatically validated and serialized by the `responseSerializers` property, which is an object conforming to `<AFURLResponseSerialization>`
|
||||
|
||||
## URL Construction Using Relative Paths
|
||||
|
||||
For HTTP convenience methods, the request serializer constructs URLs from the path relative to the `-baseURL`, using `NSURL +URLWithString:relativeToURL:`, when provided. If `baseURL` is `nil`, `path` needs to resolve to a valid `NSURL` object using `NSURL +URLWithString:`.
|
||||
|
||||
|
||||
Below are a few examples of how `baseURL` and relative paths interact:
|
||||
|
||||
NSURL *baseURL = [NSURL URLWithString:@"http://example.com/v1/"];
|
||||
@@ -66,20 +67,22 @@
|
||||
[NSURL URLWithString:@"http://example2.com/" relativeToURL:baseURL]; // http://example2.com/
|
||||
|
||||
Also important to note is that a trailing slash will be added to any `baseURL` without one. This would otherwise cause unexpected behavior when constructing URLs using paths without a leading slash.
|
||||
|
||||
@warning Managers for background sessions must be owned for the duration of their use. This can be accomplished by creating an application-wide or shared singleton instance.
|
||||
*/
|
||||
|
||||
#if (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000) || (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 1090)
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface AFHTTPSessionManager : AFURLSessionManager <NSSecureCoding, NSCopying>
|
||||
|
||||
/**
|
||||
The URL used to monitor reachability, and construct requests from relative paths in methods like `requestWithMethod:URLString:parameters:`, and the `GET` / `POST` / et al. convenience methods.
|
||||
The URL used to construct requests from relative paths in methods like `requestWithMethod:URLString:parameters:`, and the `GET` / `POST` / et al. convenience methods.
|
||||
*/
|
||||
@property (readonly, nonatomic, strong) NSURL *baseURL;
|
||||
@property (readonly, nonatomic, strong, nullable) NSURL *baseURL;
|
||||
|
||||
/**
|
||||
Requests created with `requestWithMethod:URLString:parameters:` & `multipartFormRequestWithMethod:URLString:parameters:constructingBodyWithBlock:` are constructed with a set of default headers using a parameter serialization specified by this property. By default, this is set to an instance of `AFHTTPRequestSerializer`, which serializes query string parameters for `GET`, `HEAD`, and `DELETE` requests, or otherwise URL-form-encodes HTTP message bodies.
|
||||
|
||||
|
||||
@warning `requestSerializer` must not be `nil`.
|
||||
*/
|
||||
@property (nonatomic, strong) AFHTTPRequestSerializer <AFURLRequestSerialization> * requestSerializer;
|
||||
@@ -102,12 +105,12 @@
|
||||
|
||||
/**
|
||||
Initializes an `AFHTTPSessionManager` object with the specified base URL.
|
||||
|
||||
|
||||
@param url The base URL for the HTTP client.
|
||||
|
||||
@return The newly-initialized HTTP client
|
||||
*/
|
||||
- (instancetype)initWithBaseURL:(NSURL *)url;
|
||||
- (instancetype)initWithBaseURL:(nullable NSURL *)url;
|
||||
|
||||
/**
|
||||
Initializes an `AFHTTPSessionManager` object with the specified base URL.
|
||||
@@ -119,8 +122,8 @@
|
||||
|
||||
@return The newly-initialized HTTP client
|
||||
*/
|
||||
- (instancetype)initWithBaseURL:(NSURL *)url
|
||||
sessionConfiguration:(NSURLSessionConfiguration *)configuration;
|
||||
- (instancetype)initWithBaseURL:(nullable NSURL *)url
|
||||
sessionConfiguration:(nullable NSURLSessionConfiguration *)configuration NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
///---------------------------
|
||||
/// @name Making HTTP Requests
|
||||
@@ -136,10 +139,28 @@
|
||||
|
||||
@see -dataTaskWithRequest:completionHandler:
|
||||
*/
|
||||
- (NSURLSessionDataTask *)GET:(NSString *)URLString
|
||||
parameters:(id)parameters
|
||||
success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
|
||||
failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure;
|
||||
- (nullable NSURLSessionDataTask *)GET:(NSString *)URLString
|
||||
parameters:(nullable id)parameters
|
||||
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
|
||||
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure DEPRECATED_ATTRIBUTE;
|
||||
|
||||
|
||||
/**
|
||||
Creates and runs an `NSURLSessionDataTask` with a `GET` 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 *)GET:(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;
|
||||
|
||||
/**
|
||||
Creates and runs an `NSURLSessionDataTask` with a `HEAD` request.
|
||||
@@ -151,10 +172,10 @@
|
||||
|
||||
@see -dataTaskWithRequest:completionHandler:
|
||||
*/
|
||||
- (NSURLSessionDataTask *)HEAD:(NSString *)URLString
|
||||
parameters:(id)parameters
|
||||
success:(void (^)(NSURLSessionDataTask *task))success
|
||||
failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure;
|
||||
- (nullable NSURLSessionDataTask *)HEAD:(NSString *)URLString
|
||||
parameters:(nullable id)parameters
|
||||
success:(nullable void (^)(NSURLSessionDataTask *task))success
|
||||
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
|
||||
|
||||
/**
|
||||
Creates and runs an `NSURLSessionDataTask` with a `POST` request.
|
||||
@@ -166,10 +187,27 @@
|
||||
|
||||
@see -dataTaskWithRequest:completionHandler:
|
||||
*/
|
||||
- (NSURLSessionDataTask *)POST:(NSString *)URLString
|
||||
parameters:(id)parameters
|
||||
success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
|
||||
failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure;
|
||||
- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
|
||||
parameters:(nullable id)parameters
|
||||
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
|
||||
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure DEPRECATED_ATTRIBUTE;
|
||||
|
||||
/**
|
||||
Creates and runs an `NSURLSessionDataTask` with a `POST` 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 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 *)POST:(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 multipart `POST` request.
|
||||
@@ -182,11 +220,30 @@
|
||||
|
||||
@see -dataTaskWithRequest:completionHandler:
|
||||
*/
|
||||
- (NSURLSessionDataTask *)POST:(NSString *)URLString
|
||||
parameters:(id)parameters
|
||||
constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block
|
||||
success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
|
||||
failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure;
|
||||
- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
|
||||
parameters:(nullable id)parameters
|
||||
constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block
|
||||
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
|
||||
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure DEPRECATED_ATTRIBUTE;
|
||||
|
||||
/**
|
||||
Creates and runs an `NSURLSessionDataTask` with a multipart `POST` 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 *)POST:(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 a `PUT` request.
|
||||
@@ -198,10 +255,10 @@
|
||||
|
||||
@see -dataTaskWithRequest:completionHandler:
|
||||
*/
|
||||
- (NSURLSessionDataTask *)PUT:(NSString *)URLString
|
||||
parameters:(id)parameters
|
||||
success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
|
||||
failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure;
|
||||
- (nullable NSURLSessionDataTask *)PUT:(NSString *)URLString
|
||||
parameters:(nullable id)parameters
|
||||
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 `PATCH` request.
|
||||
@@ -213,10 +270,10 @@
|
||||
|
||||
@see -dataTaskWithRequest:completionHandler:
|
||||
*/
|
||||
- (NSURLSessionDataTask *)PATCH:(NSString *)URLString
|
||||
parameters:(id)parameters
|
||||
success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
|
||||
failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure;
|
||||
- (nullable NSURLSessionDataTask *)PATCH:(NSString *)URLString
|
||||
parameters:(nullable id)parameters
|
||||
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 `DELETE` request.
|
||||
@@ -228,11 +285,11 @@
|
||||
|
||||
@see -dataTaskWithRequest:completionHandler:
|
||||
*/
|
||||
- (NSURLSessionDataTask *)DELETE:(NSString *)URLString
|
||||
parameters:(id)parameters
|
||||
success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
|
||||
failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure;
|
||||
- (nullable NSURLSessionDataTask *)DELETE:(NSString *)URLString
|
||||
parameters:(nullable id)parameters
|
||||
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
|
||||
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// AFHTTPSessionManager.m
|
||||
//
|
||||
// Copyright (c) 2013-2014 AFNetworking (http://afnetworking.com)
|
||||
// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ )
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -22,24 +21,23 @@
|
||||
|
||||
#import "AFHTTPSessionManager.h"
|
||||
|
||||
#if (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000) || (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 1090)
|
||||
|
||||
#import "AFURLRequestSerialization.h"
|
||||
#import "AFURLResponseSerialization.h"
|
||||
|
||||
#import <Availability.h>
|
||||
#import <TargetConditionals.h>
|
||||
#import <Security/Security.h>
|
||||
|
||||
#ifdef _SYSTEMCONFIGURATION_H
|
||||
#import <netinet/in.h>
|
||||
#import <netinet6/in6.h>
|
||||
#import <arpa/inet.h>
|
||||
#import <ifaddrs.h>
|
||||
#import <netdb.h>
|
||||
#endif
|
||||
|
||||
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
|
||||
#if TARGET_OS_IOS || TARGET_OS_TV
|
||||
#import <UIKit/UIKit.h>
|
||||
#elif TARGET_OS_WATCH
|
||||
#import <WatchKit/WatchKit.h>
|
||||
#endif
|
||||
|
||||
@interface AFHTTPSessionManager ()
|
||||
@@ -47,6 +45,7 @@
|
||||
@end
|
||||
|
||||
@implementation AFHTTPSessionManager
|
||||
@dynamic responseSerializer;
|
||||
|
||||
+ (instancetype)manager {
|
||||
return [[[self class] alloc] initWithBaseURL:nil];
|
||||
@@ -87,9 +86,6 @@
|
||||
|
||||
#pragma mark -
|
||||
|
||||
#ifdef _SYSTEMCONFIGURATION_H
|
||||
#endif
|
||||
|
||||
- (void)setRequestSerializer:(AFHTTPRequestSerializer <AFURLRequestSerialization> *)requestSerializer {
|
||||
NSParameterAssert(requestSerializer);
|
||||
|
||||
@@ -109,7 +105,24 @@
|
||||
success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
|
||||
failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure
|
||||
{
|
||||
NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET" URLString:URLString parameters:parameters success:success failure:failure];
|
||||
|
||||
return [self GET:URLString parameters:parameters progress:nil success:success failure:failure];
|
||||
}
|
||||
|
||||
- (NSURLSessionDataTask *)GET:(NSString *)URLString
|
||||
parameters:(id)parameters
|
||||
progress:(void (^)(NSProgress * _Nonnull))downloadProgress
|
||||
success:(void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success
|
||||
failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure
|
||||
{
|
||||
|
||||
NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET"
|
||||
URLString:URLString
|
||||
parameters:parameters
|
||||
uploadProgress:nil
|
||||
downloadProgress:downloadProgress
|
||||
success:success
|
||||
failure:failure];
|
||||
|
||||
[dataTask resume];
|
||||
|
||||
@@ -121,7 +134,7 @@
|
||||
success:(void (^)(NSURLSessionDataTask *task))success
|
||||
failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure
|
||||
{
|
||||
NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"HEAD" URLString:URLString parameters:parameters success:^(NSURLSessionDataTask *task, __unused id responseObject) {
|
||||
NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"HEAD" URLString:URLString parameters:parameters uploadProgress:nil downloadProgress:nil success:^(NSURLSessionDataTask *task, __unused id responseObject) {
|
||||
if (success) {
|
||||
success(task);
|
||||
}
|
||||
@@ -137,16 +150,35 @@
|
||||
success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
|
||||
failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure
|
||||
{
|
||||
NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"POST" URLString:URLString parameters:parameters success:success failure:failure];
|
||||
return [self POST:URLString parameters:parameters progress:nil success:success failure:failure];
|
||||
}
|
||||
|
||||
- (NSURLSessionDataTask *)POST:(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:@"POST" URLString:URLString parameters:parameters uploadProgress:uploadProgress downloadProgress:nil success:success failure:failure];
|
||||
|
||||
[dataTask resume];
|
||||
|
||||
return dataTask;
|
||||
}
|
||||
|
||||
- (NSURLSessionDataTask *)POST:(NSString *)URLString
|
||||
parameters:(nullable id)parameters
|
||||
constructingBodyWithBlock:(nullable void (^)(id<AFMultipartFormData> _Nonnull))block
|
||||
success:(nullable void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success
|
||||
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure
|
||||
{
|
||||
return [self POST:URLString parameters:parameters constructingBodyWithBlock:block progress:nil success:success failure:failure];
|
||||
}
|
||||
|
||||
- (NSURLSessionDataTask *)POST:(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
|
||||
{
|
||||
@@ -154,18 +186,15 @@
|
||||
NSMutableURLRequest *request = [self.requestSerializer multipartFormRequestWithMethod:@"POST" URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters constructingBodyWithBlock:block error:&serializationError];
|
||||
if (serializationError) {
|
||||
if (failure) {
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wgnu"
|
||||
dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
|
||||
failure(nil, serializationError);
|
||||
});
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
__block NSURLSessionDataTask *task = [self uploadTaskWithStreamedRequest:request progress:nil completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
|
||||
__block NSURLSessionDataTask *task = [self uploadTaskWithStreamedRequest:request progress:uploadProgress completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
|
||||
if (error) {
|
||||
if (failure) {
|
||||
failure(task, error);
|
||||
@@ -187,7 +216,7 @@
|
||||
success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
|
||||
failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure
|
||||
{
|
||||
NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"PUT" URLString:URLString parameters:parameters success:success failure:failure];
|
||||
NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"PUT" URLString:URLString parameters:parameters uploadProgress:nil downloadProgress:nil success:success failure:failure];
|
||||
|
||||
[dataTask resume];
|
||||
|
||||
@@ -199,7 +228,7 @@
|
||||
success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
|
||||
failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure
|
||||
{
|
||||
NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"PATCH" URLString:URLString parameters:parameters success:success failure:failure];
|
||||
NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"PATCH" URLString:URLString parameters:parameters uploadProgress:nil downloadProgress:nil success:success failure:failure];
|
||||
|
||||
[dataTask resume];
|
||||
|
||||
@@ -211,7 +240,7 @@
|
||||
success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
|
||||
failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure
|
||||
{
|
||||
NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"DELETE" URLString:URLString parameters:parameters success:success failure:failure];
|
||||
NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"DELETE" URLString:URLString parameters:parameters uploadProgress:nil downloadProgress:nil success:success failure:failure];
|
||||
|
||||
[dataTask resume];
|
||||
|
||||
@@ -221,6 +250,8 @@
|
||||
- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
|
||||
URLString:(NSString *)URLString
|
||||
parameters:(id)parameters
|
||||
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress
|
||||
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress
|
||||
success:(void (^)(NSURLSessionDataTask *, id))success
|
||||
failure:(void (^)(NSURLSessionDataTask *, NSError *))failure
|
||||
{
|
||||
@@ -228,19 +259,19 @@
|
||||
NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
|
||||
if (serializationError) {
|
||||
if (failure) {
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wgnu"
|
||||
dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
|
||||
failure(nil, serializationError);
|
||||
});
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
__block NSURLSessionDataTask *dataTask = nil;
|
||||
dataTask = [self dataTaskWithRequest:request completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
|
||||
dataTask = [self dataTaskWithRequest:request
|
||||
uploadProgress:uploadProgress
|
||||
downloadProgress:downloadProgress
|
||||
completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
|
||||
if (error) {
|
||||
if (failure) {
|
||||
failure(dataTask, error);
|
||||
@@ -267,7 +298,7 @@
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (id)initWithCoder:(NSCoder *)decoder {
|
||||
- (instancetype)initWithCoder:(NSCoder *)decoder {
|
||||
NSURL *baseURL = [decoder decodeObjectOfClass:[NSURL class] forKey:NSStringFromSelector(@selector(baseURL))];
|
||||
NSURLSessionConfiguration *configuration = [decoder decodeObjectOfClass:[NSURLSessionConfiguration class] forKey:@"sessionConfiguration"];
|
||||
if (!configuration) {
|
||||
@@ -288,6 +319,10 @@
|
||||
|
||||
self.requestSerializer = [decoder decodeObjectOfClass:[AFHTTPRequestSerializer class] forKey:NSStringFromSelector(@selector(requestSerializer))];
|
||||
self.responseSerializer = [decoder decodeObjectOfClass:[AFHTTPResponseSerializer class] forKey:NSStringFromSelector(@selector(responseSerializer))];
|
||||
AFSecurityPolicy *decodedPolicy = [decoder decodeObjectOfClass:[AFSecurityPolicy class] forKey:NSStringFromSelector(@selector(securityPolicy))];
|
||||
if (decodedPolicy) {
|
||||
self.securityPolicy = decodedPolicy;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
@@ -303,19 +338,18 @@
|
||||
}
|
||||
[coder encodeObject:self.requestSerializer forKey:NSStringFromSelector(@selector(requestSerializer))];
|
||||
[coder encodeObject:self.responseSerializer forKey:NSStringFromSelector(@selector(responseSerializer))];
|
||||
[coder encodeObject:self.securityPolicy forKey:NSStringFromSelector(@selector(securityPolicy))];
|
||||
}
|
||||
|
||||
#pragma mark - NSCopying
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone {
|
||||
- (instancetype)copyWithZone:(NSZone *)zone {
|
||||
AFHTTPSessionManager *HTTPClient = [[[self class] allocWithZone:zone] initWithBaseURL:self.baseURL sessionConfiguration:self.session.configuration];
|
||||
|
||||
HTTPClient.requestSerializer = [self.requestSerializer copyWithZone:zone];
|
||||
HTTPClient.responseSerializer = [self.responseSerializer copyWithZone:zone];
|
||||
|
||||
HTTPClient.securityPolicy = [self.securityPolicy copyWithZone:zone];
|
||||
return HTTPClient;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// AFNetworkReachabilityManager.h
|
||||
//
|
||||
// Copyright (c) 2013-2014 AFNetworking (http://afnetworking.com)
|
||||
// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ )
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -8,10 +7,10 @@
|
||||
// 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
|
||||
@@ -21,6 +20,8 @@
|
||||
// THE SOFTWARE.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#if !TARGET_OS_WATCH
|
||||
#import <SystemConfiguration/SystemConfiguration.h>
|
||||
|
||||
typedef NS_ENUM(NSInteger, AFNetworkReachabilityStatus) {
|
||||
@@ -30,13 +31,15 @@ typedef NS_ENUM(NSInteger, AFNetworkReachabilityStatus) {
|
||||
AFNetworkReachabilityStatusReachableViaWiFi = 2,
|
||||
};
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
`AFNetworkReachabilityManager` monitors the reachability of domains, and addresses for both WWAN and WiFi network interfaces.
|
||||
|
||||
|
||||
Reachability can be used to determine background information about why a network operation failed, or to trigger a network operation retrying when a connection is established. It should not be used to prevent a user from initiating a network request, as it's possible that an initial request may be required to establish reachability.
|
||||
|
||||
See Apple's Reachability Sample Code (https://developer.apple.com/library/ios/samplecode/reachability/)
|
||||
|
||||
See Apple's Reachability Sample Code ( https://developer.apple.com/library/ios/samplecode/reachability/ )
|
||||
|
||||
@warning Instances of `AFNetworkReachabilityManager` must be started with `-startMonitoring` before reachability status can be determined.
|
||||
*/
|
||||
@interface AFNetworkReachabilityManager : NSObject
|
||||
@@ -70,11 +73,18 @@ typedef NS_ENUM(NSInteger, AFNetworkReachabilityStatus) {
|
||||
*/
|
||||
+ (instancetype)sharedManager;
|
||||
|
||||
/**
|
||||
Creates and returns a network reachability manager with the default socket address.
|
||||
|
||||
@return An initialized network reachability manager, actively monitoring the default socket address.
|
||||
*/
|
||||
+ (instancetype)manager;
|
||||
|
||||
/**
|
||||
Creates and returns a network reachability manager for the specified domain.
|
||||
|
||||
|
||||
@param domain The domain used to evaluate network reachability.
|
||||
|
||||
|
||||
@return An initialized network reachability manager, actively monitoring the specified domain.
|
||||
*/
|
||||
+ (instancetype)managerForDomain:(NSString *)domain;
|
||||
@@ -82,7 +92,7 @@ typedef NS_ENUM(NSInteger, AFNetworkReachabilityStatus) {
|
||||
/**
|
||||
Creates and returns a network reachability manager for the socket address.
|
||||
|
||||
@param address The socket address (`sockaddr_in`) used to evaluate network reachability.
|
||||
@param address The socket address (`sockaddr_in6`) used to evaluate network reachability.
|
||||
|
||||
@return An initialized network reachability manager, actively monitoring the specified socket address.
|
||||
*/
|
||||
@@ -90,12 +100,12 @@ typedef NS_ENUM(NSInteger, AFNetworkReachabilityStatus) {
|
||||
|
||||
/**
|
||||
Initializes an instance of a network reachability manager from the specified reachability object.
|
||||
|
||||
|
||||
@param reachability The reachability object to monitor.
|
||||
|
||||
|
||||
@return An initialized network reachability manager, actively monitoring the specified reachability.
|
||||
*/
|
||||
- (instancetype)initWithReachability:(SCNetworkReachabilityRef)reachability;
|
||||
- (instancetype)initWithReachability:(SCNetworkReachabilityRef)reachability NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
///--------------------------------------------------
|
||||
/// @name Starting & Stopping Reachability Monitoring
|
||||
@@ -129,7 +139,7 @@ typedef NS_ENUM(NSInteger, AFNetworkReachabilityStatus) {
|
||||
|
||||
@param block A block object to be executed when the network availability of the `baseURL` host changes.. This block has no return value and takes a single argument which represents the various reachability states from the device to the `baseURL`.
|
||||
*/
|
||||
- (void)setReachabilityStatusChangeBlock:(void (^)(AFNetworkReachabilityStatus status))block;
|
||||
- (void)setReachabilityStatusChangeBlock:(nullable void (^)(AFNetworkReachabilityStatus status))block;
|
||||
|
||||
@end
|
||||
|
||||
@@ -180,8 +190,8 @@ typedef NS_ENUM(NSInteger, AFNetworkReachabilityStatus) {
|
||||
|
||||
@warning In order for network reachability to be monitored, include the `SystemConfiguration` framework in the active target's "Link Binary With Library" build phase, and add `#import <SystemConfiguration/SystemConfiguration.h>` to the header prefix of the project (`Prefix.pch`).
|
||||
*/
|
||||
extern NSString * const AFNetworkingReachabilityDidChangeNotification;
|
||||
extern NSString * const AFNetworkingReachabilityNotificationStatusItem;
|
||||
FOUNDATION_EXPORT NSString * const AFNetworkingReachabilityDidChangeNotification;
|
||||
FOUNDATION_EXPORT NSString * const AFNetworkingReachabilityNotificationStatusItem;
|
||||
|
||||
///--------------------
|
||||
/// @name Functions
|
||||
@@ -190,4 +200,7 @@ extern NSString * const AFNetworkingReachabilityNotificationStatusItem;
|
||||
/**
|
||||
Returns a localized string representation of an `AFNetworkReachabilityStatus` value.
|
||||
*/
|
||||
extern NSString * AFStringFromNetworkReachabilityStatus(AFNetworkReachabilityStatus status);
|
||||
FOUNDATION_EXPORT NSString * AFStringFromNetworkReachabilityStatus(AFNetworkReachabilityStatus status);
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
#endif
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// AFNetworkReachabilityManager.m
|
||||
//
|
||||
// Copyright (c) 2013-2014 AFNetworking (http://afnetworking.com)
|
||||
// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ )
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -8,10 +7,10 @@
|
||||
// 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
|
||||
@@ -21,6 +20,7 @@
|
||||
// THE SOFTWARE.
|
||||
|
||||
#import "AFNetworkReachabilityManager.h"
|
||||
#if !TARGET_OS_WATCH
|
||||
|
||||
#import <netinet/in.h>
|
||||
#import <netinet6/in6.h>
|
||||
@@ -33,12 +33,6 @@ NSString * const AFNetworkingReachabilityNotificationStatusItem = @"AFNetworking
|
||||
|
||||
typedef void (^AFNetworkReachabilityStatusBlock)(AFNetworkReachabilityStatus status);
|
||||
|
||||
typedef NS_ENUM(NSUInteger, AFNetworkReachabilityAssociation) {
|
||||
AFNetworkReachabilityForAddress = 1,
|
||||
AFNetworkReachabilityForAddressPair = 2,
|
||||
AFNetworkReachabilityForName = 3,
|
||||
};
|
||||
|
||||
NSString * AFStringFromNetworkReachabilityStatus(AFNetworkReachabilityStatus status) {
|
||||
switch (status) {
|
||||
case AFNetworkReachabilityStatusNotReachable:
|
||||
@@ -76,21 +70,31 @@ static AFNetworkReachabilityStatus AFNetworkReachabilityStatusForFlags(SCNetwork
|
||||
return status;
|
||||
}
|
||||
|
||||
static void AFNetworkReachabilityCallback(SCNetworkReachabilityRef __unused target, SCNetworkReachabilityFlags flags, void *info) {
|
||||
/**
|
||||
* Queue a status change notification for the main thread.
|
||||
*
|
||||
* This is done to ensure that the notifications are received in the same order
|
||||
* as they are sent. If notifications are sent directly, it is possible that
|
||||
* a queued notification (for an earlier status condition) is processed after
|
||||
* the later update, resulting in the listener being left in the wrong state.
|
||||
*/
|
||||
static void AFPostReachabilityStatusChange(SCNetworkReachabilityFlags flags, AFNetworkReachabilityStatusBlock block) {
|
||||
AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusForFlags(flags);
|
||||
AFNetworkReachabilityStatusBlock block = (__bridge AFNetworkReachabilityStatusBlock)info;
|
||||
if (block) {
|
||||
block(status);
|
||||
}
|
||||
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (block) {
|
||||
block(status);
|
||||
}
|
||||
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
|
||||
[notificationCenter postNotificationName:AFNetworkingReachabilityDidChangeNotification object:nil userInfo:@{ AFNetworkingReachabilityNotificationStatusItem: @(status) }];
|
||||
NSDictionary *userInfo = @{ AFNetworkingReachabilityNotificationStatusItem: @(status) };
|
||||
[notificationCenter postNotificationName:AFNetworkingReachabilityDidChangeNotification object:nil userInfo:userInfo];
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
static void AFNetworkReachabilityCallback(SCNetworkReachabilityRef __unused target, SCNetworkReachabilityFlags flags, void *info) {
|
||||
AFPostReachabilityStatusChange(flags, (__bridge AFNetworkReachabilityStatusBlock)info);
|
||||
}
|
||||
|
||||
|
||||
static const void * AFNetworkReachabilityRetainCallback(const void *info) {
|
||||
return Block_copy(info);
|
||||
}
|
||||
@@ -102,8 +106,7 @@ static void AFNetworkReachabilityReleaseCallback(const void *info) {
|
||||
}
|
||||
|
||||
@interface AFNetworkReachabilityManager ()
|
||||
@property (readwrite, nonatomic, assign) SCNetworkReachabilityRef networkReachability;
|
||||
@property (readwrite, nonatomic, assign) AFNetworkReachabilityAssociation networkReachabilityAssociation;
|
||||
@property (readonly, nonatomic, assign) SCNetworkReachabilityRef networkReachability;
|
||||
@property (readwrite, nonatomic, assign) AFNetworkReachabilityStatus networkReachabilityStatus;
|
||||
@property (readwrite, nonatomic, copy) AFNetworkReachabilityStatusBlock networkReachabilityStatusBlock;
|
||||
@end
|
||||
@@ -114,12 +117,7 @@ static void AFNetworkReachabilityReleaseCallback(const void *info) {
|
||||
static AFNetworkReachabilityManager *_sharedManager = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
struct sockaddr_in address;
|
||||
bzero(&address, sizeof(address));
|
||||
address.sin_len = sizeof(address);
|
||||
address.sin_family = AF_INET;
|
||||
|
||||
_sharedManager = [self managerForAddress:&address];
|
||||
_sharedManager = [self manager];
|
||||
});
|
||||
|
||||
return _sharedManager;
|
||||
@@ -129,38 +127,59 @@ static void AFNetworkReachabilityReleaseCallback(const void *info) {
|
||||
SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, [domain UTF8String]);
|
||||
|
||||
AFNetworkReachabilityManager *manager = [[self alloc] initWithReachability:reachability];
|
||||
manager.networkReachabilityAssociation = AFNetworkReachabilityForName;
|
||||
|
||||
CFRelease(reachability);
|
||||
|
||||
return manager;
|
||||
}
|
||||
|
||||
+ (instancetype)managerForAddress:(const void *)address {
|
||||
SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr *)address);
|
||||
|
||||
AFNetworkReachabilityManager *manager = [[self alloc] initWithReachability:reachability];
|
||||
manager.networkReachabilityAssociation = AFNetworkReachabilityForAddress;
|
||||
|
||||
CFRelease(reachability);
|
||||
|
||||
return manager;
|
||||
}
|
||||
|
||||
+ (instancetype)manager
|
||||
{
|
||||
#if (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 90000) || (defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101100)
|
||||
struct sockaddr_in6 address;
|
||||
bzero(&address, sizeof(address));
|
||||
address.sin6_len = sizeof(address);
|
||||
address.sin6_family = AF_INET6;
|
||||
#else
|
||||
struct sockaddr_in address;
|
||||
bzero(&address, sizeof(address));
|
||||
address.sin_len = sizeof(address);
|
||||
address.sin_family = AF_INET;
|
||||
#endif
|
||||
return [self managerForAddress:&address];
|
||||
}
|
||||
|
||||
- (instancetype)initWithReachability:(SCNetworkReachabilityRef)reachability {
|
||||
self = [super init];
|
||||
if (!self) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
self.networkReachability = reachability;
|
||||
_networkReachability = CFRetain(reachability);
|
||||
self.networkReachabilityStatus = AFNetworkReachabilityStatusUnknown;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[self stopMonitoring];
|
||||
|
||||
if (_networkReachability) {
|
||||
|
||||
if (_networkReachability != NULL) {
|
||||
CFRelease(_networkReachability);
|
||||
_networkReachability = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,28 +221,12 @@ static void AFNetworkReachabilityReleaseCallback(const void *info) {
|
||||
SCNetworkReachabilitySetCallback(self.networkReachability, AFNetworkReachabilityCallback, &context);
|
||||
SCNetworkReachabilityScheduleWithRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
|
||||
|
||||
switch (self.networkReachabilityAssociation) {
|
||||
case AFNetworkReachabilityForName:
|
||||
break;
|
||||
case AFNetworkReachabilityForAddress:
|
||||
case AFNetworkReachabilityForAddressPair:
|
||||
default: {
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{
|
||||
SCNetworkReachabilityFlags flags;
|
||||
SCNetworkReachabilityGetFlags(self.networkReachability, &flags);
|
||||
AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusForFlags(flags);
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
callback(status);
|
||||
|
||||
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
|
||||
[notificationCenter postNotificationName:AFNetworkingReachabilityDidChangeNotification object:nil userInfo:@{ AFNetworkingReachabilityNotificationStatusItem: @(status) }];
|
||||
|
||||
|
||||
});
|
||||
});
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{
|
||||
SCNetworkReachabilityFlags flags;
|
||||
if (SCNetworkReachabilityGetFlags(self.networkReachability, &flags)) {
|
||||
AFPostReachabilityStatusChange(flags, callback);
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (void)stopMonitoring {
|
||||
@@ -257,3 +260,4 @@ static void AFNetworkReachabilityReleaseCallback(const void *info) {
|
||||
}
|
||||
|
||||
@end
|
||||
#endif
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <Availability.h>
|
||||
#import <TargetConditionals.h>
|
||||
|
||||
#ifndef _AFNETWORKING_
|
||||
#define _AFNETWORKING_
|
||||
@@ -29,16 +30,12 @@
|
||||
#import "AFURLRequestSerialization.h"
|
||||
#import "AFURLResponseSerialization.h"
|
||||
#import "AFSecurityPolicy.h"
|
||||
|
||||
#if !TARGET_OS_WATCH
|
||||
#import "AFNetworkReachabilityManager.h"
|
||||
|
||||
#import "AFURLConnectionOperation.h"
|
||||
#import "AFHTTPRequestOperation.h"
|
||||
#import "AFHTTPRequestOperationManager.h"
|
||||
|
||||
#if ( ( defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 1090) || \
|
||||
( defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 ) )
|
||||
#import "AFURLSessionManager.h"
|
||||
#import "AFHTTPSessionManager.h"
|
||||
#endif
|
||||
|
||||
#import "AFURLSessionManager.h"
|
||||
#import "AFHTTPSessionManager.h"
|
||||
|
||||
#endif /* _AFNETWORKING_ */
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// AFSecurity.h
|
||||
//
|
||||
// Copyright (c) 2013-2014 AFNetworking (http://afnetworking.com)
|
||||
// AFSecurityPolicy.h
|
||||
// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ )
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -31,25 +30,27 @@ typedef NS_ENUM(NSUInteger, AFSSLPinningMode) {
|
||||
|
||||
/**
|
||||
`AFSecurityPolicy` evaluates server trust against pinned X.509 certificates and public keys over secure connections.
|
||||
|
||||
|
||||
Adding pinned SSL certificates to your app helps prevent man-in-the-middle attacks and other vulnerabilities. Applications dealing with sensitive customer data or financial information are strongly encouraged to route all communication over an HTTPS connection with SSL pinning configured and enabled.
|
||||
*/
|
||||
@interface AFSecurityPolicy : NSObject
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface AFSecurityPolicy : NSObject <NSSecureCoding, NSCopying>
|
||||
|
||||
/**
|
||||
The criteria by which server trust should be evaluated against the pinned SSL certificates. Defaults to `AFSSLPinningModeNone`.
|
||||
*/
|
||||
@property (nonatomic, assign) AFSSLPinningMode SSLPinningMode;
|
||||
@property (readonly, nonatomic, assign) AFSSLPinningMode SSLPinningMode;
|
||||
|
||||
/**
|
||||
Whether to evaluate an entire SSL certificate chain, or just the leaf certificate. Defaults to `YES`.
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL validatesCertificateChain;
|
||||
The certificates used to evaluate server trust according to the SSL pinning mode.
|
||||
|
||||
/**
|
||||
The certificates used to evaluate server trust according to the SSL pinning mode. By default, this property is set to any (`.cer`) certificates included in the app bundle.
|
||||
By default, this property is set to any (`.cer`) certificates included in the target compiling AFNetworking. Note that if you are using AFNetworking as embedded framework, no certificates will be pinned by default. Use `certificatesInBundle` to load certificates from your target, and then create a new policy by calling `policyWithPinningMode:withPinnedCertificates`.
|
||||
|
||||
Note that if pinning is enabled, `evaluateServerTrust:forDomain:` will return true if any pinned certificate matches.
|
||||
*/
|
||||
@property (nonatomic, strong) NSArray *pinnedCertificates;
|
||||
@property (nonatomic, strong, nullable) NSSet <NSData *> *pinnedCertificates;
|
||||
|
||||
/**
|
||||
Whether or not to trust servers with an invalid or expired SSL certificates. Defaults to `NO`.
|
||||
@@ -57,17 +58,28 @@ typedef NS_ENUM(NSUInteger, AFSSLPinningMode) {
|
||||
@property (nonatomic, assign) BOOL allowInvalidCertificates;
|
||||
|
||||
/**
|
||||
Whether or not to validate the domain name in the certificates CN field. Defaults to `YES` for `AFSSLPinningModePublicKey` or `AFSSLPinningModeCertificate`, otherwise `NO`.
|
||||
Whether or not to validate the domain name in the certificate's CN field. Defaults to `YES`.
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL validatesDomainName;
|
||||
|
||||
///-----------------------------------------
|
||||
/// @name Getting Certificates from the Bundle
|
||||
///-----------------------------------------
|
||||
|
||||
/**
|
||||
Returns any certificates included in the bundle. If you are using AFNetworking as an embedded framework, you must use this method to find the certificates you have included in your app bundle, and use them when creating your security policy by calling `policyWithPinningMode:withPinnedCertificates`.
|
||||
|
||||
@return The certificates included in the given bundle.
|
||||
*/
|
||||
+ (NSSet <NSData *> *)certificatesInBundle:(NSBundle *)bundle;
|
||||
|
||||
///-----------------------------------------
|
||||
/// @name Getting Specific Security Policies
|
||||
///-----------------------------------------
|
||||
|
||||
/**
|
||||
Returns the shared default security policy, which does not accept invalid certificates, and does not validate against pinned certificates or public keys.
|
||||
|
||||
Returns the shared default security policy, which does not allow invalid certificates, validates domain name, and does not validate against pinned certificates or public keys.
|
||||
|
||||
@return The default security policy.
|
||||
*/
|
||||
+ (instancetype)defaultPolicy;
|
||||
@@ -78,13 +90,23 @@ typedef NS_ENUM(NSUInteger, AFSSLPinningMode) {
|
||||
|
||||
/**
|
||||
Creates and returns a security policy with the specified pinning mode.
|
||||
|
||||
|
||||
@param pinningMode The SSL pinning mode.
|
||||
|
||||
|
||||
@return A new security policy.
|
||||
*/
|
||||
+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode;
|
||||
|
||||
/**
|
||||
Creates and returns a security policy with the specified pinning mode.
|
||||
|
||||
@param pinningMode The SSL pinning mode.
|
||||
@param pinnedCertificates The certificates to pin against.
|
||||
|
||||
@return A new security policy.
|
||||
*/
|
||||
+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode withPinnedCertificates:(NSSet <NSData *> *)pinnedCertificates;
|
||||
|
||||
///------------------------------
|
||||
/// @name Evaluating Server Trust
|
||||
///------------------------------
|
||||
@@ -94,29 +116,18 @@ typedef NS_ENUM(NSUInteger, AFSSLPinningMode) {
|
||||
|
||||
This method should be used when responding to an authentication challenge from a server.
|
||||
|
||||
@param serverTrust The X.509 certificate trust of the server.
|
||||
|
||||
@return Whether or not to trust the server.
|
||||
|
||||
@warning This method has been deprecated in favor of `-evaluateServerTrust:forDomain:`.
|
||||
*/
|
||||
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust DEPRECATED_ATTRIBUTE;
|
||||
|
||||
/**
|
||||
Whether or not the specified server trust should be accepted, based on the security policy.
|
||||
|
||||
This method should be used when responding to an authentication challenge from a server.
|
||||
|
||||
@param serverTrust The X.509 certificate trust of the server.
|
||||
@param domain The domain of serverTrust. If `nil`, the domain will not be validated.
|
||||
|
||||
|
||||
@return Whether or not to trust the server.
|
||||
*/
|
||||
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
|
||||
forDomain:(NSString *)domain;
|
||||
forDomain:(nullable NSString *)domain;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
///----------------
|
||||
/// @name Constants
|
||||
///----------------
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// AFSecurity.m
|
||||
//
|
||||
// Copyright (c) 2013-2014 AFNetworking (http://afnetworking.com)
|
||||
// AFSecurityPolicy.m
|
||||
// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ )
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -22,31 +21,13 @@
|
||||
|
||||
#import "AFSecurityPolicy.h"
|
||||
|
||||
// Equivalent of macro in <AssertMacros.h>, without causing compiler warning:
|
||||
// "'DebugAssert' is deprecated: first deprecated in OS X 10.8"
|
||||
#ifndef AF_Require
|
||||
#define AF_Require(assertion, exceptionLabel) \
|
||||
do { \
|
||||
if (__builtin_expect(!(assertion), 0)) { \
|
||||
goto exceptionLabel; \
|
||||
} \
|
||||
} while (0)
|
||||
#endif
|
||||
#import <AssertMacros.h>
|
||||
|
||||
#ifndef AF_Require_noErr
|
||||
#define AF_Require_noErr(errorCode, exceptionLabel) \
|
||||
do { \
|
||||
if (__builtin_expect(0 != (errorCode), 0)) { \
|
||||
goto exceptionLabel; \
|
||||
} \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
#if !defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
|
||||
#if !TARGET_OS_IOS && !TARGET_OS_WATCH && !TARGET_OS_TV
|
||||
static NSData * AFSecKeyGetData(SecKeyRef key) {
|
||||
CFDataRef data = NULL;
|
||||
|
||||
AF_Require_noErr(SecItemExport(key, kSecFormatUnknown, kSecItemPemArmour, NULL, &data), _out);
|
||||
__Require_noErr_Quiet(SecItemExport(key, kSecFormatUnknown, kSecItemPemArmour, NULL, &data), _out);
|
||||
|
||||
return (__bridge_transfer NSData *)data;
|
||||
|
||||
@@ -60,7 +41,7 @@ _out:
|
||||
#endif
|
||||
|
||||
static BOOL AFSecKeyIsEqualToKey(SecKeyRef key1, SecKeyRef key2) {
|
||||
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
|
||||
#if TARGET_OS_IOS || TARGET_OS_WATCH || TARGET_OS_TV
|
||||
return [(__bridge id)key1 isEqual:(__bridge id)key2];
|
||||
#else
|
||||
return [AFSecKeyGetData(key1) isEqual:AFSecKeyGetData(key2)];
|
||||
@@ -77,14 +58,14 @@ static id AFPublicKeyForCertificate(NSData *certificate) {
|
||||
SecTrustResultType result;
|
||||
|
||||
allowedCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificate);
|
||||
AF_Require(allowedCertificate != NULL, _out);
|
||||
__Require_Quiet(allowedCertificate != NULL, _out);
|
||||
|
||||
allowedCertificates[0] = allowedCertificate;
|
||||
tempCertificates = CFArrayCreate(NULL, (const void **)allowedCertificates, 1, NULL);
|
||||
|
||||
policy = SecPolicyCreateBasicX509();
|
||||
AF_Require_noErr(SecTrustCreateWithCertificates(tempCertificates, policy, &allowedTrust), _out);
|
||||
AF_Require_noErr(SecTrustEvaluate(allowedTrust, &result), _out);
|
||||
__Require_noErr_Quiet(SecTrustCreateWithCertificates(tempCertificates, policy, &allowedTrust), _out);
|
||||
__Require_noErr_Quiet(SecTrustEvaluate(allowedTrust, &result), _out);
|
||||
|
||||
allowedPublicKey = (__bridge_transfer id)SecTrustCopyPublicKey(allowedTrust);
|
||||
|
||||
@@ -111,7 +92,7 @@ _out:
|
||||
static BOOL AFServerTrustIsValid(SecTrustRef serverTrust) {
|
||||
BOOL isValid = NO;
|
||||
SecTrustResultType result;
|
||||
AF_Require_noErr(SecTrustEvaluate(serverTrust, &result), _out);
|
||||
__Require_noErr_Quiet(SecTrustEvaluate(serverTrust, &result), _out);
|
||||
|
||||
isValid = (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed);
|
||||
|
||||
@@ -142,10 +123,10 @@ static NSArray * AFPublicKeyTrustChainForServerTrust(SecTrustRef serverTrust) {
|
||||
CFArrayRef certificates = CFArrayCreate(NULL, (const void **)someCertificates, 1, NULL);
|
||||
|
||||
SecTrustRef trust;
|
||||
AF_Require_noErr(SecTrustCreateWithCertificates(certificates, policy, &trust), _out);
|
||||
|
||||
__Require_noErr_Quiet(SecTrustCreateWithCertificates(certificates, policy, &trust), _out);
|
||||
|
||||
SecTrustResultType result;
|
||||
AF_Require_noErr(SecTrustEvaluate(trust, &result), _out);
|
||||
__Require_noErr_Quiet(SecTrustEvaluate(trust, &result), _out);
|
||||
|
||||
[trustChain addObject:(__bridge_transfer id)SecTrustCopyPublicKey(trust)];
|
||||
|
||||
@@ -168,30 +149,30 @@ static NSArray * AFPublicKeyTrustChainForServerTrust(SecTrustRef serverTrust) {
|
||||
#pragma mark -
|
||||
|
||||
@interface AFSecurityPolicy()
|
||||
@property (readwrite, nonatomic, strong) NSArray *pinnedPublicKeys;
|
||||
@property (readwrite, nonatomic, assign) AFSSLPinningMode SSLPinningMode;
|
||||
@property (readwrite, nonatomic, strong) NSSet *pinnedPublicKeys;
|
||||
@end
|
||||
|
||||
@implementation AFSecurityPolicy
|
||||
|
||||
+ (NSArray *)defaultPinnedCertificates {
|
||||
static NSArray *_defaultPinnedCertificates = nil;
|
||||
+ (NSSet *)certificatesInBundle:(NSBundle *)bundle {
|
||||
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];
|
||||
}
|
||||
|
||||
return [NSSet setWithSet:certificates];
|
||||
}
|
||||
|
||||
+ (NSSet *)defaultPinnedCertificates {
|
||||
static NSSet *_defaultPinnedCertificates = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
NSBundle *bundle = [NSBundle bundleForClass:[self class]];
|
||||
NSArray *paths = [bundle pathsForResourcesOfType:@"cer" inDirectory:@"."];
|
||||
NSMutableArray *certificates = [NSMutableArray arrayWithCapacity:[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];
|
||||
}
|
||||
|
||||
_defaultPinnedCertificates = [[NSArray alloc] initWithArray:certificates];
|
||||
_defaultPinnedCertificates = [self certificatesInBundle:bundle];
|
||||
});
|
||||
|
||||
return _defaultPinnedCertificates;
|
||||
@@ -205,32 +186,34 @@ static NSArray * AFPublicKeyTrustChainForServerTrust(SecTrustRef serverTrust) {
|
||||
}
|
||||
|
||||
+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode {
|
||||
return [self policyWithPinningMode:pinningMode withPinnedCertificates:[self defaultPinnedCertificates]];
|
||||
}
|
||||
|
||||
+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode withPinnedCertificates:(NSSet *)pinnedCertificates {
|
||||
AFSecurityPolicy *securityPolicy = [[self alloc] init];
|
||||
securityPolicy.SSLPinningMode = pinningMode;
|
||||
securityPolicy.validatesDomainName = YES;
|
||||
[securityPolicy setPinnedCertificates:[self defaultPinnedCertificates]];
|
||||
|
||||
[securityPolicy setPinnedCertificates:pinnedCertificates];
|
||||
|
||||
return securityPolicy;
|
||||
}
|
||||
|
||||
- (id)init {
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (!self) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
self.validatesCertificateChain = YES;
|
||||
self.validatesDomainName = YES;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (void)setPinnedCertificates:(NSArray *)pinnedCertificates {
|
||||
- (void)setPinnedCertificates:(NSSet *)pinnedCertificates {
|
||||
_pinnedCertificates = pinnedCertificates;
|
||||
|
||||
if (self.pinnedCertificates) {
|
||||
NSMutableArray *mutablePinnedPublicKeys = [NSMutableArray arrayWithCapacity:[self.pinnedCertificates count]];
|
||||
NSMutableSet *mutablePinnedPublicKeys = [NSMutableSet setWithCapacity:[self.pinnedCertificates count]];
|
||||
for (NSData *certificate in self.pinnedCertificates) {
|
||||
id publicKey = AFPublicKeyForCertificate(certificate);
|
||||
if (!publicKey) {
|
||||
@@ -238,7 +221,7 @@ static NSArray * AFPublicKeyTrustChainForServerTrust(SecTrustRef serverTrust) {
|
||||
}
|
||||
[mutablePinnedPublicKeys addObject:publicKey];
|
||||
}
|
||||
self.pinnedPublicKeys = [NSArray arrayWithArray:mutablePinnedPublicKeys];
|
||||
self.pinnedPublicKeys = [NSSet setWithSet:mutablePinnedPublicKeys];
|
||||
} else {
|
||||
self.pinnedPublicKeys = nil;
|
||||
}
|
||||
@@ -246,13 +229,22 @@ static NSArray * AFPublicKeyTrustChainForServerTrust(SecTrustRef serverTrust) {
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust {
|
||||
return [self evaluateServerTrust:serverTrust forDomain:nil];
|
||||
}
|
||||
|
||||
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
|
||||
forDomain:(NSString *)domain
|
||||
{
|
||||
if (domain && self.allowInvalidCertificates && self.validatesDomainName && (self.SSLPinningMode == AFSSLPinningModeNone || [self.pinnedCertificates count] == 0)) {
|
||||
// https://developer.apple.com/library/mac/documentation/NetworkingInternet/Conceptual/NetworkingTopics/Articles/OverridingSSLChainValidationCorrectly.html
|
||||
// According to the docs, you should only trust your provided certs for evaluation.
|
||||
// Pinned certificates are added to the trust. Without pinned certificates,
|
||||
// there is nothing to evaluate against.
|
||||
//
|
||||
// From Apple Docs:
|
||||
// "Do not implicitly trust self-signed certificates as anchors (kSecTrustOptionImplicitAnchors).
|
||||
// Instead, add your own (self-signed) CA certificate to the list of trusted anchors."
|
||||
NSLog(@"In order to validate a domain name for self signed certificates, you MUST use pinning.");
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSMutableArray *policies = [NSMutableArray array];
|
||||
if (self.validatesDomainName) {
|
||||
[policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];
|
||||
@@ -262,14 +254,16 @@ static NSArray * AFPublicKeyTrustChainForServerTrust(SecTrustRef serverTrust) {
|
||||
|
||||
SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);
|
||||
|
||||
if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
|
||||
if (self.SSLPinningMode == AFSSLPinningModeNone) {
|
||||
return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
|
||||
} else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust);
|
||||
switch (self.SSLPinningMode) {
|
||||
case AFSSLPinningModeNone:
|
||||
return YES;
|
||||
default:
|
||||
return NO;
|
||||
case AFSSLPinningModeCertificate: {
|
||||
NSMutableArray *pinnedCertificates = [NSMutableArray array];
|
||||
for (NSData *certificateData in self.pinnedCertificates) {
|
||||
@@ -281,25 +275,20 @@ static NSArray * AFPublicKeyTrustChainForServerTrust(SecTrustRef serverTrust) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (!self.validatesCertificateChain) {
|
||||
return YES;
|
||||
}
|
||||
// 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);
|
||||
|
||||
NSUInteger trustedCertificateCount = 0;
|
||||
for (NSData *trustChainCertificate in serverCertificates) {
|
||||
for (NSData *trustChainCertificate in [serverCertificates reverseObjectEnumerator]) {
|
||||
if ([self.pinnedCertificates containsObject:trustChainCertificate]) {
|
||||
trustedCertificateCount++;
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
return trustedCertificateCount == [serverCertificates count];
|
||||
return NO;
|
||||
}
|
||||
case AFSSLPinningModePublicKey: {
|
||||
NSUInteger trustedPublicKeyCount = 0;
|
||||
NSArray *publicKeys = AFPublicKeyTrustChainForServerTrust(serverTrust);
|
||||
if (!self.validatesCertificateChain && [publicKeys count] > 0) {
|
||||
publicKeys = @[[publicKeys firstObject]];
|
||||
}
|
||||
|
||||
for (id trustChainPublicKey in publicKeys) {
|
||||
for (id pinnedPublicKey in self.pinnedPublicKeys) {
|
||||
@@ -308,11 +297,10 @@ static NSArray * AFPublicKeyTrustChainForServerTrust(SecTrustRef serverTrust) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return trustedPublicKeyCount > 0 && ((self.validatesCertificateChain && trustedPublicKeyCount == [serverCertificates count]) || (!self.validatesCertificateChain && trustedPublicKeyCount >= 1));
|
||||
return trustedPublicKeyCount > 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
@@ -322,4 +310,44 @@ static NSArray * AFPublicKeyTrustChainForServerTrust(SecTrustRef serverTrust) {
|
||||
return [NSSet setWithObject:@"pinnedCertificates"];
|
||||
}
|
||||
|
||||
#pragma mark - NSSecureCoding
|
||||
|
||||
+ (BOOL)supportsSecureCoding {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)decoder {
|
||||
|
||||
self = [self init];
|
||||
if (!self) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
self.SSLPinningMode = [[decoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(SSLPinningMode))] unsignedIntegerValue];
|
||||
self.allowInvalidCertificates = [decoder decodeBoolForKey:NSStringFromSelector(@selector(allowInvalidCertificates))];
|
||||
self.validatesDomainName = [decoder decodeBoolForKey:NSStringFromSelector(@selector(validatesDomainName))];
|
||||
self.pinnedCertificates = [decoder decodeObjectOfClass:[NSArray class] forKey:NSStringFromSelector(@selector(pinnedCertificates))];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)coder {
|
||||
[coder encodeObject:[NSNumber numberWithUnsignedInteger:self.SSLPinningMode] forKey:NSStringFromSelector(@selector(SSLPinningMode))];
|
||||
[coder encodeBool:self.allowInvalidCertificates forKey:NSStringFromSelector(@selector(allowInvalidCertificates))];
|
||||
[coder encodeBool:self.validatesDomainName forKey:NSStringFromSelector(@selector(validatesDomainName))];
|
||||
[coder encodeObject:self.pinnedCertificates forKey:NSStringFromSelector(@selector(pinnedCertificates))];
|
||||
}
|
||||
|
||||
#pragma mark - NSCopying
|
||||
|
||||
- (instancetype)copyWithZone:(NSZone *)zone {
|
||||
AFSecurityPolicy *securityPolicy = [[[self class] allocWithZone:zone] init];
|
||||
securityPolicy.SSLPinningMode = self.SSLPinningMode;
|
||||
securityPolicy.allowInvalidCertificates = self.allowInvalidCertificates;
|
||||
securityPolicy.validatesDomainName = self.validatesDomainName;
|
||||
securityPolicy.pinnedCertificates = [self.pinnedCertificates copyWithZone:zone];
|
||||
|
||||
return securityPolicy;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -1,328 +0,0 @@
|
||||
// AFURLConnectionOperation.h
|
||||
//
|
||||
// Copyright (c) 2013-2014 AFNetworking (http://afnetworking.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.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import <Availability.h>
|
||||
#import "AFURLRequestSerialization.h"
|
||||
#import "AFURLResponseSerialization.h"
|
||||
#import "AFSecurityPolicy.h"
|
||||
|
||||
/**
|
||||
`AFURLConnectionOperation` is a subclass of `NSOperation` that implements `NSURLConnection` delegate methods.
|
||||
|
||||
## Subclassing Notes
|
||||
|
||||
This is the base class of all network request operations. You may wish to create your own subclass in order to implement additional `NSURLConnection` delegate methods (see "`NSURLConnection` Delegate Methods" below), or to provide additional properties and/or class constructors.
|
||||
|
||||
If you are creating a subclass that communicates over the HTTP or HTTPS protocols, you may want to consider subclassing `AFHTTPRequestOperation` instead, as it supports specifying acceptable content types or status codes.
|
||||
|
||||
## NSURLConnection Delegate Methods
|
||||
|
||||
`AFURLConnectionOperation` implements the following `NSURLConnection` delegate methods:
|
||||
|
||||
- `connection:didReceiveResponse:`
|
||||
- `connection:didReceiveData:`
|
||||
- `connectionDidFinishLoading:`
|
||||
- `connection:didFailWithError:`
|
||||
- `connection:didSendBodyData:totalBytesWritten:totalBytesExpectedToWrite:`
|
||||
- `connection:willCacheResponse:`
|
||||
- `connectionShouldUseCredentialStorage:`
|
||||
- `connection:needNewBodyStream:`
|
||||
- `connection:willSendRequestForAuthenticationChallenge:`
|
||||
|
||||
If any of these methods are overridden in a subclass, they _must_ call the `super` implementation first.
|
||||
|
||||
## Callbacks and Completion Blocks
|
||||
|
||||
The built-in `completionBlock` provided by `NSOperation` allows for custom behavior to be executed after the request finishes. It is a common pattern for class constructors in subclasses to take callback block parameters, and execute them conditionally in the body of its `completionBlock`. Make sure to handle cancelled operations appropriately when setting a `completionBlock` (i.e. returning early before parsing response data). See the implementation of any of the `AFHTTPRequestOperation` subclasses for an example of this.
|
||||
|
||||
Subclasses are strongly discouraged from overriding `setCompletionBlock:`, as `AFURLConnectionOperation`'s implementation includes a workaround to mitigate retain cycles, and what Apple rather ominously refers to as ["The Deallocation Problem"](http://developer.apple.com/library/ios/#technotes/tn2109/).
|
||||
|
||||
## SSL Pinning
|
||||
|
||||
Relying on the CA trust model to validate SSL certificates exposes your app to security vulnerabilities, such as man-in-the-middle attacks. For applications that connect to known servers, SSL certificate pinning provides an increased level of security, by checking server certificate validity against those specified in the app bundle.
|
||||
|
||||
SSL with certificate pinning is strongly recommended for any application that transmits sensitive information to an external webservice.
|
||||
|
||||
Connections will be validated on all matching certificates with a `.cer` extension in the bundle root.
|
||||
|
||||
## App Extensions
|
||||
|
||||
When using AFNetworking in an App Extension, `#define AF_APP_EXTENSIONS` to avoid using unavailable APIs.
|
||||
|
||||
## NSCoding & NSCopying Conformance
|
||||
|
||||
`AFURLConnectionOperation` conforms to the `NSCoding` and `NSCopying` protocols, allowing operations to be archived to disk, and copied in memory, respectively. However, because of the intrinsic limitations of capturing the exact state of an operation at a particular moment, there are some important caveats to keep in mind:
|
||||
|
||||
### NSCoding Caveats
|
||||
|
||||
- Encoded operations do not include any block or stream properties. Be sure to set `completionBlock`, `outputStream`, and any callback blocks as necessary when using `-initWithCoder:` or `NSKeyedUnarchiver`.
|
||||
- Operations are paused on `encodeWithCoder:`. If the operation was encoded while paused or still executing, its archived state will return `YES` for `isReady`. Otherwise, the state of an operation when encoding will remain unchanged.
|
||||
|
||||
### NSCopying Caveats
|
||||
|
||||
- `-copy` and `-copyWithZone:` return a new operation with the `NSURLRequest` of the original. So rather than an exact copy of the operation at that particular instant, the copying mechanism returns a completely new instance, which can be useful for retrying operations.
|
||||
- A copy of an operation will not include the `outputStream` of the original.
|
||||
- Operation copies do not include `completionBlock`, as it often strongly captures a reference to `self`, which would otherwise have the unintuitive side-effect of pointing to the _original_ operation when copied.
|
||||
*/
|
||||
|
||||
@interface AFURLConnectionOperation : NSOperation <NSURLConnectionDelegate, NSURLConnectionDataDelegate, NSSecureCoding, NSCopying>
|
||||
|
||||
///-------------------------------
|
||||
/// @name Accessing Run Loop Modes
|
||||
///-------------------------------
|
||||
|
||||
/**
|
||||
The run loop modes in which the operation will run on the network thread. By default, this is a single-member set containing `NSRunLoopCommonModes`.
|
||||
*/
|
||||
@property (nonatomic, strong) NSSet *runLoopModes;
|
||||
|
||||
///-----------------------------------------
|
||||
/// @name Getting URL Connection Information
|
||||
///-----------------------------------------
|
||||
|
||||
/**
|
||||
The request used by the operation's connection.
|
||||
*/
|
||||
@property (readonly, nonatomic, strong) NSURLRequest *request;
|
||||
|
||||
/**
|
||||
The last response received by the operation's connection.
|
||||
*/
|
||||
@property (readonly, nonatomic, strong) NSURLResponse *response;
|
||||
|
||||
/**
|
||||
The error, if any, that occurred in the lifecycle of the request.
|
||||
*/
|
||||
@property (readonly, nonatomic, strong) NSError *error;
|
||||
|
||||
///----------------------------
|
||||
/// @name Getting Response Data
|
||||
///----------------------------
|
||||
|
||||
/**
|
||||
The data received during the request.
|
||||
*/
|
||||
@property (readonly, nonatomic, strong) NSData *responseData;
|
||||
|
||||
/**
|
||||
The string representation of the response data.
|
||||
*/
|
||||
@property (readonly, nonatomic, copy) NSString *responseString;
|
||||
|
||||
/**
|
||||
The string encoding of the response.
|
||||
|
||||
If the response does not specify a valid string encoding, `responseStringEncoding` will return `NSUTF8StringEncoding`.
|
||||
*/
|
||||
@property (readonly, nonatomic, assign) NSStringEncoding responseStringEncoding;
|
||||
|
||||
///-------------------------------
|
||||
/// @name Managing URL Credentials
|
||||
///-------------------------------
|
||||
|
||||
/**
|
||||
Whether the URL connection should consult the credential storage for authenticating the connection. `YES` by default.
|
||||
|
||||
This is the value that is returned in the `NSURLConnectionDelegate` method `-connectionShouldUseCredentialStorage:`.
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL shouldUseCredentialStorage;
|
||||
|
||||
/**
|
||||
The credential used for authentication challenges in `-connection:didReceiveAuthenticationChallenge:`.
|
||||
|
||||
This will be overridden by any shared credentials that exist for the username or password of the request URL, if present.
|
||||
*/
|
||||
@property (nonatomic, strong) NSURLCredential *credential;
|
||||
|
||||
///-------------------------------
|
||||
/// @name Managing Security Policy
|
||||
///-------------------------------
|
||||
|
||||
/**
|
||||
The security policy used to evaluate server trust for secure connections.
|
||||
*/
|
||||
@property (nonatomic, strong) AFSecurityPolicy *securityPolicy;
|
||||
|
||||
///------------------------
|
||||
/// @name Accessing Streams
|
||||
///------------------------
|
||||
|
||||
/**
|
||||
The input stream used to read data to be sent during the request.
|
||||
|
||||
This property acts as a proxy to the `HTTPBodyStream` property of `request`.
|
||||
*/
|
||||
@property (nonatomic, strong) NSInputStream *inputStream;
|
||||
|
||||
/**
|
||||
The output stream that is used to write data received until the request is finished.
|
||||
|
||||
By default, data is accumulated into a buffer that is stored into `responseData` upon completion of the request, with the intermediary `outputStream` property set to `nil`. When `outputStream` is set, the data will not be accumulated into an internal buffer, and as a result, the `responseData` property of the completed request will be `nil`. The output stream will be scheduled in the network thread runloop upon being set.
|
||||
*/
|
||||
@property (nonatomic, strong) NSOutputStream *outputStream;
|
||||
|
||||
///---------------------------------
|
||||
/// @name Managing Callback Queues
|
||||
///---------------------------------
|
||||
|
||||
/**
|
||||
The dispatch queue for `completionBlock`. If `NULL` (default), the main queue is used.
|
||||
*/
|
||||
@property (nonatomic, strong) dispatch_queue_t completionQueue;
|
||||
|
||||
/**
|
||||
The dispatch group for `completionBlock`. If `NULL` (default), a private dispatch group is used.
|
||||
*/
|
||||
@property (nonatomic, strong) dispatch_group_t completionGroup;
|
||||
|
||||
///---------------------------------------------
|
||||
/// @name Managing Request Operation Information
|
||||
///---------------------------------------------
|
||||
|
||||
/**
|
||||
The user info dictionary for the receiver.
|
||||
*/
|
||||
@property (nonatomic, strong) NSDictionary *userInfo;
|
||||
|
||||
///------------------------------------------------------
|
||||
/// @name Initializing an AFURLConnectionOperation Object
|
||||
///------------------------------------------------------
|
||||
|
||||
/**
|
||||
Initializes and returns a newly allocated operation object with a url connection configured with the specified url request.
|
||||
|
||||
This is the designated initializer.
|
||||
|
||||
@param urlRequest The request object to be used by the operation connection.
|
||||
*/
|
||||
- (instancetype)initWithRequest:(NSURLRequest *)urlRequest;
|
||||
|
||||
///----------------------------------
|
||||
/// @name Pausing / Resuming Requests
|
||||
///----------------------------------
|
||||
|
||||
/**
|
||||
Pauses the execution of the request operation.
|
||||
|
||||
A paused operation returns `NO` for `-isReady`, `-isExecuting`, and `-isFinished`. As such, it will remain in an `NSOperationQueue` until it is either cancelled or resumed. Pausing a finished, cancelled, or paused operation has no effect.
|
||||
*/
|
||||
- (void)pause;
|
||||
|
||||
/**
|
||||
Whether the request operation is currently paused.
|
||||
|
||||
@return `YES` if the operation is currently paused, otherwise `NO`.
|
||||
*/
|
||||
- (BOOL)isPaused;
|
||||
|
||||
/**
|
||||
Resumes the execution of the paused request operation.
|
||||
|
||||
Pause/Resume behavior varies depending on the underlying implementation for the operation class. In its base implementation, resuming a paused requests restarts the original request. However, since HTTP defines a specification for how to request a specific content range, `AFHTTPRequestOperation` will resume downloading the request from where it left off, instead of restarting the original request.
|
||||
*/
|
||||
- (void)resume;
|
||||
|
||||
///----------------------------------------------
|
||||
/// @name Configuring Backgrounding Task Behavior
|
||||
///----------------------------------------------
|
||||
|
||||
/**
|
||||
Specifies that the operation should continue execution after the app has entered the background, and the expiration handler for that background task.
|
||||
|
||||
@param handler A handler to be called shortly before the application’s remaining background time reaches 0. The handler is wrapped in a block that cancels the operation, and cleans up and marks the end of execution, unlike the `handler` parameter in `UIApplication -beginBackgroundTaskWithExpirationHandler:`, which expects this to be done in the handler itself. The handler is called synchronously on the main thread, thus blocking the application’s suspension momentarily while the application is notified.
|
||||
*/
|
||||
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && !defined(AF_APP_EXTENSIONS)
|
||||
- (void)setShouldExecuteAsBackgroundTaskWithExpirationHandler:(void (^)(void))handler;
|
||||
#endif
|
||||
|
||||
///---------------------------------
|
||||
/// @name Setting Progress Callbacks
|
||||
///---------------------------------
|
||||
|
||||
/**
|
||||
Sets a callback to be called when an undetermined number of bytes have been uploaded to the server.
|
||||
|
||||
@param block A block object to be called when an undetermined number of bytes have been uploaded to the server. This block has no return value and takes three arguments: the number of bytes written since the last time the upload progress block was called, the total bytes written, and the total bytes expected to be written during the request, as initially determined by the length of the HTTP body. This block may be called multiple times, and will execute on the main thread.
|
||||
*/
|
||||
- (void)setUploadProgressBlock:(void (^)(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite))block;
|
||||
|
||||
/**
|
||||
Sets a callback to be called when an undetermined number of bytes have been downloaded from the server.
|
||||
|
||||
@param block A block object to be called when an undetermined number of bytes have been downloaded from the server. This block has no return value and takes three arguments: the number of bytes read since the last time the download progress block was called, the total bytes read, and the total bytes expected to be read during the request, as initially determined by the expected content size of the `NSHTTPURLResponse` object. This block may be called multiple times, and will execute on the main thread.
|
||||
*/
|
||||
- (void)setDownloadProgressBlock:(void (^)(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead))block;
|
||||
|
||||
///-------------------------------------------------
|
||||
/// @name Setting NSURLConnection Delegate Callbacks
|
||||
///-------------------------------------------------
|
||||
|
||||
/**
|
||||
Sets a block to be executed when the connection will authenticate a challenge in order to download its request, as handled by the `NSURLConnectionDelegate` method `connection:willSendRequestForAuthenticationChallenge:`.
|
||||
|
||||
@param block A block object to be executed when the connection will authenticate a challenge in order to download its request. The block has no return type and takes two arguments: the URL connection object, and the challenge that must be authenticated. This block must invoke one of the challenge-responder methods (NSURLAuthenticationChallengeSender protocol).
|
||||
|
||||
If `allowsInvalidSSLCertificate` is set to YES, `connection:willSendRequestForAuthenticationChallenge:` will attempt to have the challenge sender use credentials with invalid SSL certificates.
|
||||
*/
|
||||
- (void)setWillSendRequestForAuthenticationChallengeBlock:(void (^)(NSURLConnection *connection, NSURLAuthenticationChallenge *challenge))block;
|
||||
|
||||
/**
|
||||
Sets a block to be executed when the server redirects the request from one URL to another URL, or when the request URL changed by the `NSURLProtocol` subclass handling the request in order to standardize its format, as handled by the `NSURLConnectionDataDelegate` method `connection:willSendRequest:redirectResponse:`.
|
||||
|
||||
@param block A block object to be executed when the request URL was changed. The block returns an `NSURLRequest` object, the URL request to redirect, and takes three arguments: the URL connection object, the the proposed redirected request, and the URL response that caused the redirect.
|
||||
*/
|
||||
- (void)setRedirectResponseBlock:(NSURLRequest * (^)(NSURLConnection *connection, NSURLRequest *request, NSURLResponse *redirectResponse))block;
|
||||
|
||||
|
||||
/**
|
||||
Sets a block to be executed to modify the response a connection will cache, if any, as handled by the `NSURLConnectionDelegate` method `connection:willCacheResponse:`.
|
||||
|
||||
@param block A block object to be executed to determine what response a connection will cache, if any. The block returns an `NSCachedURLResponse` object, the cached response to store in memory or `nil` to prevent the response from being cached, and takes two arguments: the URL connection object, and the cached response provided for the request.
|
||||
*/
|
||||
- (void)setCacheResponseBlock:(NSCachedURLResponse * (^)(NSURLConnection *connection, NSCachedURLResponse *cachedResponse))block;
|
||||
|
||||
///
|
||||
|
||||
/**
|
||||
|
||||
*/
|
||||
+ (NSArray *)batchOfRequestOperations:(NSArray *)operations
|
||||
progressBlock:(void (^)(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations))progressBlock
|
||||
completionBlock:(void (^)(NSArray *operations))completionBlock;
|
||||
|
||||
@end
|
||||
|
||||
///--------------------
|
||||
/// @name Notifications
|
||||
///--------------------
|
||||
|
||||
/**
|
||||
Posted when an operation begins executing.
|
||||
*/
|
||||
extern NSString * const AFNetworkingOperationDidStartNotification;
|
||||
|
||||
/**
|
||||
Posted when an operation finishes.
|
||||
*/
|
||||
extern NSString * const AFNetworkingOperationDidFinishNotification;
|
||||
@@ -1,786 +0,0 @@
|
||||
// AFURLConnectionOperation.m
|
||||
//
|
||||
// Copyright (c) 2013-2014 AFNetworking (http://afnetworking.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.
|
||||
|
||||
#import "AFURLConnectionOperation.h"
|
||||
|
||||
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
|
||||
#import <UIKit/UIKit.h>
|
||||
#endif
|
||||
|
||||
#if !__has_feature(objc_arc)
|
||||
#error AFNetworking must be built with ARC.
|
||||
// You can turn on ARC for only AFNetworking files by adding -fobjc-arc to the build phase for each of its files.
|
||||
#endif
|
||||
|
||||
typedef NS_ENUM(NSInteger, AFOperationState) {
|
||||
AFOperationPausedState = -1,
|
||||
AFOperationReadyState = 1,
|
||||
AFOperationExecutingState = 2,
|
||||
AFOperationFinishedState = 3,
|
||||
};
|
||||
|
||||
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && !defined(AF_APP_EXTENSIONS)
|
||||
typedef UIBackgroundTaskIdentifier AFBackgroundTaskIdentifier;
|
||||
#else
|
||||
typedef id AFBackgroundTaskIdentifier;
|
||||
#endif
|
||||
|
||||
static dispatch_group_t url_request_operation_completion_group() {
|
||||
static dispatch_group_t af_url_request_operation_completion_group;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
af_url_request_operation_completion_group = dispatch_group_create();
|
||||
});
|
||||
|
||||
return af_url_request_operation_completion_group;
|
||||
}
|
||||
|
||||
static dispatch_queue_t url_request_operation_completion_queue() {
|
||||
static dispatch_queue_t af_url_request_operation_completion_queue;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
af_url_request_operation_completion_queue = dispatch_queue_create("com.alamofire.networking.operation.queue", DISPATCH_QUEUE_CONCURRENT );
|
||||
});
|
||||
|
||||
return af_url_request_operation_completion_queue;
|
||||
}
|
||||
|
||||
static NSString * const kAFNetworkingLockName = @"com.alamofire.networking.operation.lock";
|
||||
|
||||
NSString * const AFNetworkingOperationDidStartNotification = @"com.alamofire.networking.operation.start";
|
||||
NSString * const AFNetworkingOperationDidFinishNotification = @"com.alamofire.networking.operation.finish";
|
||||
|
||||
typedef void (^AFURLConnectionOperationProgressBlock)(NSUInteger bytes, long long totalBytes, long long totalBytesExpected);
|
||||
typedef void (^AFURLConnectionOperationAuthenticationChallengeBlock)(NSURLConnection *connection, NSURLAuthenticationChallenge *challenge);
|
||||
typedef NSCachedURLResponse * (^AFURLConnectionOperationCacheResponseBlock)(NSURLConnection *connection, NSCachedURLResponse *cachedResponse);
|
||||
typedef NSURLRequest * (^AFURLConnectionOperationRedirectResponseBlock)(NSURLConnection *connection, NSURLRequest *request, NSURLResponse *redirectResponse);
|
||||
|
||||
static inline NSString * AFKeyPathFromOperationState(AFOperationState state) {
|
||||
switch (state) {
|
||||
case AFOperationReadyState:
|
||||
return @"isReady";
|
||||
case AFOperationExecutingState:
|
||||
return @"isExecuting";
|
||||
case AFOperationFinishedState:
|
||||
return @"isFinished";
|
||||
case AFOperationPausedState:
|
||||
return @"isPaused";
|
||||
default: {
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wunreachable-code"
|
||||
return @"state";
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOperationState toState, BOOL isCancelled) {
|
||||
switch (fromState) {
|
||||
case AFOperationReadyState:
|
||||
switch (toState) {
|
||||
case AFOperationPausedState:
|
||||
case AFOperationExecutingState:
|
||||
return YES;
|
||||
case AFOperationFinishedState:
|
||||
return isCancelled;
|
||||
default:
|
||||
return NO;
|
||||
}
|
||||
case AFOperationExecutingState:
|
||||
switch (toState) {
|
||||
case AFOperationPausedState:
|
||||
case AFOperationFinishedState:
|
||||
return YES;
|
||||
default:
|
||||
return NO;
|
||||
}
|
||||
case AFOperationFinishedState:
|
||||
return NO;
|
||||
case AFOperationPausedState:
|
||||
return toState == AFOperationReadyState;
|
||||
default: {
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wunreachable-code"
|
||||
switch (toState) {
|
||||
case AFOperationPausedState:
|
||||
case AFOperationReadyState:
|
||||
case AFOperationExecutingState:
|
||||
case AFOperationFinishedState:
|
||||
return YES;
|
||||
default:
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
}
|
||||
|
||||
@interface AFURLConnectionOperation ()
|
||||
@property (readwrite, nonatomic, assign) AFOperationState state;
|
||||
@property (readwrite, nonatomic, strong) NSRecursiveLock *lock;
|
||||
@property (readwrite, nonatomic, strong) NSURLConnection *connection;
|
||||
@property (readwrite, nonatomic, strong) NSURLRequest *request;
|
||||
@property (readwrite, nonatomic, strong) NSURLResponse *response;
|
||||
@property (readwrite, nonatomic, strong) NSError *error;
|
||||
@property (readwrite, nonatomic, strong) NSData *responseData;
|
||||
@property (readwrite, nonatomic, copy) NSString *responseString;
|
||||
@property (readwrite, nonatomic, assign) NSStringEncoding responseStringEncoding;
|
||||
@property (readwrite, nonatomic, assign) long long totalBytesRead;
|
||||
@property (readwrite, nonatomic, assign) AFBackgroundTaskIdentifier backgroundTaskIdentifier;
|
||||
@property (readwrite, nonatomic, copy) AFURLConnectionOperationProgressBlock uploadProgress;
|
||||
@property (readwrite, nonatomic, copy) AFURLConnectionOperationProgressBlock downloadProgress;
|
||||
@property (readwrite, nonatomic, copy) AFURLConnectionOperationAuthenticationChallengeBlock authenticationChallenge;
|
||||
@property (readwrite, nonatomic, copy) AFURLConnectionOperationCacheResponseBlock cacheResponse;
|
||||
@property (readwrite, nonatomic, copy) AFURLConnectionOperationRedirectResponseBlock redirectResponse;
|
||||
|
||||
- (void)operationDidStart;
|
||||
- (void)finish;
|
||||
- (void)cancelConnection;
|
||||
@end
|
||||
|
||||
@implementation AFURLConnectionOperation
|
||||
@synthesize outputStream = _outputStream;
|
||||
|
||||
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
|
||||
@autoreleasepool {
|
||||
[[NSThread currentThread] setName:@"AFNetworking"];
|
||||
|
||||
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
|
||||
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
|
||||
[runLoop run];
|
||||
}
|
||||
}
|
||||
|
||||
+ (NSThread *)networkRequestThread {
|
||||
static NSThread *_networkRequestThread = nil;
|
||||
static dispatch_once_t oncePredicate;
|
||||
dispatch_once(&oncePredicate, ^{
|
||||
_networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
|
||||
[_networkRequestThread start];
|
||||
});
|
||||
|
||||
return _networkRequestThread;
|
||||
}
|
||||
|
||||
- (instancetype)initWithRequest:(NSURLRequest *)urlRequest {
|
||||
NSParameterAssert(urlRequest);
|
||||
|
||||
self = [super init];
|
||||
if (!self) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
_state = AFOperationReadyState;
|
||||
|
||||
self.lock = [[NSRecursiveLock alloc] init];
|
||||
self.lock.name = kAFNetworkingLockName;
|
||||
|
||||
self.runLoopModes = [NSSet setWithObject:NSRunLoopCommonModes];
|
||||
|
||||
self.request = urlRequest;
|
||||
|
||||
self.shouldUseCredentialStorage = YES;
|
||||
|
||||
self.securityPolicy = [AFSecurityPolicy defaultPolicy];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
if (_outputStream) {
|
||||
[_outputStream close];
|
||||
_outputStream = nil;
|
||||
}
|
||||
|
||||
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && !defined(AF_APP_EXTENSIONS)
|
||||
if (_backgroundTaskIdentifier) {
|
||||
[[UIApplication sharedApplication] endBackgroundTask:_backgroundTaskIdentifier];
|
||||
_backgroundTaskIdentifier = UIBackgroundTaskInvalid;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (void)setResponseData:(NSData *)responseData {
|
||||
[self.lock lock];
|
||||
if (!responseData) {
|
||||
_responseData = nil;
|
||||
} else {
|
||||
_responseData = [NSData dataWithBytes:responseData.bytes length:responseData.length];
|
||||
}
|
||||
[self.lock unlock];
|
||||
}
|
||||
|
||||
- (NSString *)responseString {
|
||||
[self.lock lock];
|
||||
if (!_responseString && self.response && self.responseData) {
|
||||
self.responseString = [[NSString alloc] initWithData:self.responseData encoding:self.responseStringEncoding];
|
||||
}
|
||||
[self.lock unlock];
|
||||
|
||||
return _responseString;
|
||||
}
|
||||
|
||||
- (NSStringEncoding)responseStringEncoding {
|
||||
[self.lock lock];
|
||||
if (!_responseStringEncoding && self.response) {
|
||||
NSStringEncoding stringEncoding = NSUTF8StringEncoding;
|
||||
if (self.response.textEncodingName) {
|
||||
CFStringEncoding IANAEncoding = CFStringConvertIANACharSetNameToEncoding((__bridge CFStringRef)self.response.textEncodingName);
|
||||
if (IANAEncoding != kCFStringEncodingInvalidId) {
|
||||
stringEncoding = CFStringConvertEncodingToNSStringEncoding(IANAEncoding);
|
||||
}
|
||||
}
|
||||
|
||||
self.responseStringEncoding = stringEncoding;
|
||||
}
|
||||
[self.lock unlock];
|
||||
|
||||
return _responseStringEncoding;
|
||||
}
|
||||
|
||||
- (NSInputStream *)inputStream {
|
||||
return self.request.HTTPBodyStream;
|
||||
}
|
||||
|
||||
- (void)setInputStream:(NSInputStream *)inputStream {
|
||||
NSMutableURLRequest *mutableRequest = [self.request mutableCopy];
|
||||
mutableRequest.HTTPBodyStream = inputStream;
|
||||
self.request = mutableRequest;
|
||||
}
|
||||
|
||||
- (NSOutputStream *)outputStream {
|
||||
if (!_outputStream) {
|
||||
self.outputStream = [NSOutputStream outputStreamToMemory];
|
||||
}
|
||||
|
||||
return _outputStream;
|
||||
}
|
||||
|
||||
- (void)setOutputStream:(NSOutputStream *)outputStream {
|
||||
[self.lock lock];
|
||||
if (outputStream != _outputStream) {
|
||||
if (_outputStream) {
|
||||
[_outputStream close];
|
||||
}
|
||||
|
||||
_outputStream = outputStream;
|
||||
}
|
||||
[self.lock unlock];
|
||||
}
|
||||
|
||||
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && !defined(AF_APP_EXTENSIONS)
|
||||
- (void)setShouldExecuteAsBackgroundTaskWithExpirationHandler:(void (^)(void))handler {
|
||||
[self.lock lock];
|
||||
if (!self.backgroundTaskIdentifier) {
|
||||
UIApplication *application = [UIApplication sharedApplication];
|
||||
__weak __typeof(self)weakSelf = self;
|
||||
self.backgroundTaskIdentifier = [application beginBackgroundTaskWithExpirationHandler:^{
|
||||
__strong __typeof(weakSelf)strongSelf = weakSelf;
|
||||
|
||||
if (handler) {
|
||||
handler();
|
||||
}
|
||||
|
||||
if (strongSelf) {
|
||||
[strongSelf cancel];
|
||||
|
||||
[application endBackgroundTask:strongSelf.backgroundTaskIdentifier];
|
||||
strongSelf.backgroundTaskIdentifier = UIBackgroundTaskInvalid;
|
||||
}
|
||||
}];
|
||||
}
|
||||
[self.lock unlock];
|
||||
}
|
||||
#endif
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (void)setState:(AFOperationState)state {
|
||||
if (!AFStateTransitionIsValid(self.state, state, [self isCancelled])) {
|
||||
return;
|
||||
}
|
||||
|
||||
[self.lock lock];
|
||||
NSString *oldStateKey = AFKeyPathFromOperationState(self.state);
|
||||
NSString *newStateKey = AFKeyPathFromOperationState(state);
|
||||
|
||||
[self willChangeValueForKey:newStateKey];
|
||||
[self willChangeValueForKey:oldStateKey];
|
||||
_state = state;
|
||||
[self didChangeValueForKey:oldStateKey];
|
||||
[self didChangeValueForKey:newStateKey];
|
||||
[self.lock unlock];
|
||||
}
|
||||
|
||||
- (void)pause {
|
||||
if ([self isPaused] || [self isFinished] || [self isCancelled]) {
|
||||
return;
|
||||
}
|
||||
|
||||
[self.lock lock];
|
||||
if ([self isExecuting]) {
|
||||
[self performSelector:@selector(operationDidPause) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
|
||||
[notificationCenter postNotificationName:AFNetworkingOperationDidFinishNotification object:self];
|
||||
});
|
||||
}
|
||||
|
||||
self.state = AFOperationPausedState;
|
||||
[self.lock unlock];
|
||||
}
|
||||
|
||||
- (void)operationDidPause {
|
||||
[self.lock lock];
|
||||
[self.connection cancel];
|
||||
[self.lock unlock];
|
||||
}
|
||||
|
||||
- (BOOL)isPaused {
|
||||
return self.state == AFOperationPausedState;
|
||||
}
|
||||
|
||||
- (void)resume {
|
||||
if (![self isPaused]) {
|
||||
return;
|
||||
}
|
||||
|
||||
[self.lock lock];
|
||||
self.state = AFOperationReadyState;
|
||||
|
||||
[self start];
|
||||
[self.lock unlock];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (void)setUploadProgressBlock:(void (^)(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite))block {
|
||||
self.uploadProgress = block;
|
||||
}
|
||||
|
||||
- (void)setDownloadProgressBlock:(void (^)(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead))block {
|
||||
self.downloadProgress = block;
|
||||
}
|
||||
|
||||
- (void)setWillSendRequestForAuthenticationChallengeBlock:(void (^)(NSURLConnection *connection, NSURLAuthenticationChallenge *challenge))block {
|
||||
self.authenticationChallenge = block;
|
||||
}
|
||||
|
||||
- (void)setCacheResponseBlock:(NSCachedURLResponse * (^)(NSURLConnection *connection, NSCachedURLResponse *cachedResponse))block {
|
||||
self.cacheResponse = block;
|
||||
}
|
||||
|
||||
- (void)setRedirectResponseBlock:(NSURLRequest * (^)(NSURLConnection *connection, NSURLRequest *request, NSURLResponse *redirectResponse))block {
|
||||
self.redirectResponse = block;
|
||||
}
|
||||
|
||||
#pragma mark - NSOperation
|
||||
|
||||
- (void)setCompletionBlock:(void (^)(void))block {
|
||||
[self.lock lock];
|
||||
if (!block) {
|
||||
[super setCompletionBlock:nil];
|
||||
} else {
|
||||
__weak __typeof(self)weakSelf = self;
|
||||
[super setCompletionBlock:^ {
|
||||
__strong __typeof(weakSelf)strongSelf = weakSelf;
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wgnu"
|
||||
dispatch_group_t group = strongSelf.completionGroup ?: url_request_operation_completion_group();
|
||||
dispatch_queue_t queue = strongSelf.completionQueue ?: dispatch_get_main_queue();
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
dispatch_group_async(group, queue, ^{
|
||||
block();
|
||||
});
|
||||
|
||||
dispatch_group_notify(group, url_request_operation_completion_queue(), ^{
|
||||
[strongSelf setCompletionBlock:nil];
|
||||
});
|
||||
}];
|
||||
}
|
||||
[self.lock unlock];
|
||||
}
|
||||
|
||||
- (BOOL)isReady {
|
||||
return self.state == AFOperationReadyState && [super isReady];
|
||||
}
|
||||
|
||||
- (BOOL)isExecuting {
|
||||
return self.state == AFOperationExecutingState;
|
||||
}
|
||||
|
||||
- (BOOL)isFinished {
|
||||
return self.state == AFOperationFinishedState;
|
||||
}
|
||||
|
||||
- (BOOL)isConcurrent {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)start {
|
||||
[self.lock lock];
|
||||
if ([self isCancelled]) {
|
||||
[self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
|
||||
} else if ([self isReady]) {
|
||||
self.state = AFOperationExecutingState;
|
||||
|
||||
[self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
|
||||
}
|
||||
[self.lock unlock];
|
||||
}
|
||||
|
||||
- (void)operationDidStart {
|
||||
[self.lock lock];
|
||||
if (![self isCancelled]) {
|
||||
self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO];
|
||||
|
||||
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
|
||||
for (NSString *runLoopMode in self.runLoopModes) {
|
||||
[self.connection scheduleInRunLoop:runLoop forMode:runLoopMode];
|
||||
[self.outputStream scheduleInRunLoop:runLoop forMode:runLoopMode];
|
||||
}
|
||||
|
||||
[self.outputStream open];
|
||||
[self.connection start];
|
||||
}
|
||||
[self.lock unlock];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingOperationDidStartNotification object:self];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)finish {
|
||||
[self.lock lock];
|
||||
self.state = AFOperationFinishedState;
|
||||
[self.lock unlock];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingOperationDidFinishNotification object:self];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)cancel {
|
||||
[self.lock lock];
|
||||
if (![self isFinished] && ![self isCancelled]) {
|
||||
[super cancel];
|
||||
|
||||
if ([self isExecuting]) {
|
||||
[self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
|
||||
}
|
||||
}
|
||||
[self.lock unlock];
|
||||
}
|
||||
|
||||
- (void)cancelConnection {
|
||||
NSDictionary *userInfo = nil;
|
||||
if ([self.request URL]) {
|
||||
userInfo = [NSDictionary dictionaryWithObject:[self.request URL] forKey:NSURLErrorFailingURLErrorKey];
|
||||
}
|
||||
NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCancelled userInfo:userInfo];
|
||||
|
||||
if (![self isFinished]) {
|
||||
if (self.connection) {
|
||||
[self.connection cancel];
|
||||
[self performSelector:@selector(connection:didFailWithError:) withObject:self.connection withObject:error];
|
||||
} else {
|
||||
// Accomodate race condition where `self.connection` has not yet been set before cancellation
|
||||
self.error = error;
|
||||
[self finish];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
+ (NSArray *)batchOfRequestOperations:(NSArray *)operations
|
||||
progressBlock:(void (^)(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations))progressBlock
|
||||
completionBlock:(void (^)(NSArray *operations))completionBlock
|
||||
{
|
||||
if (!operations || [operations count] == 0) {
|
||||
return @[[NSBlockOperation blockOperationWithBlock:^{
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (completionBlock) {
|
||||
completionBlock(@[]);
|
||||
}
|
||||
});
|
||||
}]];
|
||||
}
|
||||
|
||||
__block dispatch_group_t group = dispatch_group_create();
|
||||
NSBlockOperation *batchedOperation = [NSBlockOperation blockOperationWithBlock:^{
|
||||
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
|
||||
if (completionBlock) {
|
||||
completionBlock(operations);
|
||||
}
|
||||
});
|
||||
}];
|
||||
|
||||
for (AFURLConnectionOperation *operation in operations) {
|
||||
operation.completionGroup = group;
|
||||
void (^originalCompletionBlock)(void) = [operation.completionBlock copy];
|
||||
__weak __typeof(operation)weakOperation = operation;
|
||||
operation.completionBlock = ^{
|
||||
__strong __typeof(weakOperation)strongOperation = weakOperation;
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wgnu"
|
||||
dispatch_queue_t queue = strongOperation.completionQueue ?: dispatch_get_main_queue();
|
||||
#pragma clang diagnostic pop
|
||||
dispatch_group_async(group, queue, ^{
|
||||
if (originalCompletionBlock) {
|
||||
originalCompletionBlock();
|
||||
}
|
||||
|
||||
NSUInteger numberOfFinishedOperations = [[operations indexesOfObjectsPassingTest:^BOOL(id op, NSUInteger __unused idx, BOOL __unused *stop) {
|
||||
return [op isFinished];
|
||||
}] count];
|
||||
|
||||
if (progressBlock) {
|
||||
progressBlock(numberOfFinishedOperations, [operations count]);
|
||||
}
|
||||
|
||||
dispatch_group_leave(group);
|
||||
});
|
||||
};
|
||||
|
||||
dispatch_group_enter(group);
|
||||
[batchedOperation addDependency:operation];
|
||||
}
|
||||
|
||||
return [operations arrayByAddingObject:batchedOperation];
|
||||
}
|
||||
|
||||
#pragma mark - NSObject
|
||||
|
||||
- (NSString *)description {
|
||||
return [NSString stringWithFormat:@"<%@: %p, state: %@, cancelled: %@ request: %@, response: %@>", NSStringFromClass([self class]), self, AFKeyPathFromOperationState(self.state), ([self isCancelled] ? @"YES" : @"NO"), self.request, self.response];
|
||||
}
|
||||
|
||||
#pragma mark - NSURLConnectionDelegate
|
||||
|
||||
- (void)connection:(NSURLConnection *)connection
|
||||
willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
|
||||
{
|
||||
if (self.authenticationChallenge) {
|
||||
self.authenticationChallenge(connection, challenge);
|
||||
return;
|
||||
}
|
||||
|
||||
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
|
||||
if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
|
||||
NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
|
||||
[[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
|
||||
} else {
|
||||
[[challenge sender] cancelAuthenticationChallenge:challenge];
|
||||
}
|
||||
} else {
|
||||
if ([challenge previousFailureCount] == 0) {
|
||||
if (self.credential) {
|
||||
[[challenge sender] useCredential:self.credential forAuthenticationChallenge:challenge];
|
||||
} else {
|
||||
[[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge];
|
||||
}
|
||||
} else {
|
||||
[[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection __unused *)connection {
|
||||
return self.shouldUseCredentialStorage;
|
||||
}
|
||||
|
||||
- (NSURLRequest *)connection:(NSURLConnection *)connection
|
||||
willSendRequest:(NSURLRequest *)request
|
||||
redirectResponse:(NSURLResponse *)redirectResponse
|
||||
{
|
||||
if (self.redirectResponse) {
|
||||
return self.redirectResponse(connection, request, redirectResponse);
|
||||
} else {
|
||||
return request;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)connection:(NSURLConnection __unused *)connection
|
||||
didSendBodyData:(NSInteger)bytesWritten
|
||||
totalBytesWritten:(NSInteger)totalBytesWritten
|
||||
totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite
|
||||
{
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (self.uploadProgress) {
|
||||
self.uploadProgress((NSUInteger)bytesWritten, totalBytesWritten, totalBytesExpectedToWrite);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (void)connection:(NSURLConnection __unused *)connection
|
||||
didReceiveResponse:(NSURLResponse *)response
|
||||
{
|
||||
self.response = response;
|
||||
}
|
||||
|
||||
- (void)connection:(NSURLConnection __unused *)connection
|
||||
didReceiveData:(NSData *)data
|
||||
{
|
||||
NSUInteger length = [data length];
|
||||
while (YES) {
|
||||
NSInteger totalNumberOfBytesWritten = 0;
|
||||
if ([self.outputStream hasSpaceAvailable]) {
|
||||
const uint8_t *dataBuffer = (uint8_t *)[data bytes];
|
||||
|
||||
NSInteger numberOfBytesWritten = 0;
|
||||
while (totalNumberOfBytesWritten < (NSInteger)length) {
|
||||
numberOfBytesWritten = [self.outputStream write:&dataBuffer[(NSUInteger)totalNumberOfBytesWritten] maxLength:(length - (NSUInteger)totalNumberOfBytesWritten)];
|
||||
if (numberOfBytesWritten == -1) {
|
||||
break;
|
||||
}
|
||||
|
||||
totalNumberOfBytesWritten += numberOfBytesWritten;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (self.outputStream.streamError) {
|
||||
[self.connection cancel];
|
||||
[self performSelector:@selector(connection:didFailWithError:) withObject:self.connection withObject:self.outputStream.streamError];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
self.totalBytesRead += (long long)length;
|
||||
|
||||
if (self.downloadProgress) {
|
||||
self.downloadProgress(length, self.totalBytesRead, self.response.expectedContentLength);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (void)connectionDidFinishLoading:(NSURLConnection __unused *)connection {
|
||||
self.responseData = [self.outputStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey];
|
||||
|
||||
[self.outputStream close];
|
||||
if (self.responseData) {
|
||||
self.outputStream = nil;
|
||||
}
|
||||
|
||||
self.connection = nil;
|
||||
|
||||
[self finish];
|
||||
}
|
||||
|
||||
- (void)connection:(NSURLConnection __unused *)connection
|
||||
didFailWithError:(NSError *)error
|
||||
{
|
||||
self.error = error;
|
||||
|
||||
[self.outputStream close];
|
||||
if (self.responseData) {
|
||||
self.outputStream = nil;
|
||||
}
|
||||
|
||||
self.connection = nil;
|
||||
|
||||
[self finish];
|
||||
}
|
||||
|
||||
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection
|
||||
willCacheResponse:(NSCachedURLResponse *)cachedResponse
|
||||
{
|
||||
if (self.cacheResponse) {
|
||||
return self.cacheResponse(connection, cachedResponse);
|
||||
} else {
|
||||
if ([self isCancelled]) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
return cachedResponse;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - NSSecureCoding
|
||||
|
||||
+ (BOOL)supportsSecureCoding {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (id)initWithCoder:(NSCoder *)decoder {
|
||||
NSURLRequest *request = [decoder decodeObjectOfClass:[NSURLRequest class] forKey:NSStringFromSelector(@selector(request))];
|
||||
|
||||
self = [self initWithRequest:request];
|
||||
if (!self) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
self.state = [[decoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(state))] integerValue];
|
||||
self.response = [decoder decodeObjectOfClass:[NSHTTPURLResponse class] forKey:NSStringFromSelector(@selector(response))];
|
||||
self.error = [decoder decodeObjectOfClass:[NSError class] forKey:NSStringFromSelector(@selector(error))];
|
||||
self.responseData = [decoder decodeObjectOfClass:[NSData class] forKey:NSStringFromSelector(@selector(responseData))];
|
||||
self.totalBytesRead = [[decoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(totalBytesRead))] longLongValue];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)coder {
|
||||
[self pause];
|
||||
|
||||
[coder encodeObject:self.request forKey:NSStringFromSelector(@selector(request))];
|
||||
|
||||
switch (self.state) {
|
||||
case AFOperationExecutingState:
|
||||
case AFOperationPausedState:
|
||||
[coder encodeInteger:AFOperationReadyState forKey:NSStringFromSelector(@selector(state))];
|
||||
break;
|
||||
default:
|
||||
[coder encodeInteger:self.state forKey:NSStringFromSelector(@selector(state))];
|
||||
break;
|
||||
}
|
||||
|
||||
[coder encodeObject:self.response forKey:NSStringFromSelector(@selector(response))];
|
||||
[coder encodeObject:self.error forKey:NSStringFromSelector(@selector(error))];
|
||||
[coder encodeObject:self.responseData forKey:NSStringFromSelector(@selector(responseData))];
|
||||
[coder encodeInt64:self.totalBytesRead forKey:NSStringFromSelector(@selector(totalBytesRead))];
|
||||
}
|
||||
|
||||
#pragma mark - NSCopying
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone {
|
||||
AFURLConnectionOperation *operation = [(AFURLConnectionOperation *)[[self class] allocWithZone:zone] initWithRequest:self.request];
|
||||
|
||||
operation.uploadProgress = self.uploadProgress;
|
||||
operation.downloadProgress = self.downloadProgress;
|
||||
operation.authenticationChallenge = self.authenticationChallenge;
|
||||
operation.cacheResponse = self.cacheResponse;
|
||||
operation.redirectResponse = self.redirectResponse;
|
||||
operation.completionQueue = self.completionQueue;
|
||||
operation.completionGroup = self.completionGroup;
|
||||
|
||||
return operation;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,6 +1,5 @@
|
||||
// AFSerialization.h
|
||||
//
|
||||
// Copyright (c) 2013-2014 AFNetworking (http://afnetworking.com)
|
||||
// AFURLRequestSerialization.h
|
||||
// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ )
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -8,10 +7,10 @@
|
||||
// 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
|
||||
@@ -21,10 +20,41 @@
|
||||
// THE SOFTWARE.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
|
||||
#import <TargetConditionals.h>
|
||||
|
||||
#if TARGET_OS_IOS || TARGET_OS_TV
|
||||
#import <UIKit/UIKit.h>
|
||||
#elif TARGET_OS_WATCH
|
||||
#import <WatchKit/WatchKit.h>
|
||||
#endif
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
Returns a percent-escaped string following RFC 3986 for a query string key or value.
|
||||
RFC 3986 states that the following characters are "reserved" characters.
|
||||
- General Delimiters: ":", "#", "[", "]", "@", "?", "/"
|
||||
- Sub-Delimiters: "!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "="
|
||||
|
||||
In RFC 3986 - Section 3.4, it states that the "?" and "/" characters should not be escaped to allow
|
||||
query strings to include a URL. Therefore, all "reserved" characters with the exception of "?" and "/"
|
||||
should be percent-escaped in the query string.
|
||||
|
||||
@param string The string to be percent-escaped.
|
||||
|
||||
@return The percent-escaped string.
|
||||
*/
|
||||
FOUNDATION_EXPORT NSString * AFPercentEscapedStringFromString(NSString *string);
|
||||
|
||||
/**
|
||||
A helper method to generate encoded url query parameters for appending to the end of a URL.
|
||||
|
||||
@param parameters A dictionary of key/values to be encoded.
|
||||
|
||||
@return A url encoded query string
|
||||
*/
|
||||
FOUNDATION_EXPORT NSString * AFQueryStringFromParameters(NSDictionary *parameters);
|
||||
|
||||
/**
|
||||
The `AFURLRequestSerialization` protocol is adopted by an object that encodes parameters for a specified HTTP requests. Request serializers may encode parameters as query strings, HTTP bodies, setting the appropriate HTTP header fields as necessary.
|
||||
|
||||
@@ -41,9 +71,9 @@
|
||||
|
||||
@return A serialized request.
|
||||
*/
|
||||
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
|
||||
withParameters:(id)parameters
|
||||
error:(NSError * __autoreleasing *)error;
|
||||
- (nullable NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
|
||||
withParameters:(nullable id)parameters
|
||||
error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW;
|
||||
|
||||
@end
|
||||
|
||||
@@ -72,42 +102,42 @@ typedef NS_ENUM(NSUInteger, AFHTTPRequestQueryStringSerializationStyle) {
|
||||
|
||||
/**
|
||||
Whether created requests can use the device’s cellular radio (if present). `YES` by default.
|
||||
|
||||
|
||||
@see NSMutableURLRequest -setAllowsCellularAccess:
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL allowsCellularAccess;
|
||||
|
||||
/**
|
||||
The cache policy of created requests. `NSURLRequestUseProtocolCachePolicy` by default.
|
||||
|
||||
|
||||
@see NSMutableURLRequest -setCachePolicy:
|
||||
*/
|
||||
@property (nonatomic, assign) NSURLRequestCachePolicy cachePolicy;
|
||||
|
||||
/**
|
||||
Whether created requests should use the default cookie handling. `YES` by default.
|
||||
|
||||
|
||||
@see NSMutableURLRequest -setHTTPShouldHandleCookies:
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL HTTPShouldHandleCookies;
|
||||
|
||||
/**
|
||||
Whether created requests can continue transmitting data before receiving a response from an earlier transmission. `NO` by default
|
||||
|
||||
|
||||
@see NSMutableURLRequest -setHTTPShouldUsePipelining:
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL HTTPShouldUsePipelining;
|
||||
|
||||
/**
|
||||
The network service type for created requests. `NSURLNetworkServiceTypeDefault` by default.
|
||||
|
||||
|
||||
@see NSMutableURLRequest -setNetworkServiceType:
|
||||
*/
|
||||
@property (nonatomic, assign) NSURLRequestNetworkServiceType networkServiceType;
|
||||
|
||||
/**
|
||||
The timeout interval, in seconds, for created requests. The default timeout interval is 60 seconds.
|
||||
|
||||
|
||||
@see NSMutableURLRequest -setTimeoutInterval:
|
||||
*/
|
||||
@property (nonatomic, assign) NSTimeInterval timeoutInterval;
|
||||
@@ -117,9 +147,14 @@ typedef NS_ENUM(NSUInteger, AFHTTPRequestQueryStringSerializationStyle) {
|
||||
///---------------------------------------
|
||||
|
||||
/**
|
||||
Default HTTP header field values to be applied to serialized requests.
|
||||
Default HTTP header field values to be applied to serialized requests. By default, these include the following:
|
||||
|
||||
- `Accept-Language` with the contents of `NSLocale +preferredLanguages`
|
||||
- `User-Agent` with the contents of various bundle identifiers and OS designations
|
||||
|
||||
@discussion To add or remove default request headers, use `setValue:forHTTPHeaderField:`.
|
||||
*/
|
||||
@property (readonly, nonatomic, strong) NSDictionary *HTTPRequestHeaders;
|
||||
@property (readonly, nonatomic, strong) NSDictionary <NSString *, NSString *> *HTTPRequestHeaders;
|
||||
|
||||
/**
|
||||
Creates and returns a serializer with default configuration.
|
||||
@@ -132,17 +167,17 @@ typedef NS_ENUM(NSUInteger, AFHTTPRequestQueryStringSerializationStyle) {
|
||||
@param field The HTTP header to set a default value for
|
||||
@param value The value set as default for the specified header, or `nil`
|
||||
*/
|
||||
- (void)setValue:(NSString *)value
|
||||
- (void)setValue:(nullable NSString *)value
|
||||
forHTTPHeaderField:(NSString *)field;
|
||||
|
||||
/**
|
||||
Returns the value for the HTTP headers set in the request serializer.
|
||||
|
||||
@param field The HTTP header to retrieve the default value for
|
||||
|
||||
|
||||
@return The value set as default for the specified header, or `nil`
|
||||
*/
|
||||
- (NSString *)valueForHTTPHeaderField:(NSString *)field;
|
||||
- (nullable NSString *)valueForHTTPHeaderField:(NSString *)field;
|
||||
|
||||
/**
|
||||
Sets the "Authorization" HTTP header set in request objects made by the HTTP client to a basic authentication value with Base64-encoded username and password. This overwrites any existing value for this header.
|
||||
@@ -153,12 +188,6 @@ forHTTPHeaderField:(NSString *)field;
|
||||
- (void)setAuthorizationHeaderFieldWithUsername:(NSString *)username
|
||||
password:(NSString *)password;
|
||||
|
||||
/**
|
||||
@deprecated This method has been deprecated. Use -setValue:forHTTPHeaderField: instead.
|
||||
*/
|
||||
- (void)setAuthorizationHeaderFieldWithToken:(NSString *)token DEPRECATED_ATTRIBUTE;
|
||||
|
||||
|
||||
/**
|
||||
Clears any existing value for the "Authorization" HTTP header.
|
||||
*/
|
||||
@@ -171,7 +200,7 @@ forHTTPHeaderField:(NSString *)field;
|
||||
/**
|
||||
HTTP methods for which serialized requests will encode parameters as a query string. `GET`, `HEAD`, and `DELETE` by default.
|
||||
*/
|
||||
@property (nonatomic, strong) NSSet *HTTPMethodsEncodingParametersInURI;
|
||||
@property (nonatomic, strong) NSSet <NSString *> *HTTPMethodsEncodingParametersInURI;
|
||||
|
||||
/**
|
||||
Set the method of query string serialization according to one of the pre-defined styles.
|
||||
@@ -187,19 +216,12 @@ forHTTPHeaderField:(NSString *)field;
|
||||
|
||||
@param block A block that defines a process of encoding parameters into a query string. This block returns the query string and takes three arguments: the request, the parameters to encode, and the error that occurred when attempting to encode parameters for the given request.
|
||||
*/
|
||||
- (void)setQueryStringSerializationWithBlock:(NSString * (^)(NSURLRequest *request, NSDictionary *parameters, NSError * __autoreleasing *error))block;
|
||||
- (void)setQueryStringSerializationWithBlock:(nullable NSString * (^)(NSURLRequest *request, id parameters, NSError * __autoreleasing *error))block;
|
||||
|
||||
///-------------------------------
|
||||
/// @name Creating Request Objects
|
||||
///-------------------------------
|
||||
|
||||
/**
|
||||
@deprecated This method has been deprecated. Use -requestWithMethod:URLString:parameters:error: instead.
|
||||
*/
|
||||
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
|
||||
URLString:(NSString *)URLString
|
||||
parameters:(id)parameters DEPRECATED_ATTRIBUTE;
|
||||
|
||||
/**
|
||||
Creates an `NSMutableURLRequest` object with the specified HTTP method and URL string.
|
||||
|
||||
@@ -208,22 +230,14 @@ forHTTPHeaderField:(NSString *)field;
|
||||
@param method The HTTP method for the request, such as `GET`, `POST`, `PUT`, or `DELETE`. This parameter must not be `nil`.
|
||||
@param URLString The URL string used to create the request URL.
|
||||
@param parameters The parameters to be either set as a query string for `GET` requests, or the request HTTP body.
|
||||
@param error The error that occured while constructing the request.
|
||||
@param error The error that occurred while constructing the request.
|
||||
|
||||
@return An `NSMutableURLRequest` object.
|
||||
*/
|
||||
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
|
||||
URLString:(NSString *)URLString
|
||||
parameters:(id)parameters
|
||||
error:(NSError * __autoreleasing *)error;
|
||||
|
||||
/**
|
||||
@deprecated This method has been deprecated. Use -multipartFormRequestWithMethod:URLString:parameters:constructingBodyWithBlock:error: instead.
|
||||
*/
|
||||
- (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method
|
||||
URLString:(NSString *)URLString
|
||||
parameters:(NSDictionary *)parameters
|
||||
constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block DEPRECATED_ATTRIBUTE;
|
||||
parameters:(nullable id)parameters
|
||||
error:(NSError * _Nullable __autoreleasing *)error;
|
||||
|
||||
/**
|
||||
Creates an `NSMutableURLRequest` object with the specified HTTP method and URLString, and constructs a `multipart/form-data` HTTP body, using the specified parameters and multipart form data block. See http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.2
|
||||
@@ -234,30 +248,30 @@ forHTTPHeaderField:(NSString *)field;
|
||||
@param URLString The URL string used to create the request URL.
|
||||
@param parameters The parameters to be encoded and set in the request HTTP body.
|
||||
@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 error The error that occured while constructing the request.
|
||||
@param error The error that occurred while constructing the request.
|
||||
|
||||
@return An `NSMutableURLRequest` object
|
||||
*/
|
||||
- (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method
|
||||
URLString:(NSString *)URLString
|
||||
parameters:(NSDictionary *)parameters
|
||||
constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block
|
||||
error:(NSError * __autoreleasing *)error;
|
||||
parameters:(nullable NSDictionary <NSString *, id> *)parameters
|
||||
constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block
|
||||
error:(NSError * _Nullable __autoreleasing *)error;
|
||||
|
||||
/**
|
||||
Creates an `NSMutableURLRequest` by removing the `HTTPBodyStream` from a request, and asynchronously writing its contents into the specified file, invoking the completion handler when finished.
|
||||
|
||||
@param request The multipart form request.
|
||||
|
||||
@param request The multipart form request. The `HTTPBodyStream` property of `request` must not be `nil`.
|
||||
@param fileURL The file URL to write multipart form contents to.
|
||||
@param handler A handler block to execute.
|
||||
|
||||
|
||||
@discussion There is a bug in `NSURLSessionTask` that causes requests to not send a `Content-Length` header when streaming contents from an HTTP body, which is notably problematic when interacting with the Amazon S3 webservice. As a workaround, this method takes a request constructed with `multipartFormRequestWithMethod:URLString:parameters:constructingBodyWithBlock:error:`, or any other request with an `HTTPBodyStream`, writes the contents to the specified file and returns a copy of the original request with the `HTTPBodyStream` property set to `nil`. From here, the file can either be passed to `AFURLSessionManager -uploadTaskWithRequest:fromFile:progress:completionHandler:`, or have its contents read into an `NSData` that's assigned to the `HTTPBody` property of the request.
|
||||
|
||||
@see https://github.com/AFNetworking/AFNetworking/issues/1398
|
||||
*/
|
||||
- (NSMutableURLRequest *)requestWithMultipartFormRequest:(NSURLRequest *)request
|
||||
writingStreamContentsToFile:(NSURL *)fileURL
|
||||
completionHandler:(void (^)(NSError *error))handler;
|
||||
completionHandler:(nullable void (^)(NSError * _Nullable error))handler;
|
||||
|
||||
@end
|
||||
|
||||
@@ -281,7 +295,7 @@ forHTTPHeaderField:(NSString *)field;
|
||||
*/
|
||||
- (BOOL)appendPartWithFileURL:(NSURL *)fileURL
|
||||
name:(NSString *)name
|
||||
error:(NSError * __autoreleasing *)error;
|
||||
error:(NSError * _Nullable __autoreleasing *)error;
|
||||
|
||||
/**
|
||||
Appends the HTTP header `Content-Disposition: file; filename=#{filename}; name=#{name}"` and `Content-Type: #{mimeType}`, followed by the encoded file data and the multipart form boundary.
|
||||
@@ -298,7 +312,7 @@ forHTTPHeaderField:(NSString *)field;
|
||||
name:(NSString *)name
|
||||
fileName:(NSString *)fileName
|
||||
mimeType:(NSString *)mimeType
|
||||
error:(NSError * __autoreleasing *)error;
|
||||
error:(NSError * _Nullable __autoreleasing *)error;
|
||||
|
||||
/**
|
||||
Appends the HTTP header `Content-Disposition: file; filename=#{filename}; name=#{name}"` and `Content-Type: #{mimeType}`, followed by the data from the input stream and the multipart form boundary.
|
||||
@@ -309,7 +323,7 @@ forHTTPHeaderField:(NSString *)field;
|
||||
@param length The length of the specified input stream in bytes.
|
||||
@param mimeType The MIME type of the specified data. (For example, the MIME type for a JPEG image is image/jpeg.) For a list of valid MIME types, see http://www.iana.org/assignments/media-types/. This parameter must not be `nil`.
|
||||
*/
|
||||
- (void)appendPartWithInputStream:(NSInputStream *)inputStream
|
||||
- (void)appendPartWithInputStream:(nullable NSInputStream *)inputStream
|
||||
name:(NSString *)name
|
||||
fileName:(NSString *)fileName
|
||||
length:(int64_t)length
|
||||
@@ -345,7 +359,7 @@ forHTTPHeaderField:(NSString *)field;
|
||||
@param headers The HTTP headers to be appended to the form data.
|
||||
@param body The data to be encoded and appended to the form data. This parameter must not be `nil`.
|
||||
*/
|
||||
- (void)appendPartWithHeaders:(NSDictionary *)headers
|
||||
- (void)appendPartWithHeaders:(nullable NSDictionary <NSString *, NSString *> *)headers
|
||||
body:(NSData *)body;
|
||||
|
||||
/**
|
||||
@@ -363,6 +377,9 @@ forHTTPHeaderField:(NSString *)field;
|
||||
|
||||
#pragma mark -
|
||||
|
||||
/**
|
||||
`AFJSONRequestSerializer` is a subclass of `AFHTTPRequestSerializer` that encodes parameters as JSON using `NSJSONSerialization`, setting the `Content-Type` of the encoded request to `application/json`.
|
||||
*/
|
||||
@interface AFJSONRequestSerializer : AFHTTPRequestSerializer
|
||||
|
||||
/**
|
||||
@@ -379,6 +396,11 @@ forHTTPHeaderField:(NSString *)field;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
/**
|
||||
`AFPropertyListRequestSerializer` is a subclass of `AFHTTPRequestSerializer` that encodes parameters as JSON using `NSPropertyListSerializer`, setting the `Content-Type` of the encoded request to `application/x-plist`.
|
||||
*/
|
||||
@interface AFPropertyListRequestSerializer : AFHTTPRequestSerializer
|
||||
|
||||
/**
|
||||
@@ -396,7 +418,7 @@ forHTTPHeaderField:(NSString *)field;
|
||||
|
||||
@param format The property list format.
|
||||
@param writeOptions The property list write options.
|
||||
|
||||
|
||||
@warning The `writeOptions` property is currently unused.
|
||||
*/
|
||||
+ (instancetype)serializerWithFormat:(NSPropertyListFormat)format
|
||||
@@ -404,6 +426,8 @@ forHTTPHeaderField:(NSString *)field;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
///----------------
|
||||
/// @name Constants
|
||||
///----------------
|
||||
@@ -420,21 +444,21 @@ forHTTPHeaderField:(NSString *)field;
|
||||
`AFURLRequestSerializationErrorDomain`
|
||||
AFURLRequestSerializer errors. Error codes for `AFURLRequestSerializationErrorDomain` correspond to codes in `NSURLErrorDomain`.
|
||||
*/
|
||||
extern NSString * const AFURLRequestSerializationErrorDomain;
|
||||
FOUNDATION_EXPORT NSString * const AFURLRequestSerializationErrorDomain;
|
||||
|
||||
/**
|
||||
## User info dictionary keys
|
||||
|
||||
These keys may exist in the user info dictionary, in addition to those defined for NSError.
|
||||
|
||||
- `NSString * const AFNetworkingOperationFailingURLResponseErrorKey`
|
||||
- `NSString * const AFNetworkingOperationFailingURLRequestErrorKey`
|
||||
|
||||
### Constants
|
||||
|
||||
`AFNetworkingOperationFailingURLRequestErrorKey`
|
||||
The corresponding value is an `NSURLRequest` containing the request of the operation associated with an error. This key is only present in the `AFURLRequestSerializationErrorDomain`.
|
||||
*/
|
||||
extern NSString * const AFNetworkingOperationFailingURLRequestErrorKey;
|
||||
FOUNDATION_EXPORT NSString * const AFNetworkingOperationFailingURLRequestErrorKey;
|
||||
|
||||
/**
|
||||
## Throttling Bandwidth for HTTP Request Input Streams
|
||||
@@ -449,5 +473,7 @@ extern NSString * const AFNetworkingOperationFailingURLRequestErrorKey;
|
||||
`kAFUploadStream3GSuggestedDelay`
|
||||
Duration of delay each time a packet is read. Equal to 0.2 seconds.
|
||||
*/
|
||||
extern NSUInteger const kAFUploadStream3GSuggestedPacketSize;
|
||||
extern NSTimeInterval const kAFUploadStream3GSuggestedDelay;
|
||||
FOUNDATION_EXPORT NSUInteger const kAFUploadStream3GSuggestedPacketSize;
|
||||
FOUNDATION_EXPORT NSTimeInterval const kAFUploadStream3GSuggestedDelay;
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// AFSerialization.h
|
||||
//
|
||||
// Copyright (c) 2013-2014 AFNetworking (http://afnetworking.com)
|
||||
// AFURLRequestSerialization.m
|
||||
// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ )
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -8,10 +7,10 @@
|
||||
// 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
|
||||
@@ -22,7 +21,7 @@
|
||||
|
||||
#import "AFURLRequestSerialization.h"
|
||||
|
||||
#if __IPHONE_OS_VERSION_MIN_REQUIRED
|
||||
#if TARGET_OS_IOS || TARGET_OS_WATCH || TARGET_OS_TV
|
||||
#import <MobileCoreServices/MobileCoreServices.h>
|
||||
#else
|
||||
#import <CoreServices/CoreServices.h>
|
||||
@@ -31,47 +30,50 @@
|
||||
NSString * const AFURLRequestSerializationErrorDomain = @"com.alamofire.error.serialization.request";
|
||||
NSString * const AFNetworkingOperationFailingURLRequestErrorKey = @"com.alamofire.serialization.request.error.response";
|
||||
|
||||
typedef NSString * (^AFQueryStringSerializationBlock)(NSURLRequest *request, NSDictionary *parameters, NSError *__autoreleasing *error);
|
||||
typedef NSString * (^AFQueryStringSerializationBlock)(NSURLRequest *request, id parameters, NSError *__autoreleasing *error);
|
||||
|
||||
static NSString * AFBase64EncodedStringFromString(NSString *string) {
|
||||
NSData *data = [NSData dataWithBytes:[string UTF8String] length:[string lengthOfBytesUsingEncoding:NSUTF8StringEncoding]];
|
||||
NSUInteger length = [data length];
|
||||
NSMutableData *mutableData = [NSMutableData dataWithLength:((length + 2) / 3) * 4];
|
||||
/**
|
||||
Returns a percent-escaped string following RFC 3986 for a query string key or value.
|
||||
RFC 3986 states that the following characters are "reserved" characters.
|
||||
- General Delimiters: ":", "#", "[", "]", "@", "?", "/"
|
||||
- Sub-Delimiters: "!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "="
|
||||
|
||||
uint8_t *input = (uint8_t *)[data bytes];
|
||||
uint8_t *output = (uint8_t *)[mutableData mutableBytes];
|
||||
In RFC 3986 - Section 3.4, it states that the "?" and "/" characters should not be escaped to allow
|
||||
query strings to include a URL. Therefore, all "reserved" characters with the exception of "?" and "/"
|
||||
should be percent-escaped in the query string.
|
||||
- parameter string: The string to be percent-escaped.
|
||||
- returns: The percent-escaped string.
|
||||
*/
|
||||
NSString * AFPercentEscapedStringFromString(NSString *string) {
|
||||
static NSString * const kAFCharactersGeneralDelimitersToEncode = @":#[]@"; // does not include "?" or "/" due to RFC 3986 - Section 3.4
|
||||
static NSString * const kAFCharactersSubDelimitersToEncode = @"!$&'()*+,;=";
|
||||
|
||||
for (NSUInteger i = 0; i < length; i += 3) {
|
||||
NSUInteger value = 0;
|
||||
for (NSUInteger j = i; j < (i + 3); j++) {
|
||||
value <<= 8;
|
||||
if (j < length) {
|
||||
value |= (0xFF & input[j]);
|
||||
}
|
||||
}
|
||||
NSMutableCharacterSet * allowedCharacterSet = [[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy];
|
||||
[allowedCharacterSet removeCharactersInString:[kAFCharactersGeneralDelimitersToEncode stringByAppendingString:kAFCharactersSubDelimitersToEncode]];
|
||||
|
||||
static uint8_t const kAFBase64EncodingTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
// FIXME: https://github.com/AFNetworking/AFNetworking/pull/3028
|
||||
// return [string stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet];
|
||||
|
||||
NSUInteger idx = (i / 3) * 4;
|
||||
output[idx + 0] = kAFBase64EncodingTable[(value >> 18) & 0x3F];
|
||||
output[idx + 1] = kAFBase64EncodingTable[(value >> 12) & 0x3F];
|
||||
output[idx + 2] = (i + 1) < length ? kAFBase64EncodingTable[(value >> 6) & 0x3F] : '=';
|
||||
output[idx + 3] = (i + 2) < length ? kAFBase64EncodingTable[(value >> 0) & 0x3F] : '=';
|
||||
static NSUInteger const batchSize = 50;
|
||||
|
||||
NSUInteger index = 0;
|
||||
NSMutableString *escaped = @"".mutableCopy;
|
||||
|
||||
while (index < string.length) {
|
||||
NSUInteger length = MIN(string.length - index, batchSize);
|
||||
NSRange range = NSMakeRange(index, length);
|
||||
|
||||
// To avoid breaking up character sequences such as 👴🏻👮🏽
|
||||
range = [string rangeOfComposedCharacterSequencesForRange:range];
|
||||
|
||||
NSString *substring = [string substringWithRange:range];
|
||||
NSString *encoded = [substring stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet];
|
||||
[escaped appendString:encoded];
|
||||
|
||||
index += range.length;
|
||||
}
|
||||
|
||||
return [[NSString alloc] initWithData:mutableData encoding:NSASCIIStringEncoding];
|
||||
}
|
||||
|
||||
static NSString * const kAFCharactersToBeEscapedInQueryString = @":/?&=;+!@#$()',*";
|
||||
|
||||
static NSString * AFPercentEscapedQueryStringKeyFromStringWithEncoding(NSString *string, NSStringEncoding encoding) {
|
||||
static NSString * const kAFCharactersToLeaveUnescapedInQueryStringPairKey = @"[].";
|
||||
|
||||
return (__bridge_transfer NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (__bridge CFStringRef)string, (__bridge CFStringRef)kAFCharactersToLeaveUnescapedInQueryStringPairKey, (__bridge CFStringRef)kAFCharactersToBeEscapedInQueryString, CFStringConvertNSStringEncodingToEncoding(encoding));
|
||||
}
|
||||
|
||||
static NSString * AFPercentEscapedQueryStringValueFromStringWithEncoding(NSString *string, NSStringEncoding encoding) {
|
||||
return (__bridge_transfer NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (__bridge CFStringRef)string, NULL, (__bridge CFStringRef)kAFCharactersToBeEscapedInQueryString, CFStringConvertNSStringEncodingToEncoding(encoding));
|
||||
return escaped;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
@@ -80,14 +82,14 @@ static NSString * AFPercentEscapedQueryStringValueFromStringWithEncoding(NSStrin
|
||||
@property (readwrite, nonatomic, strong) id field;
|
||||
@property (readwrite, nonatomic, strong) id value;
|
||||
|
||||
- (id)initWithField:(id)field value:(id)value;
|
||||
- (instancetype)initWithField:(id)field value:(id)value;
|
||||
|
||||
- (NSString *)URLEncodedStringValueWithEncoding:(NSStringEncoding)stringEncoding;
|
||||
- (NSString *)URLEncodedStringValue;
|
||||
@end
|
||||
|
||||
@implementation AFQueryStringPair
|
||||
|
||||
- (id)initWithField:(id)field value:(id)value {
|
||||
- (instancetype)initWithField:(id)field value:(id)value {
|
||||
self = [super init];
|
||||
if (!self) {
|
||||
return nil;
|
||||
@@ -99,11 +101,11 @@ static NSString * AFPercentEscapedQueryStringValueFromStringWithEncoding(NSStrin
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *)URLEncodedStringValueWithEncoding:(NSStringEncoding)stringEncoding {
|
||||
- (NSString *)URLEncodedStringValue {
|
||||
if (!self.value || [self.value isEqual:[NSNull null]]) {
|
||||
return AFPercentEscapedQueryStringKeyFromStringWithEncoding([self.field description], stringEncoding);
|
||||
return AFPercentEscapedStringFromString([self.field description]);
|
||||
} else {
|
||||
return [NSString stringWithFormat:@"%@=%@", AFPercentEscapedQueryStringKeyFromStringWithEncoding([self.field description], stringEncoding), AFPercentEscapedQueryStringValueFromStringWithEncoding([self.value description], stringEncoding)];
|
||||
return [NSString stringWithFormat:@"%@=%@", AFPercentEscapedStringFromString([self.field description]), AFPercentEscapedStringFromString([self.value description])];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,13 +113,13 @@ static NSString * AFPercentEscapedQueryStringValueFromStringWithEncoding(NSStrin
|
||||
|
||||
#pragma mark -
|
||||
|
||||
extern NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary);
|
||||
extern NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value);
|
||||
FOUNDATION_EXPORT NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary);
|
||||
FOUNDATION_EXPORT NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value);
|
||||
|
||||
static NSString * AFQueryStringFromParametersWithEncoding(NSDictionary *parameters, NSStringEncoding stringEncoding) {
|
||||
NSString * AFQueryStringFromParameters(NSDictionary *parameters) {
|
||||
NSMutableArray *mutablePairs = [NSMutableArray array];
|
||||
for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
|
||||
[mutablePairs addObject:[pair URLEncodedStringValueWithEncoding:stringEncoding]];
|
||||
[mutablePairs addObject:[pair URLEncodedStringValue]];
|
||||
}
|
||||
|
||||
return [mutablePairs componentsJoinedByString:@"&"];
|
||||
@@ -136,7 +138,7 @@ NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) {
|
||||
NSDictionary *dictionary = value;
|
||||
// Sort dictionary keys to ensure consistent ordering in query string, which is important when deserializing potentially ambiguous sequences, such as an array of dictionaries
|
||||
for (id nestedKey in [dictionary.allKeys sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
|
||||
id nestedValue = [dictionary objectForKey:nestedKey];
|
||||
id nestedValue = dictionary[nestedKey];
|
||||
if (nestedValue) {
|
||||
[mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@[%@]", key, nestedKey] : nestedKey), nestedValue)];
|
||||
}
|
||||
@@ -154,7 +156,7 @@ NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) {
|
||||
} else {
|
||||
[mutableQueryStringComponents addObject:[[AFQueryStringPair alloc] initWithField:key value:value]];
|
||||
}
|
||||
|
||||
|
||||
return mutableQueryStringComponents;
|
||||
}
|
||||
|
||||
@@ -214,15 +216,15 @@ static void *AFHTTPRequestSerializerObserverContext = &AFHTTPRequestSerializerOb
|
||||
[self setValue:[acceptLanguagesComponents componentsJoinedByString:@", "] forHTTPHeaderField:@"Accept-Language"];
|
||||
|
||||
NSString *userAgent = nil;
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wgnu"
|
||||
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
|
||||
#if TARGET_OS_IOS
|
||||
// User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43
|
||||
userAgent = [NSString stringWithFormat:@"%@/%@ (%@; iOS %@; Scale/%0.2f)", [[[NSBundle mainBundle] infoDictionary] objectForKey:(__bridge NSString *)kCFBundleExecutableKey] ?: [[[NSBundle mainBundle] infoDictionary] objectForKey:(__bridge NSString *)kCFBundleIdentifierKey], (__bridge id)CFBundleGetValueForInfoDictionaryKey(CFBundleGetMainBundle(), kCFBundleVersionKey) ?: [[[NSBundle mainBundle] infoDictionary] objectForKey:(__bridge NSString *)kCFBundleVersionKey], [[UIDevice currentDevice] model], [[UIDevice currentDevice] systemVersion], [[UIScreen mainScreen] scale]];
|
||||
userAgent = [NSString stringWithFormat:@"%@/%@ (%@; iOS %@; Scale/%0.2f)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[UIDevice currentDevice] model], [[UIDevice currentDevice] systemVersion], [[UIScreen mainScreen] scale]];
|
||||
#elif TARGET_OS_WATCH
|
||||
// User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43
|
||||
userAgent = [NSString stringWithFormat:@"%@/%@ (%@; watchOS %@; Scale/%0.2f)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[WKInterfaceDevice currentDevice] model], [[WKInterfaceDevice currentDevice] systemVersion], [[WKInterfaceDevice currentDevice] screenScale]];
|
||||
#elif defined(__MAC_OS_X_VERSION_MIN_REQUIRED)
|
||||
userAgent = [NSString stringWithFormat:@"%@/%@ (Mac OS X %@)", [[[NSBundle mainBundle] infoDictionary] objectForKey:(__bridge NSString *)kCFBundleExecutableKey] ?: [[[NSBundle mainBundle] infoDictionary] objectForKey:(__bridge NSString *)kCFBundleIdentifierKey], [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"] ?: [[[NSBundle mainBundle] infoDictionary] objectForKey:(__bridge NSString *)kCFBundleVersionKey], [[NSProcessInfo processInfo] operatingSystemVersionString]];
|
||||
userAgent = [NSString stringWithFormat:@"%@/%@ (Mac OS X %@)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[NSProcessInfo processInfo] operatingSystemVersionString]];
|
||||
#endif
|
||||
#pragma clang diagnostic pop
|
||||
if (userAgent) {
|
||||
if (![userAgent canBeConvertedToEncoding:NSASCIIStringEncoding]) {
|
||||
NSMutableString *mutableUserAgent = [userAgent mutableCopy];
|
||||
@@ -238,7 +240,9 @@ static void *AFHTTPRequestSerializerObserverContext = &AFHTTPRequestSerializerOb
|
||||
|
||||
self.mutableObservedChangedKeyPaths = [NSMutableSet set];
|
||||
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
|
||||
[self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext];
|
||||
if ([self respondsToSelector:NSSelectorFromString(keyPath)]) {
|
||||
[self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext];
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
@@ -246,12 +250,55 @@ static void *AFHTTPRequestSerializerObserverContext = &AFHTTPRequestSerializerOb
|
||||
|
||||
- (void)dealloc {
|
||||
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
|
||||
[self removeObserver:self forKeyPath:keyPath context:AFHTTPRequestSerializerObserverContext];
|
||||
if ([self respondsToSelector:NSSelectorFromString(keyPath)]) {
|
||||
[self removeObserver:self forKeyPath:keyPath context:AFHTTPRequestSerializerObserverContext];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
// Workarounds for crashing behavior using Key-Value Observing with XCTest
|
||||
// See https://github.com/AFNetworking/AFNetworking/issues/2523
|
||||
|
||||
- (void)setAllowsCellularAccess:(BOOL)allowsCellularAccess {
|
||||
[self willChangeValueForKey:NSStringFromSelector(@selector(allowsCellularAccess))];
|
||||
_allowsCellularAccess = allowsCellularAccess;
|
||||
[self didChangeValueForKey:NSStringFromSelector(@selector(allowsCellularAccess))];
|
||||
}
|
||||
|
||||
- (void)setCachePolicy:(NSURLRequestCachePolicy)cachePolicy {
|
||||
[self willChangeValueForKey:NSStringFromSelector(@selector(cachePolicy))];
|
||||
_cachePolicy = cachePolicy;
|
||||
[self didChangeValueForKey:NSStringFromSelector(@selector(cachePolicy))];
|
||||
}
|
||||
|
||||
- (void)setHTTPShouldHandleCookies:(BOOL)HTTPShouldHandleCookies {
|
||||
[self willChangeValueForKey:NSStringFromSelector(@selector(HTTPShouldHandleCookies))];
|
||||
_HTTPShouldHandleCookies = HTTPShouldHandleCookies;
|
||||
[self didChangeValueForKey:NSStringFromSelector(@selector(HTTPShouldHandleCookies))];
|
||||
}
|
||||
|
||||
- (void)setHTTPShouldUsePipelining:(BOOL)HTTPShouldUsePipelining {
|
||||
[self willChangeValueForKey:NSStringFromSelector(@selector(HTTPShouldUsePipelining))];
|
||||
_HTTPShouldUsePipelining = HTTPShouldUsePipelining;
|
||||
[self didChangeValueForKey:NSStringFromSelector(@selector(HTTPShouldUsePipelining))];
|
||||
}
|
||||
|
||||
- (void)setNetworkServiceType:(NSURLRequestNetworkServiceType)networkServiceType {
|
||||
[self willChangeValueForKey:NSStringFromSelector(@selector(networkServiceType))];
|
||||
_networkServiceType = networkServiceType;
|
||||
[self didChangeValueForKey:NSStringFromSelector(@selector(networkServiceType))];
|
||||
}
|
||||
|
||||
- (void)setTimeoutInterval:(NSTimeInterval)timeoutInterval {
|
||||
[self willChangeValueForKey:NSStringFromSelector(@selector(timeoutInterval))];
|
||||
_timeoutInterval = timeoutInterval;
|
||||
[self didChangeValueForKey:NSStringFromSelector(@selector(timeoutInterval))];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (NSDictionary *)HTTPRequestHeaders {
|
||||
return [NSDictionary dictionaryWithDictionary:self.mutableHTTPRequestHeaders];
|
||||
}
|
||||
@@ -269,12 +316,9 @@ forHTTPHeaderField:(NSString *)field
|
||||
- (void)setAuthorizationHeaderFieldWithUsername:(NSString *)username
|
||||
password:(NSString *)password
|
||||
{
|
||||
NSString *basicAuthCredentials = [NSString stringWithFormat:@"%@:%@", username, password];
|
||||
[self setValue:[NSString stringWithFormat:@"Basic %@", AFBase64EncodedStringFromString(basicAuthCredentials)] forHTTPHeaderField:@"Authorization"];
|
||||
}
|
||||
|
||||
- (void)setAuthorizationHeaderFieldWithToken:(NSString *)token {
|
||||
[self setValue:[NSString stringWithFormat:@"Token token=\"%@\"", token] forHTTPHeaderField:@"Authorization"];
|
||||
NSData *basicAuthCredentials = [[NSString stringWithFormat:@"%@:%@", username, password] dataUsingEncoding:NSUTF8StringEncoding];
|
||||
NSString *base64AuthCredentials = [basicAuthCredentials base64EncodedStringWithOptions:(NSDataBase64EncodingOptions)0];
|
||||
[self setValue:[NSString stringWithFormat:@"Basic %@", base64AuthCredentials] forHTTPHeaderField:@"Authorization"];
|
||||
}
|
||||
|
||||
- (void)clearAuthorizationHeader {
|
||||
@@ -288,19 +332,12 @@ forHTTPHeaderField:(NSString *)field
|
||||
self.queryStringSerialization = nil;
|
||||
}
|
||||
|
||||
- (void)setQueryStringSerializationWithBlock:(NSString *(^)(NSURLRequest *, NSDictionary *, NSError *__autoreleasing *))block {
|
||||
- (void)setQueryStringSerializationWithBlock:(NSString *(^)(NSURLRequest *, id, NSError *__autoreleasing *))block {
|
||||
self.queryStringSerialization = block;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
|
||||
URLString:(NSString *)URLString
|
||||
parameters:(id)parameters
|
||||
{
|
||||
return [self requestWithMethod:method URLString:URLString parameters:parameters error:nil];
|
||||
}
|
||||
|
||||
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
|
||||
URLString:(NSString *)URLString
|
||||
parameters:(id)parameters
|
||||
@@ -327,14 +364,6 @@ forHTTPHeaderField:(NSString *)field
|
||||
return mutableRequest;
|
||||
}
|
||||
|
||||
- (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method
|
||||
URLString:(NSString *)URLString
|
||||
parameters:(NSDictionary *)parameters
|
||||
constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block
|
||||
{
|
||||
return [self multipartFormRequestWithMethod:method URLString:URLString parameters:parameters constructingBodyWithBlock:block error:nil];
|
||||
}
|
||||
|
||||
- (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method
|
||||
URLString:(NSString *)URLString
|
||||
parameters:(NSDictionary *)parameters
|
||||
@@ -376,10 +405,7 @@ forHTTPHeaderField:(NSString *)field
|
||||
writingStreamContentsToFile:(NSURL *)fileURL
|
||||
completionHandler:(void (^)(NSError *error))handler
|
||||
{
|
||||
if (!request.HTTPBodyStream) {
|
||||
return [request mutableCopy];
|
||||
}
|
||||
|
||||
NSParameterAssert(request.HTTPBodyStream);
|
||||
NSParameterAssert([fileURL isFileURL]);
|
||||
|
||||
NSInputStream *inputStream = request.HTTPBodyStream;
|
||||
@@ -445,8 +471,8 @@ forHTTPHeaderField:(NSString *)field
|
||||
}
|
||||
}];
|
||||
|
||||
NSString *query = nil;
|
||||
if (parameters) {
|
||||
NSString *query = nil;
|
||||
if (self.queryStringSerialization) {
|
||||
NSError *serializationError;
|
||||
query = self.queryStringSerialization(request, parameters, &serializationError);
|
||||
@@ -461,19 +487,25 @@ forHTTPHeaderField:(NSString *)field
|
||||
} else {
|
||||
switch (self.queryStringSerializationStyle) {
|
||||
case AFHTTPRequestQueryStringDefaultStyle:
|
||||
query = AFQueryStringFromParametersWithEncoding(parameters, self.stringEncoding);
|
||||
query = AFQueryStringFromParameters(parameters);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
|
||||
if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
|
||||
if (query && query.length > 0) {
|
||||
mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]];
|
||||
} else {
|
||||
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
|
||||
[mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
|
||||
}
|
||||
[mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
|
||||
}
|
||||
} else {
|
||||
// #2864: an empty string is a valid x-www-form-urlencoded payload
|
||||
if (!query) {
|
||||
query = @"";
|
||||
}
|
||||
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
|
||||
[mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
|
||||
}
|
||||
[mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
|
||||
}
|
||||
|
||||
return mutableRequest;
|
||||
@@ -481,6 +513,14 @@ forHTTPHeaderField:(NSString *)field
|
||||
|
||||
#pragma mark - NSKeyValueObserving
|
||||
|
||||
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
|
||||
if ([AFHTTPRequestSerializerObservedKeyPaths() containsObject:key]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
return [super automaticallyNotifiesObserversForKey:key];
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(NSString *)keyPath
|
||||
ofObject:(__unused id)object
|
||||
change:(NSDictionary *)change
|
||||
@@ -501,14 +541,14 @@ forHTTPHeaderField:(NSString *)field
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (id)initWithCoder:(NSCoder *)decoder {
|
||||
- (instancetype)initWithCoder:(NSCoder *)decoder {
|
||||
self = [self init];
|
||||
if (!self) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
self.mutableHTTPRequestHeaders = [[decoder decodeObjectOfClass:[NSDictionary class] forKey:NSStringFromSelector(@selector(mutableHTTPRequestHeaders))] mutableCopy];
|
||||
self.queryStringSerializationStyle = [[decoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(queryStringSerializationStyle))] unsignedIntegerValue];
|
||||
self.queryStringSerializationStyle = (AFHTTPRequestQueryStringSerializationStyle)[[decoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(queryStringSerializationStyle))] unsignedIntegerValue];
|
||||
|
||||
return self;
|
||||
}
|
||||
@@ -520,12 +560,12 @@ forHTTPHeaderField:(NSString *)field
|
||||
|
||||
#pragma mark - NSCopying
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone {
|
||||
- (instancetype)copyWithZone:(NSZone *)zone {
|
||||
AFHTTPRequestSerializer *serializer = [[[self class] allocWithZone:zone] init];
|
||||
serializer.mutableHTTPRequestHeaders = [self.mutableHTTPRequestHeaders mutableCopyWithZone:zone];
|
||||
serializer.queryStringSerializationStyle = self.queryStringSerializationStyle;
|
||||
serializer.queryStringSerialization = self.queryStringSerialization;
|
||||
|
||||
|
||||
return serializer;
|
||||
}
|
||||
|
||||
@@ -552,7 +592,6 @@ static inline NSString * AFMultipartFormFinalBoundary(NSString *boundary) {
|
||||
}
|
||||
|
||||
static inline NSString * AFContentTypeForPathExtension(NSString *extension) {
|
||||
#ifdef __UTTYPE__
|
||||
NSString *UTI = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)extension, NULL);
|
||||
NSString *contentType = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)UTI, kUTTagClassMIMEType);
|
||||
if (!contentType) {
|
||||
@@ -560,10 +599,6 @@ static inline NSString * AFContentTypeForPathExtension(NSString *extension) {
|
||||
} else {
|
||||
return contentType;
|
||||
}
|
||||
#else
|
||||
#pragma unused (extension)
|
||||
return @"application/octet-stream";
|
||||
#endif
|
||||
}
|
||||
|
||||
NSUInteger const kAFUploadStream3GSuggestedPacketSize = 1024 * 16;
|
||||
@@ -594,7 +629,7 @@ NSTimeInterval const kAFUploadStream3GSuggestedDelay = 0.2;
|
||||
@property (readonly, nonatomic, assign) unsigned long long contentLength;
|
||||
@property (readonly, nonatomic, assign, getter = isEmpty) BOOL empty;
|
||||
|
||||
- (id)initWithStringEncoding:(NSStringEncoding)encoding;
|
||||
- (instancetype)initWithStringEncoding:(NSStringEncoding)encoding;
|
||||
- (void)setInitialAndFinalBoundaries;
|
||||
- (void)appendHTTPBodyPart:(AFHTTPBodyPart *)bodyPart;
|
||||
@end
|
||||
@@ -610,8 +645,8 @@ NSTimeInterval const kAFUploadStream3GSuggestedDelay = 0.2;
|
||||
|
||||
@implementation AFStreamingMultipartFormData
|
||||
|
||||
- (id)initWithURLRequest:(NSMutableURLRequest *)urlRequest
|
||||
stringEncoding:(NSStringEncoding)encoding
|
||||
- (instancetype)initWithURLRequest:(NSMutableURLRequest *)urlRequest
|
||||
stringEncoding:(NSStringEncoding)encoding
|
||||
{
|
||||
self = [super init];
|
||||
if (!self) {
|
||||
@@ -670,17 +705,17 @@ NSTimeInterval const kAFUploadStream3GSuggestedDelay = 0.2;
|
||||
if (!fileAttributes) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
|
||||
NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary];
|
||||
[mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"; filename=\"%@\"", name, fileName] forKey:@"Content-Disposition"];
|
||||
[mutableHeaders setValue:mimeType forKey:@"Content-Type"];
|
||||
|
||||
|
||||
AFHTTPBodyPart *bodyPart = [[AFHTTPBodyPart alloc] init];
|
||||
bodyPart.stringEncoding = self.stringEncoding;
|
||||
bodyPart.headers = mutableHeaders;
|
||||
bodyPart.boundary = self.boundary;
|
||||
bodyPart.body = fileURL;
|
||||
bodyPart.bodyContentLength = [[fileAttributes objectForKey:NSFileSize] unsignedLongLongValue];
|
||||
bodyPart.bodyContentLength = [fileAttributes[NSFileSize] unsignedLongLongValue];
|
||||
[self.bodyStream appendHTTPBodyPart:bodyPart];
|
||||
|
||||
return YES;
|
||||
@@ -794,16 +829,13 @@ NSTimeInterval const kAFUploadStream3GSuggestedDelay = 0.2;
|
||||
@end
|
||||
|
||||
@implementation AFMultipartBodyStream
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wimplicit-atomic-properties"
|
||||
#if (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000) || (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 1100)
|
||||
@synthesize delegate;
|
||||
#endif
|
||||
@synthesize streamStatus;
|
||||
@synthesize streamError;
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
- (id)initWithStringEncoding:(NSStringEncoding)encoding {
|
||||
- (instancetype)initWithStringEncoding:(NSStringEncoding)encoding {
|
||||
self = [super init];
|
||||
if (!self) {
|
||||
return nil;
|
||||
@@ -823,7 +855,7 @@ NSTimeInterval const kAFUploadStream3GSuggestedDelay = 0.2;
|
||||
bodyPart.hasFinalBoundary = NO;
|
||||
}
|
||||
|
||||
[[self.HTTPBodyParts objectAtIndex:0] setHasInitialBoundary:YES];
|
||||
[[self.HTTPBodyParts firstObject] setHasInitialBoundary:YES];
|
||||
[[self.HTTPBodyParts lastObject] setHasFinalBoundary:YES];
|
||||
}
|
||||
}
|
||||
@@ -847,15 +879,13 @@ NSTimeInterval const kAFUploadStream3GSuggestedDelay = 0.2;
|
||||
|
||||
NSInteger totalNumberOfBytesRead = 0;
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wgnu"
|
||||
while ((NSUInteger)totalNumberOfBytesRead < MIN(length, self.numberOfBytesInPacket)) {
|
||||
if (!self.currentHTTPBodyPart || ![self.currentHTTPBodyPart hasBytesAvailable]) {
|
||||
if (!(self.currentHTTPBodyPart = [self.HTTPBodyPartEnumerator nextObject])) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
NSUInteger maxLength = length - (NSUInteger)totalNumberOfBytesRead;
|
||||
NSUInteger maxLength = MIN(length, self.numberOfBytesInPacket) - (NSUInteger)totalNumberOfBytesRead;
|
||||
NSInteger numberOfBytesRead = [self.currentHTTPBodyPart read:&buffer[totalNumberOfBytesRead] maxLength:maxLength];
|
||||
if (numberOfBytesRead == -1) {
|
||||
self.streamError = self.currentHTTPBodyPart.inputStream.streamError;
|
||||
@@ -869,7 +899,6 @@ NSTimeInterval const kAFUploadStream3GSuggestedDelay = 0.2;
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
return totalNumberOfBytesRead;
|
||||
}
|
||||
@@ -946,7 +975,7 @@ NSTimeInterval const kAFUploadStream3GSuggestedDelay = 0.2;
|
||||
|
||||
#pragma mark - NSCopying
|
||||
|
||||
-(id)copyWithZone:(NSZone *)zone {
|
||||
- (instancetype)copyWithZone:(NSZone *)zone {
|
||||
AFMultipartBodyStream *bodyStreamCopy = [[[self class] allocWithZone:zone] initWithStringEncoding:self.stringEncoding];
|
||||
|
||||
for (AFHTTPBodyPart *bodyPart in self.HTTPBodyParts) {
|
||||
@@ -983,7 +1012,7 @@ typedef enum {
|
||||
|
||||
@implementation AFHTTPBodyPart
|
||||
|
||||
- (id)init {
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (!self) {
|
||||
return nil;
|
||||
@@ -1050,8 +1079,6 @@ typedef enum {
|
||||
return YES;
|
||||
}
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wcovered-switch-default"
|
||||
switch (self.inputStream.streamStatus) {
|
||||
case NSStreamStatusNotOpen:
|
||||
case NSStreamStatusOpening:
|
||||
@@ -1065,7 +1092,6 @@ typedef enum {
|
||||
default:
|
||||
return NO;
|
||||
}
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
|
||||
- (NSInteger)read:(uint8_t *)buffer
|
||||
@@ -1110,11 +1136,8 @@ typedef enum {
|
||||
intoBuffer:(uint8_t *)buffer
|
||||
maxLength:(NSUInteger)length
|
||||
{
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wgnu"
|
||||
NSRange range = NSMakeRange((NSUInteger)_phaseReadOffset, MIN([data length] - ((NSUInteger)_phaseReadOffset), length));
|
||||
[data getBytes:buffer range:range];
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
_phaseReadOffset += range.length;
|
||||
|
||||
@@ -1127,12 +1150,12 @@ typedef enum {
|
||||
|
||||
- (BOOL)transitionToNextPhase {
|
||||
if (![[NSThread currentThread] isMainThread]) {
|
||||
[self performSelectorOnMainThread:@selector(transitionToNextPhase) withObject:nil waitUntilDone:YES];
|
||||
dispatch_sync(dispatch_get_main_queue(), ^{
|
||||
[self transitionToNextPhase];
|
||||
});
|
||||
return YES;
|
||||
}
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wcovered-switch-default"
|
||||
switch (_phase) {
|
||||
case AFEncapsulationBoundaryPhase:
|
||||
_phase = AFHeaderPhase;
|
||||
@@ -1152,22 +1175,21 @@ typedef enum {
|
||||
break;
|
||||
}
|
||||
_phaseReadOffset = 0;
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
#pragma mark - NSCopying
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone {
|
||||
- (instancetype)copyWithZone:(NSZone *)zone {
|
||||
AFHTTPBodyPart *bodyPart = [[[self class] allocWithZone:zone] init];
|
||||
|
||||
|
||||
bodyPart.stringEncoding = self.stringEncoding;
|
||||
bodyPart.headers = self.headers;
|
||||
bodyPart.bodyContentLength = self.bodyContentLength;
|
||||
bodyPart.body = self.body;
|
||||
bodyPart.boundary = self.boundary;
|
||||
|
||||
|
||||
return bodyPart;
|
||||
}
|
||||
|
||||
@@ -1208,11 +1230,10 @@ typedef enum {
|
||||
[mutableRequest setValue:value forHTTPHeaderField:field];
|
||||
}
|
||||
}];
|
||||
|
||||
|
||||
if (parameters) {
|
||||
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
|
||||
NSString *charset = (__bridge NSString *)CFStringConvertEncodingToIANACharSetName(CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding));
|
||||
[mutableRequest setValue:[NSString stringWithFormat:@"application/json; charset=%@", charset] forHTTPHeaderField:@"Content-Type"];
|
||||
[mutableRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
|
||||
}
|
||||
|
||||
[mutableRequest setHTTPBody:[NSJSONSerialization dataWithJSONObject:parameters options:self.writingOptions error:error]];
|
||||
@@ -1223,7 +1244,7 @@ typedef enum {
|
||||
|
||||
#pragma mark - NSSecureCoding
|
||||
|
||||
- (id)initWithCoder:(NSCoder *)decoder {
|
||||
- (instancetype)initWithCoder:(NSCoder *)decoder {
|
||||
self = [super initWithCoder:decoder];
|
||||
if (!self) {
|
||||
return nil;
|
||||
@@ -1242,7 +1263,7 @@ typedef enum {
|
||||
|
||||
#pragma mark - NSCopying
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone {
|
||||
- (instancetype)copyWithZone:(NSZone *)zone {
|
||||
AFJSONRequestSerializer *serializer = [super copyWithZone:zone];
|
||||
serializer.writingOptions = self.writingOptions;
|
||||
|
||||
@@ -1291,8 +1312,7 @@ typedef enum {
|
||||
|
||||
if (parameters) {
|
||||
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
|
||||
NSString *charset = (__bridge NSString *)CFStringConvertEncodingToIANACharSetName(CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding));
|
||||
[mutableRequest setValue:[NSString stringWithFormat:@"application/x-plist; charset=%@", charset] forHTTPHeaderField:@"Content-Type"];
|
||||
[mutableRequest setValue:@"application/x-plist" forHTTPHeaderField:@"Content-Type"];
|
||||
}
|
||||
|
||||
[mutableRequest setHTTPBody:[NSPropertyListSerialization dataWithPropertyList:parameters format:self.format options:self.writeOptions error:error]];
|
||||
@@ -1303,13 +1323,13 @@ typedef enum {
|
||||
|
||||
#pragma mark - NSSecureCoding
|
||||
|
||||
- (id)initWithCoder:(NSCoder *)decoder {
|
||||
- (instancetype)initWithCoder:(NSCoder *)decoder {
|
||||
self = [super initWithCoder:decoder];
|
||||
if (!self) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
self.format = [[decoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(format))] unsignedIntegerValue];
|
||||
self.format = (NSPropertyListFormat)[[decoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(format))] unsignedIntegerValue];
|
||||
self.writeOptions = [[decoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(writeOptions))] unsignedIntegerValue];
|
||||
|
||||
return self;
|
||||
@@ -1324,7 +1344,7 @@ typedef enum {
|
||||
|
||||
#pragma mark - NSCopying
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone {
|
||||
- (instancetype)copyWithZone:(NSZone *)zone {
|
||||
AFPropertyListRequestSerializer *serializer = [super copyWithZone:zone];
|
||||
serializer.format = self.format;
|
||||
serializer.writeOptions = self.writeOptions;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// AFSerialization.h
|
||||
//
|
||||
// Copyright (c) 2013-2014 AFNetworking (http://afnetworking.com)
|
||||
// AFURLResponseSerialization.h
|
||||
// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ )
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -8,10 +7,10 @@
|
||||
// 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
|
||||
@@ -23,6 +22,8 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <CoreGraphics/CoreGraphics.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
The `AFURLResponseSerialization` protocol is adopted by an object that decodes data into a more useful object representation, according to details in the server response. Response serializers may additionally perform validation on the incoming response and data.
|
||||
|
||||
@@ -39,9 +40,9 @@
|
||||
|
||||
@return The object decoded from the specified response data.
|
||||
*/
|
||||
- (id)responseObjectForResponse:(NSURLResponse *)response
|
||||
data:(NSData *)data
|
||||
error:(NSError *__autoreleasing *)error;
|
||||
- (nullable id)responseObjectForResponse:(nullable NSURLResponse *)response
|
||||
data:(nullable NSData *)data
|
||||
error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW;
|
||||
|
||||
@end
|
||||
|
||||
@@ -54,8 +55,10 @@
|
||||
*/
|
||||
@interface AFHTTPResponseSerializer : NSObject <AFURLResponseSerialization>
|
||||
|
||||
- (instancetype)init;
|
||||
|
||||
/**
|
||||
The string encoding used to serialize parameters.
|
||||
The string encoding used to serialize data received from the server, when no string encoding is specified by the response. `NSUTF8StringEncoding` by default.
|
||||
*/
|
||||
@property (nonatomic, assign) NSStringEncoding stringEncoding;
|
||||
|
||||
@@ -73,12 +76,12 @@
|
||||
|
||||
See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
|
||||
*/
|
||||
@property (nonatomic, copy) NSIndexSet *acceptableStatusCodes;
|
||||
@property (nonatomic, copy, nullable) NSIndexSet *acceptableStatusCodes;
|
||||
|
||||
/**
|
||||
The acceptable MIME types for responses. When non-`nil`, responses with a `Content-Type` with MIME types that do not intersect with the set will result in an error during validation.
|
||||
*/
|
||||
@property (nonatomic, copy) NSSet *acceptableContentTypes;
|
||||
@property (nonatomic, copy, nullable) NSSet <NSString *> *acceptableContentTypes;
|
||||
|
||||
/**
|
||||
Validates the specified response and data.
|
||||
@@ -91,9 +94,9 @@
|
||||
|
||||
@return `YES` if the response is valid, otherwise `NO`.
|
||||
*/
|
||||
- (BOOL)validateResponse:(NSHTTPURLResponse *)response
|
||||
data:(NSData *)data
|
||||
error:(NSError *__autoreleasing *)error;
|
||||
- (BOOL)validateResponse:(nullable NSHTTPURLResponse *)response
|
||||
data:(nullable NSData *)data
|
||||
error:(NSError * _Nullable __autoreleasing *)error;
|
||||
|
||||
@end
|
||||
|
||||
@@ -111,6 +114,8 @@
|
||||
*/
|
||||
@interface AFJSONResponseSerializer : AFHTTPResponseSerializer
|
||||
|
||||
- (instancetype)init;
|
||||
|
||||
/**
|
||||
Options for reading the response JSON data and creating the Foundation objects. For possible values, see the `NSJSONSerialization` documentation section "NSJSONReadingOptions". `0` by default.
|
||||
*/
|
||||
@@ -133,9 +138,9 @@
|
||||
#pragma mark -
|
||||
|
||||
/**
|
||||
`AFXMLParserSerializer` is a subclass of `AFHTTPResponseSerializer` that validates and decodes XML responses as an `NSXMLParser` objects.
|
||||
`AFXMLParserResponseSerializer` is a subclass of `AFHTTPResponseSerializer` that validates and decodes XML responses as an `NSXMLParser` objects.
|
||||
|
||||
By default, `AFXMLParserSerializer` accepts the following MIME types, which includes the official standard, `application/xml`, as well as other commonly-used types:
|
||||
By default, `AFXMLParserResponseSerializer` accepts the following MIME types, which includes the official standard, `application/xml`, as well as other commonly-used types:
|
||||
|
||||
- `application/xml`
|
||||
- `text/xml`
|
||||
@@ -149,15 +154,17 @@
|
||||
#ifdef __MAC_OS_X_VERSION_MIN_REQUIRED
|
||||
|
||||
/**
|
||||
`AFXMLDocumentSerializer` is a subclass of `AFHTTPResponseSerializer` that validates and decodes XML responses as an `NSXMLDocument` objects.
|
||||
`AFXMLDocumentResponseSerializer` is a subclass of `AFHTTPResponseSerializer` that validates and decodes XML responses as an `NSXMLDocument` objects.
|
||||
|
||||
By default, `AFXMLDocumentSerializer` accepts the following MIME types, which includes the official standard, `application/xml`, as well as other commonly-used types:
|
||||
By default, `AFXMLDocumentResponseSerializer` accepts the following MIME types, which includes the official standard, `application/xml`, as well as other commonly-used types:
|
||||
|
||||
- `application/xml`
|
||||
- `text/xml`
|
||||
*/
|
||||
@interface AFXMLDocumentResponseSerializer : AFHTTPResponseSerializer
|
||||
|
||||
- (instancetype)init;
|
||||
|
||||
/**
|
||||
Input and output options specifically intended for `NSXMLDocument` objects. For possible values, see the `NSJSONSerialization` documentation section "NSJSONReadingOptions". `0` by default.
|
||||
*/
|
||||
@@ -177,14 +184,16 @@
|
||||
#pragma mark -
|
||||
|
||||
/**
|
||||
`AFPropertyListSerializer` is a subclass of `AFHTTPResponseSerializer` that validates and decodes XML responses as an `NSXMLDocument` objects.
|
||||
`AFPropertyListResponseSerializer` is a subclass of `AFHTTPResponseSerializer` that validates and decodes XML responses as an `NSXMLDocument` objects.
|
||||
|
||||
By default, `AFPropertyListSerializer` accepts the following MIME types:
|
||||
By default, `AFPropertyListResponseSerializer` accepts the following MIME types:
|
||||
|
||||
- `application/x-plist`
|
||||
*/
|
||||
@interface AFPropertyListResponseSerializer : AFHTTPResponseSerializer
|
||||
|
||||
- (instancetype)init;
|
||||
|
||||
/**
|
||||
The property list format. Possible values are described in "NSPropertyListFormat".
|
||||
*/
|
||||
@@ -209,9 +218,9 @@
|
||||
#pragma mark -
|
||||
|
||||
/**
|
||||
`AFImageSerializer` is a subclass of `AFHTTPResponseSerializer` that validates and decodes image responses.
|
||||
`AFImageResponseSerializer` is a subclass of `AFHTTPResponseSerializer` that validates and decodes image responses.
|
||||
|
||||
By default, `AFImageSerializer` accepts the following MIME types, which correspond to the image formats supported by UIImage or NSImage:
|
||||
By default, `AFImageResponseSerializer` accepts the following MIME types, which correspond to the image formats supported by UIImage or NSImage:
|
||||
|
||||
- `image/tiff`
|
||||
- `image/jpeg`
|
||||
@@ -226,7 +235,7 @@
|
||||
*/
|
||||
@interface AFImageResponseSerializer : AFHTTPResponseSerializer
|
||||
|
||||
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
|
||||
#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH
|
||||
/**
|
||||
The scale factor used when interpreting the image data to construct `responseImage`. Specifying a scale factor of 1.0 results in an image whose size matches the pixel-based dimensions of the image. Applying a different scale factor changes the size of the image as reported by the size property. This is set to the value of scale of the main screen by default, which automatically scales images for retina displays, for instance.
|
||||
*/
|
||||
@@ -250,14 +259,14 @@
|
||||
/**
|
||||
The component response serializers.
|
||||
*/
|
||||
@property (readonly, nonatomic, copy) NSArray *responseSerializers;
|
||||
@property (readonly, nonatomic, copy) NSArray <id<AFURLResponseSerialization>> *responseSerializers;
|
||||
|
||||
/**
|
||||
Creates and returns a compound serializer comprised of the specified response serializers.
|
||||
|
||||
@warning Each response serializer specified must be a subclass of `AFHTTPResponseSerializer`, and response to `-validateResponse:data:error:`.
|
||||
*/
|
||||
+ (instancetype)compoundSerializerWithResponseSerializers:(NSArray *)responseSerializers;
|
||||
+ (instancetype)compoundSerializerWithResponseSerializers:(NSArray <id<AFURLResponseSerialization>> *)responseSerializers;
|
||||
|
||||
@end
|
||||
|
||||
@@ -277,7 +286,7 @@
|
||||
`AFURLResponseSerializationErrorDomain`
|
||||
AFURLResponseSerializer errors. Error codes for `AFURLResponseSerializationErrorDomain` correspond to codes in `NSURLErrorDomain`.
|
||||
*/
|
||||
extern NSString * const AFURLResponseSerializationErrorDomain;
|
||||
FOUNDATION_EXPORT NSString * const AFURLResponseSerializationErrorDomain;
|
||||
|
||||
/**
|
||||
## User info dictionary keys
|
||||
@@ -291,12 +300,12 @@ extern NSString * const AFURLResponseSerializationErrorDomain;
|
||||
|
||||
`AFNetworkingOperationFailingURLResponseErrorKey`
|
||||
The corresponding value is an `NSURLResponse` containing the response of the operation associated with an error. This key is only present in the `AFURLResponseSerializationErrorDomain`.
|
||||
|
||||
|
||||
`AFNetworkingOperationFailingURLResponseDataErrorKey`
|
||||
The corresponding value is an `NSData` containing the original data of the operation associated with an error. This key is only present in the `AFURLResponseSerializationErrorDomain`.
|
||||
*/
|
||||
extern NSString * const AFNetworkingOperationFailingURLResponseErrorKey;
|
||||
|
||||
extern NSString * const AFNetworkingOperationFailingURLResponseDataErrorKey;
|
||||
FOUNDATION_EXPORT NSString * const AFNetworkingOperationFailingURLResponseErrorKey;
|
||||
|
||||
FOUNDATION_EXPORT NSString * const AFNetworkingOperationFailingURLResponseDataErrorKey;
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
154
src/ios/AFNetworking/AFURLResponseSerialization.m
Normal file → Executable file
154
src/ios/AFNetworking/AFURLResponseSerialization.m
Normal file → Executable file
@@ -1,6 +1,5 @@
|
||||
// AFSerialization.h
|
||||
//
|
||||
// Copyright (c) 2013-2014 AFNetworking (http://afnetworking.com)
|
||||
// AFURLResponseSerialization.m
|
||||
// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ )
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -8,10 +7,10 @@
|
||||
// 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
|
||||
@@ -22,8 +21,12 @@
|
||||
|
||||
#import "AFURLResponseSerialization.h"
|
||||
|
||||
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
|
||||
#import <TargetConditionals.h>
|
||||
|
||||
#if TARGET_OS_IOS
|
||||
#import <UIKit/UIKit.h>
|
||||
#elif TARGET_OS_WATCH
|
||||
#import <WatchKit/WatchKit.h>
|
||||
#elif defined(__MAC_OS_X_VERSION_MIN_REQUIRED)
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#endif
|
||||
@@ -68,11 +71,11 @@ static id AFJSONObjectByRemovingKeysWithNullValues(id JSONObject, NSJSONReadingO
|
||||
} else if ([JSONObject isKindOfClass:[NSDictionary class]]) {
|
||||
NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionaryWithDictionary:JSONObject];
|
||||
for (id <NSCopying> key in [(NSDictionary *)JSONObject allKeys]) {
|
||||
id value = [(NSDictionary *)JSONObject objectForKey:key];
|
||||
id value = (NSDictionary *)JSONObject[key];
|
||||
if (!value || [value isEqual:[NSNull null]]) {
|
||||
[mutableDictionary removeObjectForKey:key];
|
||||
} else if ([value isKindOfClass:[NSArray class]] || [value isKindOfClass:[NSDictionary class]]) {
|
||||
[mutableDictionary setObject:AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions) forKey:key];
|
||||
mutableDictionary[key] = AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,8 +115,10 @@ static id AFJSONObjectByRemovingKeysWithNullValues(id JSONObject, NSJSONReadingO
|
||||
NSError *validationError = nil;
|
||||
|
||||
if (response && [response isKindOfClass:[NSHTTPURLResponse class]]) {
|
||||
if (self.acceptableContentTypes && ![self.acceptableContentTypes containsObject:[response MIMEType]]) {
|
||||
if ([data length] > 0) {
|
||||
if (self.acceptableContentTypes && ![self.acceptableContentTypes containsObject:[response MIMEType]] &&
|
||||
!([response MIMEType] == nil && [data length] == 0)) {
|
||||
|
||||
if ([data length] > 0 && [response URL]) {
|
||||
NSMutableDictionary *mutableUserInfo = [@{
|
||||
NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedStringFromTable(@"Request failed: unacceptable content-type: %@", @"AFNetworking", nil), [response MIMEType]],
|
||||
NSURLErrorFailingURLErrorKey:[response URL],
|
||||
@@ -129,7 +134,7 @@ static id AFJSONObjectByRemovingKeysWithNullValues(id JSONObject, NSJSONReadingO
|
||||
responseIsValid = NO;
|
||||
}
|
||||
|
||||
if (self.acceptableStatusCodes && ![self.acceptableStatusCodes containsIndex:(NSUInteger)response.statusCode]) {
|
||||
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],
|
||||
@@ -170,7 +175,7 @@ static id AFJSONObjectByRemovingKeysWithNullValues(id JSONObject, NSJSONReadingO
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (id)initWithCoder:(NSCoder *)decoder {
|
||||
- (instancetype)initWithCoder:(NSCoder *)decoder {
|
||||
self = [self init];
|
||||
if (!self) {
|
||||
return nil;
|
||||
@@ -189,7 +194,7 @@ static id AFJSONObjectByRemovingKeysWithNullValues(id JSONObject, NSJSONReadingO
|
||||
|
||||
#pragma mark - NSCopying
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone {
|
||||
- (instancetype)copyWithZone:(NSZone *)zone {
|
||||
AFHTTPResponseSerializer *serializer = [[[self class] allocWithZone:zone] init];
|
||||
serializer.acceptableStatusCodes = [self.acceptableStatusCodes copyWithZone:zone];
|
||||
serializer.acceptableContentTypes = [self.acceptableContentTypes copyWithZone:zone];
|
||||
@@ -237,56 +242,31 @@ static id AFJSONObjectByRemovingKeysWithNullValues(id JSONObject, NSJSONReadingO
|
||||
}
|
||||
}
|
||||
|
||||
// Workaround for behavior of Rails to return a single space for `head :ok` (a workaround for a bug in Safari), which is not interpreted as valid input by NSJSONSerialization.
|
||||
// See https://github.com/rails/rails/issues/1742
|
||||
NSStringEncoding stringEncoding = self.stringEncoding;
|
||||
if (response.textEncodingName) {
|
||||
CFStringEncoding encoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName);
|
||||
if (encoding != kCFStringEncodingInvalidId) {
|
||||
stringEncoding = CFStringConvertEncodingToNSStringEncoding(encoding);
|
||||
}
|
||||
}
|
||||
|
||||
id responseObject = nil;
|
||||
NSError *serializationError = nil;
|
||||
@autoreleasepool {
|
||||
NSString *responseString = [[NSString alloc] initWithData:data encoding:stringEncoding];
|
||||
if (responseString && ![responseString isEqualToString:@" "]) {
|
||||
// Workaround for a bug in NSJSONSerialization when Unicode character escape codes are used instead of the actual character
|
||||
// See http://stackoverflow.com/a/12843465/157142
|
||||
data = [responseString dataUsingEncoding:NSUTF8StringEncoding];
|
||||
|
||||
if (data) {
|
||||
if ([data length] > 0) {
|
||||
responseObject = [NSJSONSerialization JSONObjectWithData:data options:self.readingOptions error:&serializationError];
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
} else {
|
||||
NSDictionary *userInfo = @{
|
||||
NSLocalizedDescriptionKey: NSLocalizedStringFromTable(@"Data failed decoding as a UTF-8 string", @"AFNetworking", nil),
|
||||
NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:NSLocalizedStringFromTable(@"Could not decode string: %@", @"AFNetworking", nil), responseString]
|
||||
};
|
||||
|
||||
serializationError = [NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorCannotDecodeContentData userInfo:userInfo];
|
||||
}
|
||||
}
|
||||
// Workaround for behavior of Rails to return a single space for `head :ok` (a workaround for a bug in Safari), which is not interpreted as valid input by NSJSONSerialization.
|
||||
// See https://github.com/rails/rails/issues/1742
|
||||
BOOL isSpace = [data isEqualToData:[NSData dataWithBytes:" " length:1]];
|
||||
if (data.length > 0 && !isSpace) {
|
||||
responseObject = [NSJSONSerialization JSONObjectWithData:data options:self.readingOptions error:&serializationError];
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
|
||||
if (self.removesKeysWithNullValues && responseObject) {
|
||||
responseObject = AFJSONObjectByRemovingKeysWithNullValues(responseObject, self.readingOptions);
|
||||
}
|
||||
|
||||
|
||||
if (error) {
|
||||
*error = AFErrorWithUnderlyingError(serializationError, *error);
|
||||
}
|
||||
|
||||
|
||||
return responseObject;
|
||||
}
|
||||
|
||||
#pragma mark - NSSecureCoding
|
||||
|
||||
- (id)initWithCoder:(NSCoder *)decoder {
|
||||
- (instancetype)initWithCoder:(NSCoder *)decoder {
|
||||
self = [super initWithCoder:decoder];
|
||||
if (!self) {
|
||||
return nil;
|
||||
@@ -307,7 +287,7 @@ static id AFJSONObjectByRemovingKeysWithNullValues(id JSONObject, NSJSONReadingO
|
||||
|
||||
#pragma mark - NSCopying
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone {
|
||||
- (instancetype)copyWithZone:(NSZone *)zone {
|
||||
AFJSONResponseSerializer *serializer = [[[self class] allocWithZone:zone] init];
|
||||
serializer.readingOptions = self.readingOptions;
|
||||
serializer.removesKeysWithNullValues = self.removesKeysWithNullValues;
|
||||
@@ -407,7 +387,7 @@ static id AFJSONObjectByRemovingKeysWithNullValues(id JSONObject, NSJSONReadingO
|
||||
|
||||
#pragma mark - NSSecureCoding
|
||||
|
||||
- (id)initWithCoder:(NSCoder *)decoder {
|
||||
- (instancetype)initWithCoder:(NSCoder *)decoder {
|
||||
self = [super initWithCoder:decoder];
|
||||
if (!self) {
|
||||
return nil;
|
||||
@@ -426,7 +406,7 @@ static id AFJSONObjectByRemovingKeysWithNullValues(id JSONObject, NSJSONReadingO
|
||||
|
||||
#pragma mark - NSCopying
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone {
|
||||
- (instancetype)copyWithZone:(NSZone *)zone {
|
||||
AFXMLDocumentResponseSerializer *serializer = [[[self class] allocWithZone:zone] init];
|
||||
serializer.options = self.options;
|
||||
|
||||
@@ -494,13 +474,13 @@ static id AFJSONObjectByRemovingKeysWithNullValues(id JSONObject, NSJSONReadingO
|
||||
|
||||
#pragma mark - NSSecureCoding
|
||||
|
||||
- (id)initWithCoder:(NSCoder *)decoder {
|
||||
- (instancetype)initWithCoder:(NSCoder *)decoder {
|
||||
self = [super initWithCoder:decoder];
|
||||
if (!self) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
self.format = [[decoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(format))] unsignedIntegerValue];
|
||||
self.format = (NSPropertyListFormat)[[decoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(format))] unsignedIntegerValue];
|
||||
self.readOptions = [[decoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(readOptions))] unsignedIntegerValue];
|
||||
|
||||
return self;
|
||||
@@ -515,7 +495,7 @@ static id AFJSONObjectByRemovingKeysWithNullValues(id JSONObject, NSJSONReadingO
|
||||
|
||||
#pragma mark - NSCopying
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone {
|
||||
- (instancetype)copyWithZone:(NSZone *)zone {
|
||||
AFPropertyListResponseSerializer *serializer = [[[self class] allocWithZone:zone] init];
|
||||
serializer.format = self.format;
|
||||
serializer.readOptions = self.readOptions;
|
||||
@@ -527,12 +507,39 @@ static id AFJSONObjectByRemovingKeysWithNullValues(id JSONObject, NSJSONReadingO
|
||||
|
||||
#pragma mark -
|
||||
|
||||
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
|
||||
#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH
|
||||
#import <CoreGraphics/CoreGraphics.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface UIImage (AFNetworkingSafeImageLoading)
|
||||
+ (UIImage *)af_safeImageWithData:(NSData *)data;
|
||||
@end
|
||||
|
||||
static NSLock* imageLock = nil;
|
||||
|
||||
@implementation UIImage (AFNetworkingSafeImageLoading)
|
||||
|
||||
+ (UIImage *)af_safeImageWithData:(NSData *)data {
|
||||
UIImage* image = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
imageLock = [[NSLock alloc] init];
|
||||
});
|
||||
|
||||
[imageLock lock];
|
||||
image = [UIImage imageWithData:data];
|
||||
[imageLock unlock];
|
||||
return image;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
static UIImage * AFImageWithDataAtScale(NSData *data, CGFloat scale) {
|
||||
UIImage *image = [[UIImage alloc] initWithData:data];
|
||||
|
||||
UIImage *image = [UIImage af_safeImageWithData:data];
|
||||
if (image.images) {
|
||||
return image;
|
||||
}
|
||||
|
||||
return [[UIImage alloc] initWithCGImage:[image CGImage] scale:scale orientation:image.imageOrientation];
|
||||
}
|
||||
|
||||
@@ -549,10 +556,11 @@ static UIImage * AFInflatedImageFromResponseWithDataAtScale(NSHTTPURLResponse *r
|
||||
} else if ([response.MIMEType isEqualToString:@"image/jpeg"]) {
|
||||
imageRef = CGImageCreateWithJPEGDataProvider(dataProvider, NULL, true, kCGRenderingIntentDefault);
|
||||
|
||||
// CGImageCreateWithJPEGDataProvider does not properly handle CMKY, so if so, fall back to AFImageWithDataAtScale
|
||||
if (imageRef) {
|
||||
CGColorSpaceRef imageColorSpace = CGImageGetColorSpace(imageRef);
|
||||
CGColorSpaceModel imageColorSpaceModel = CGColorSpaceGetModel(imageColorSpace);
|
||||
|
||||
// CGImageCreateWithJPEGDataProvider does not properly handle CMKY, so fall back to AFImageWithDataAtScale
|
||||
if (imageColorSpaceModel == kCGColorSpaceModelCMYK) {
|
||||
CGImageRelease(imageRef);
|
||||
imageRef = NULL;
|
||||
@@ -584,7 +592,8 @@ static UIImage * AFInflatedImageFromResponseWithDataAtScale(NSHTTPURLResponse *r
|
||||
return image;
|
||||
}
|
||||
|
||||
size_t bytesPerRow = 0; // CGImageGetBytesPerRow() calculates incorrectly in iOS 5.0, so defer to CGBitmapContextCreate
|
||||
// CGImageGetBytesPerRow() calculates incorrectly in iOS 5.0, so defer to CGBitmapContextCreate
|
||||
size_t bytesPerRow = 0;
|
||||
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
|
||||
CGColorSpaceModel colorSpaceModel = CGColorSpaceGetModel(colorSpace);
|
||||
CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef);
|
||||
@@ -622,7 +631,7 @@ static UIImage * AFInflatedImageFromResponseWithDataAtScale(NSHTTPURLResponse *r
|
||||
|
||||
CGImageRelease(inflatedImageRef);
|
||||
CGImageRelease(imageRef);
|
||||
|
||||
|
||||
return inflatedImage;
|
||||
}
|
||||
#endif
|
||||
@@ -638,9 +647,12 @@ static UIImage * AFInflatedImageFromResponseWithDataAtScale(NSHTTPURLResponse *r
|
||||
|
||||
self.acceptableContentTypes = [[NSSet alloc] initWithObjects:@"image/tiff", @"image/jpeg", @"image/gif", @"image/png", @"image/ico", @"image/x-icon", @"image/bmp", @"image/x-bmp", @"image/x-xbitmap", @"image/x-win-bitmap", nil];
|
||||
|
||||
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
|
||||
#if TARGET_OS_IOS || TARGET_OS_TV
|
||||
self.imageScale = [[UIScreen mainScreen] scale];
|
||||
self.automaticallyInflatesResponseImage = YES;
|
||||
#elif TARGET_OS_WATCH
|
||||
self.imageScale = [[WKInterfaceDevice currentDevice] screenScale];
|
||||
self.automaticallyInflatesResponseImage = YES;
|
||||
#endif
|
||||
|
||||
return self;
|
||||
@@ -658,13 +670,13 @@ static UIImage * AFInflatedImageFromResponseWithDataAtScale(NSHTTPURLResponse *r
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
|
||||
#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH
|
||||
if (self.automaticallyInflatesResponseImage) {
|
||||
return AFInflatedImageFromResponseWithDataAtScale((NSHTTPURLResponse *)response, data, self.imageScale);
|
||||
} else {
|
||||
return AFImageWithDataAtScale(data, self.imageScale);
|
||||
}
|
||||
#elif defined(__MAC_OS_X_VERSION_MIN_REQUIRED)
|
||||
#else
|
||||
// Ensure that the image is set to it's correct pixel width and height
|
||||
NSBitmapImageRep *bitimage = [[NSBitmapImageRep alloc] initWithData:data];
|
||||
NSImage *image = [[NSImage alloc] initWithSize:NSMakeSize([bitimage pixelsWide], [bitimage pixelsHigh])];
|
||||
@@ -678,13 +690,13 @@ static UIImage * AFInflatedImageFromResponseWithDataAtScale(NSHTTPURLResponse *r
|
||||
|
||||
#pragma mark - NSSecureCoding
|
||||
|
||||
- (id)initWithCoder:(NSCoder *)decoder {
|
||||
- (instancetype)initWithCoder:(NSCoder *)decoder {
|
||||
self = [super initWithCoder:decoder];
|
||||
if (!self) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
|
||||
#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH
|
||||
NSNumber *imageScale = [decoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(imageScale))];
|
||||
#if CGFLOAT_IS_DOUBLE
|
||||
self.imageScale = [imageScale doubleValue];
|
||||
@@ -701,7 +713,7 @@ static UIImage * AFInflatedImageFromResponseWithDataAtScale(NSHTTPURLResponse *r
|
||||
- (void)encodeWithCoder:(NSCoder *)coder {
|
||||
[super encodeWithCoder:coder];
|
||||
|
||||
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
|
||||
#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH
|
||||
[coder encodeObject:@(self.imageScale) forKey:NSStringFromSelector(@selector(imageScale))];
|
||||
[coder encodeBool:self.automaticallyInflatesResponseImage forKey:NSStringFromSelector(@selector(automaticallyInflatesResponseImage))];
|
||||
#endif
|
||||
@@ -709,10 +721,10 @@ static UIImage * AFInflatedImageFromResponseWithDataAtScale(NSHTTPURLResponse *r
|
||||
|
||||
#pragma mark - NSCopying
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone {
|
||||
- (instancetype)copyWithZone:(NSZone *)zone {
|
||||
AFImageResponseSerializer *serializer = [[[self class] allocWithZone:zone] init];
|
||||
|
||||
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
|
||||
#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH
|
||||
serializer.imageScale = self.imageScale;
|
||||
serializer.automaticallyInflatesResponseImage = self.automaticallyInflatesResponseImage;
|
||||
#endif
|
||||
@@ -758,13 +770,13 @@ static UIImage * AFInflatedImageFromResponseWithDataAtScale(NSHTTPURLResponse *r
|
||||
return responseObject;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return [super responseObjectForResponse:response data:data error:error];
|
||||
}
|
||||
|
||||
#pragma mark - NSSecureCoding
|
||||
|
||||
- (id)initWithCoder:(NSCoder *)decoder {
|
||||
- (instancetype)initWithCoder:(NSCoder *)decoder {
|
||||
self = [super initWithCoder:decoder];
|
||||
if (!self) {
|
||||
return nil;
|
||||
@@ -783,7 +795,7 @@ static UIImage * AFInflatedImageFromResponseWithDataAtScale(NSHTTPURLResponse *r
|
||||
|
||||
#pragma mark - NSCopying
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone {
|
||||
- (instancetype)copyWithZone:(NSZone *)zone {
|
||||
AFCompoundResponseSerializer *serializer = [[[self class] allocWithZone:zone] init];
|
||||
serializer.responseSerializers = self.responseSerializers;
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// AFURLSessionManager.h
|
||||
//
|
||||
// Copyright (c) 2013-2014 AFNetworking (http://afnetworking.com)
|
||||
// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ )
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -8,10 +7,10 @@
|
||||
// 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
|
||||
@@ -20,39 +19,43 @@
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "AFURLResponseSerialization.h"
|
||||
#import "AFURLRequestSerialization.h"
|
||||
#import "AFSecurityPolicy.h"
|
||||
#if !TARGET_OS_WATCH
|
||||
#import "AFNetworkReachabilityManager.h"
|
||||
#endif
|
||||
|
||||
/**
|
||||
`AFURLSessionManager` creates and manages an `NSURLSession` object based on a specified `NSURLSessionConfiguration` object, which conforms to `<NSURLSessionTaskDelegate>`, `<NSURLSessionDataDelegate>`, `<NSURLSessionDownloadDelegate>`, and `<NSURLSessionDelegate>`.
|
||||
|
||||
|
||||
## Subclassing Notes
|
||||
|
||||
|
||||
This is the base class for `AFHTTPSessionManager`, which adds functionality specific to making HTTP requests. If you are looking to extend `AFURLSessionManager` specifically for HTTP, consider subclassing `AFHTTPSessionManager` instead.
|
||||
|
||||
|
||||
## NSURLSession & NSURLSessionTask Delegate Methods
|
||||
|
||||
|
||||
`AFURLSessionManager` implements the following delegate methods:
|
||||
|
||||
|
||||
### `NSURLSessionDelegate`
|
||||
|
||||
|
||||
- `URLSession:didBecomeInvalidWithError:`
|
||||
- `URLSession:didReceiveChallenge:completionHandler:`
|
||||
- `URLSessionDidFinishEventsForBackgroundURLSession:`
|
||||
|
||||
### `NSURLSessionTaskDelegate`
|
||||
|
||||
|
||||
- `URLSession:willPerformHTTPRedirection:newRequest:completionHandler:`
|
||||
- `URLSession:task:didReceiveChallenge:completionHandler:`
|
||||
- `URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:`
|
||||
- `URLSession:task:needNewBodyStream:`
|
||||
- `URLSession:task:didCompleteWithError:`
|
||||
|
||||
### `NSURLSessionDataDelegate`
|
||||
|
||||
|
||||
- `URLSession:dataTask:didReceiveResponse:completionHandler:`
|
||||
- `URLSession:dataTask:didBecomeDownloadTask:`
|
||||
- `URLSession:dataTask:didReceiveData:`
|
||||
@@ -63,24 +66,26 @@
|
||||
- `URLSession:downloadTask:didFinishDownloadingToURL:`
|
||||
- `URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesWritten:totalBytesExpectedToWrite:`
|
||||
- `URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes:`
|
||||
|
||||
|
||||
If any of these methods are overridden in a subclass, they _must_ call the `super` implementation first.
|
||||
|
||||
|
||||
## Network Reachability Monitoring
|
||||
|
||||
Network reachability status and change monitoring is available through the `reachabilityManager` property. Applications may choose to monitor network reachability conditions in order to prevent or suspend any outbound requests. See `AFNetworkReachabilityManager` for more details.
|
||||
|
||||
|
||||
## NSCoding Caveats
|
||||
|
||||
|
||||
- Encoded managers do not include any block properties. Be sure to set delegate callback blocks when using `-initWithCoder:` or `NSKeyedUnarchiver`.
|
||||
|
||||
## NSCopying Caveats
|
||||
|
||||
- `-copy` and `-copyWithZone:` return a new manager with a new `NSURLSession` created from the configuration of the original.
|
||||
- Operation copies do not include any delegate callback blocks, as they often strongly captures a reference to `self`, which would otherwise have the unintuitive side-effect of pointing to the _original_ session manager when copied.
|
||||
|
||||
@warning Managers for background sessions must be owned for the duration of their use. This can be accomplished by creating an application-wide or shared singleton instance.
|
||||
*/
|
||||
|
||||
#if (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000) || (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 1090)
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface AFURLSessionManager : NSObject <NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate, NSSecureCoding, NSCopying>
|
||||
|
||||
@@ -106,10 +111,11 @@
|
||||
///-------------------------------
|
||||
|
||||
/**
|
||||
The security policy used by created request operations to evaluate server trust for secure connections. `AFURLSessionManager` uses the `defaultPolicy` unless otherwise specified.
|
||||
The security policy used by created session to evaluate server trust for secure connections. `AFURLSessionManager` uses the `defaultPolicy` unless otherwise specified.
|
||||
*/
|
||||
@property (nonatomic, strong) AFSecurityPolicy *securityPolicy;
|
||||
|
||||
#if !TARGET_OS_WATCH
|
||||
///--------------------------------------
|
||||
/// @name Monitoring Network Reachability
|
||||
///--------------------------------------
|
||||
@@ -118,6 +124,7 @@
|
||||
The network reachability manager. `AFURLSessionManager` uses the `sharedManager` by default.
|
||||
*/
|
||||
@property (readwrite, nonatomic, strong) AFNetworkReachabilityManager *reachabilityManager;
|
||||
#endif
|
||||
|
||||
///----------------------------
|
||||
/// @name Getting Session Tasks
|
||||
@@ -126,22 +133,22 @@
|
||||
/**
|
||||
The data, upload, and download tasks currently run by the managed session.
|
||||
*/
|
||||
@property (readonly, nonatomic, strong) NSArray *tasks;
|
||||
@property (readonly, nonatomic, strong) NSArray <NSURLSessionTask *> *tasks;
|
||||
|
||||
/**
|
||||
The data tasks currently run by the managed session.
|
||||
*/
|
||||
@property (readonly, nonatomic, strong) NSArray *dataTasks;
|
||||
@property (readonly, nonatomic, strong) NSArray <NSURLSessionDataTask *> *dataTasks;
|
||||
|
||||
/**
|
||||
The upload tasks currently run by the managed session.
|
||||
*/
|
||||
@property (readonly, nonatomic, strong) NSArray *uploadTasks;
|
||||
@property (readonly, nonatomic, strong) NSArray <NSURLSessionUploadTask *> *uploadTasks;
|
||||
|
||||
/**
|
||||
The download tasks currently run by the managed session.
|
||||
*/
|
||||
@property (readonly, nonatomic, strong) NSArray *downloadTasks;
|
||||
@property (readonly, nonatomic, strong) NSArray <NSURLSessionDownloadTask *> *downloadTasks;
|
||||
|
||||
///-------------------------------
|
||||
/// @name Managing Callback Queues
|
||||
@@ -150,12 +157,12 @@
|
||||
/**
|
||||
The dispatch queue for `completionBlock`. If `NULL` (default), the main queue is used.
|
||||
*/
|
||||
@property (nonatomic, strong) dispatch_queue_t completionQueue;
|
||||
@property (nonatomic, strong, nullable) dispatch_queue_t completionQueue;
|
||||
|
||||
/**
|
||||
The dispatch group for `completionBlock`. If `NULL` (default), a private dispatch group is used.
|
||||
*/
|
||||
@property (nonatomic, strong) dispatch_group_t completionGroup;
|
||||
@property (nonatomic, strong, nullable) dispatch_group_t completionGroup;
|
||||
|
||||
///---------------------------------
|
||||
/// @name Working Around System Bugs
|
||||
@@ -176,16 +183,16 @@
|
||||
|
||||
/**
|
||||
Creates and returns a manager for a session created with the specified configuration. This is the designated initializer.
|
||||
|
||||
|
||||
@param configuration The configuration used to create the managed session.
|
||||
|
||||
|
||||
@return A manager for a newly-created session.
|
||||
*/
|
||||
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration;
|
||||
- (instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)configuration NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
/**
|
||||
Invalidates the managed session, optionally canceling pending tasks.
|
||||
|
||||
|
||||
@param cancelPendingTasks Whether or not to cancel pending tasks.
|
||||
*/
|
||||
- (void)invalidateSessionCancelingTasks:(BOOL)cancelPendingTasks;
|
||||
@@ -201,7 +208,20 @@
|
||||
@param completionHandler A block object to be executed when the task finishes. This block has no return value and takes three arguments: the server response, the response object created by that serializer, and the error that occurred, if any.
|
||||
*/
|
||||
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
|
||||
completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler;
|
||||
completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler;
|
||||
|
||||
/**
|
||||
Creates an `NSURLSessionDataTask` with the specified request.
|
||||
|
||||
@param request The HTTP request for the request.
|
||||
@param uploadProgressBlock 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 downloadProgressBlock 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 completionHandler A block object to be executed when the task finishes. This block has no return value and takes three arguments: the server response, the response object created by that serializer, and the error that occurred, if any.
|
||||
*/
|
||||
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
|
||||
uploadProgress:(nullable void (^)(NSProgress *uploadProgress))uploadProgressBlock
|
||||
downloadProgress:(nullable void (^)(NSProgress *downloadProgress))downloadProgressBlock
|
||||
completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler;
|
||||
|
||||
///---------------------------
|
||||
/// @name Running Upload Tasks
|
||||
@@ -212,39 +232,39 @@
|
||||
|
||||
@param request The HTTP request for the request.
|
||||
@param fileURL A URL to the local file to be uploaded.
|
||||
@param progress A progress object monitoring the current upload progress.
|
||||
@param uploadProgressBlock 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 completionHandler A block object to be executed when the task finishes. This block has no return value and takes three arguments: the server response, the response object created by that serializer, and the error that occurred, if any.
|
||||
|
||||
|
||||
@see `attemptsToRecreateUploadTasksForBackgroundSessions`
|
||||
*/
|
||||
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request
|
||||
fromFile:(NSURL *)fileURL
|
||||
progress:(NSProgress * __autoreleasing *)progress
|
||||
completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler;
|
||||
progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgressBlock
|
||||
completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler;
|
||||
|
||||
/**
|
||||
Creates an `NSURLSessionUploadTask` with the specified request for an HTTP body.
|
||||
|
||||
@param request The HTTP request for the request.
|
||||
@param bodyData A data object containing the HTTP body to be uploaded.
|
||||
@param progress A progress object monitoring the current upload progress.
|
||||
@param uploadProgressBlock 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 completionHandler A block object to be executed when the task finishes. This block has no return value and takes three arguments: the server response, the response object created by that serializer, and the error that occurred, if any.
|
||||
*/
|
||||
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request
|
||||
fromData:(NSData *)bodyData
|
||||
progress:(NSProgress * __autoreleasing *)progress
|
||||
completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler;
|
||||
fromData:(nullable NSData *)bodyData
|
||||
progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgressBlock
|
||||
completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler;
|
||||
|
||||
/**
|
||||
Creates an `NSURLSessionUploadTask` with the specified streaming request.
|
||||
|
||||
@param request The HTTP request for the request.
|
||||
@param progress A progress object monitoring the current upload progress.
|
||||
@param uploadProgressBlock 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 completionHandler A block object to be executed when the task finishes. This block has no return value and takes three arguments: the server response, the response object created by that serializer, and the error that occurred, if any.
|
||||
*/
|
||||
- (NSURLSessionUploadTask *)uploadTaskWithStreamedRequest:(NSURLRequest *)request
|
||||
progress:(NSProgress * __autoreleasing *)progress
|
||||
completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler;
|
||||
progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgressBlock
|
||||
completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler;
|
||||
|
||||
///-----------------------------
|
||||
/// @name Running Download Tasks
|
||||
@@ -254,29 +274,29 @@
|
||||
Creates an `NSURLSessionDownloadTask` with the specified request.
|
||||
|
||||
@param request The HTTP request for the request.
|
||||
@param progress A progress object monitoring the current download progress.
|
||||
@param downloadProgressBlock 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 destination A block object to be executed in order to determine the destination of the downloaded file. This block takes two arguments, the target path & the server response, and returns the desired file URL of the resulting download. The temporary file used during the download will be automatically deleted after being moved to the returned URL.
|
||||
@param completionHandler A block to be executed when a task finishes. This block has no return value and takes three arguments: the server response, the path of the downloaded file, and the error describing the network or parsing error that occurred, if any.
|
||||
|
||||
|
||||
@warning If using a background `NSURLSessionConfiguration` on iOS, these blocks will be lost when the app is terminated. Background sessions may prefer to use `-setDownloadTaskDidFinishDownloadingBlock:` to specify the URL for saving the downloaded file, rather than the destination block of this method.
|
||||
*/
|
||||
- (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request
|
||||
progress:(NSProgress * __autoreleasing *)progress
|
||||
destination:(NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
|
||||
completionHandler:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler;
|
||||
progress:(nullable void (^)(NSProgress *downloadProgress))downloadProgressBlock
|
||||
destination:(nullable NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
|
||||
completionHandler:(nullable void (^)(NSURLResponse *response, NSURL * _Nullable filePath, NSError * _Nullable error))completionHandler;
|
||||
|
||||
/**
|
||||
Creates an `NSURLSessionDownloadTask` with the specified resume data.
|
||||
|
||||
@param resumeData The data used to resume downloading.
|
||||
@param progress A progress object monitoring the current download progress.
|
||||
@param downloadProgressBlock 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 destination A block object to be executed in order to determine the destination of the downloaded file. This block takes two arguments, the target path & the server response, and returns the desired file URL of the resulting download. The temporary file used during the download will be automatically deleted after being moved to the returned URL.
|
||||
@param completionHandler A block to be executed when a task finishes. This block has no return value and takes three arguments: the server response, the path of the downloaded file, and the error describing the network or parsing error that occurred, if any.
|
||||
*/
|
||||
- (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData
|
||||
progress:(NSProgress * __autoreleasing *)progress
|
||||
destination:(NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
|
||||
completionHandler:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler;
|
||||
progress:(nullable void (^)(NSProgress *downloadProgress))downloadProgressBlock
|
||||
destination:(nullable NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
|
||||
completionHandler:(nullable void (^)(NSURLResponse *response, NSURL * _Nullable filePath, NSError * _Nullable error))completionHandler;
|
||||
|
||||
///---------------------------------
|
||||
/// @name Getting Progress for Tasks
|
||||
@@ -285,20 +305,20 @@
|
||||
/**
|
||||
Returns the upload progress of the specified task.
|
||||
|
||||
@param uploadTask The session upload task. Must not be `nil`.
|
||||
@param task The session task. Must not be `nil`.
|
||||
|
||||
@return An `NSProgress` object reporting the upload progress of a task, or `nil` if the progress is unavailable.
|
||||
*/
|
||||
- (NSProgress *)uploadProgressForTask:(NSURLSessionUploadTask *)uploadTask;
|
||||
- (nullable NSProgress *)uploadProgressForTask:(NSURLSessionTask *)task;
|
||||
|
||||
/**
|
||||
Returns the download progress of the specified task.
|
||||
|
||||
@param downloadTask The session download task. Must not be `nil`.
|
||||
|
||||
|
||||
@param task The session task. Must not be `nil`.
|
||||
|
||||
@return An `NSProgress` object reporting the download progress of a task, or `nil` if the progress is unavailable.
|
||||
*/
|
||||
- (NSProgress *)downloadProgressForTask:(NSURLSessionDownloadTask *)downloadTask;
|
||||
- (nullable NSProgress *)downloadProgressForTask:(NSURLSessionTask *)task;
|
||||
|
||||
///-----------------------------------------
|
||||
/// @name Setting Session Delegate Callbacks
|
||||
@@ -306,17 +326,17 @@
|
||||
|
||||
/**
|
||||
Sets a block to be executed when the managed session becomes invalid, as handled by the `NSURLSessionDelegate` method `URLSession:didBecomeInvalidWithError:`.
|
||||
|
||||
|
||||
@param block A block object to be executed when the managed session becomes invalid. The block has no return value, and takes two arguments: the session, and the error related to the cause of invalidation.
|
||||
*/
|
||||
- (void)setSessionDidBecomeInvalidBlock:(void (^)(NSURLSession *session, NSError *error))block;
|
||||
- (void)setSessionDidBecomeInvalidBlock:(nullable void (^)(NSURLSession *session, NSError *error))block;
|
||||
|
||||
/**
|
||||
Sets a block to be executed when a connection level authentication challenge has occurred, as handled by the `NSURLSessionDelegate` method `URLSession:didReceiveChallenge:completionHandler:`.
|
||||
|
||||
@param block A block object to be executed when a connection level authentication challenge has occurred. The block returns the disposition of the authentication challenge, and takes three arguments: the session, the authentication challenge, and a pointer to the credential that should be used to resolve the challenge.
|
||||
*/
|
||||
- (void)setSessionDidReceiveAuthenticationChallengeBlock:(NSURLSessionAuthChallengeDisposition (^)(NSURLSession *session, NSURLAuthenticationChallenge *challenge, NSURLCredential * __autoreleasing *credential))block;
|
||||
- (void)setSessionDidReceiveAuthenticationChallengeBlock:(nullable NSURLSessionAuthChallengeDisposition (^)(NSURLSession *session, NSURLAuthenticationChallenge *challenge, NSURLCredential * _Nullable __autoreleasing * _Nullable credential))block;
|
||||
|
||||
///--------------------------------------
|
||||
/// @name Setting Task Delegate Callbacks
|
||||
@@ -327,35 +347,35 @@
|
||||
|
||||
@param block A block object to be executed when a task requires a new request body stream.
|
||||
*/
|
||||
- (void)setTaskNeedNewBodyStreamBlock:(NSInputStream * (^)(NSURLSession *session, NSURLSessionTask *task))block;
|
||||
- (void)setTaskNeedNewBodyStreamBlock:(nullable NSInputStream * (^)(NSURLSession *session, NSURLSessionTask *task))block;
|
||||
|
||||
/**
|
||||
Sets a block to be executed when an HTTP request is attempting to perform a redirection to a different URL, as handled by the `NSURLSessionTaskDelegate` method `URLSession:willPerformHTTPRedirection:newRequest:completionHandler:`.
|
||||
|
||||
|
||||
@param block A block object to be executed when an HTTP request is attempting to perform a redirection to a different URL. The block returns the request to be made for the redirection, and takes four arguments: the session, the task, the redirection response, and the request corresponding to the redirection response.
|
||||
*/
|
||||
- (void)setTaskWillPerformHTTPRedirectionBlock:(NSURLRequest * (^)(NSURLSession *session, NSURLSessionTask *task, NSURLResponse *response, NSURLRequest *request))block;
|
||||
- (void)setTaskWillPerformHTTPRedirectionBlock:(nullable NSURLRequest * (^)(NSURLSession *session, NSURLSessionTask *task, NSURLResponse *response, NSURLRequest *request))block;
|
||||
|
||||
/**
|
||||
Sets a block to be executed when a session task has received a request specific authentication challenge, as handled by the `NSURLSessionTaskDelegate` method `URLSession:task:didReceiveChallenge:completionHandler:`.
|
||||
|
||||
|
||||
@param block A block object to be executed when a session task has received a request specific authentication challenge. The block returns the disposition of the authentication challenge, and takes four arguments: the session, the task, the authentication challenge, and a pointer to the credential that should be used to resolve the challenge.
|
||||
*/
|
||||
- (void)setTaskDidReceiveAuthenticationChallengeBlock:(NSURLSessionAuthChallengeDisposition (^)(NSURLSession *session, NSURLSessionTask *task, NSURLAuthenticationChallenge *challenge, NSURLCredential * __autoreleasing *credential))block;
|
||||
- (void)setTaskDidReceiveAuthenticationChallengeBlock:(nullable NSURLSessionAuthChallengeDisposition (^)(NSURLSession *session, NSURLSessionTask *task, NSURLAuthenticationChallenge *challenge, NSURLCredential * _Nullable __autoreleasing * _Nullable credential))block;
|
||||
|
||||
/**
|
||||
Sets a block to be executed periodically to track upload progress, as handled by the `NSURLSessionTaskDelegate` method `URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:`.
|
||||
|
||||
|
||||
@param block A block object to be called when an undetermined number of bytes have been uploaded to the server. This block has no return value and takes five arguments: the session, the task, the number of bytes written since the last time the upload progress block was called, the total bytes written, and the total bytes expected to be written during the request, as initially determined by the length of the HTTP body. This block may be called multiple times, and will execute on the main thread.
|
||||
*/
|
||||
- (void)setTaskDidSendBodyDataBlock:(void (^)(NSURLSession *session, NSURLSessionTask *task, int64_t bytesSent, int64_t totalBytesSent, int64_t totalBytesExpectedToSend))block;
|
||||
- (void)setTaskDidSendBodyDataBlock:(nullable void (^)(NSURLSession *session, NSURLSessionTask *task, int64_t bytesSent, int64_t totalBytesSent, int64_t totalBytesExpectedToSend))block;
|
||||
|
||||
/**
|
||||
Sets a block to be executed as the last message related to a specific task, as handled by the `NSURLSessionTaskDelegate` method `URLSession:task:didCompleteWithError:`.
|
||||
|
||||
|
||||
@param block A block object to be executed when a session task is completed. The block has no return value, and takes three arguments: the session, the task, and any error that occurred in the process of executing the task.
|
||||
*/
|
||||
- (void)setTaskDidCompleteBlock:(void (^)(NSURLSession *session, NSURLSessionTask *task, NSError *error))block;
|
||||
- (void)setTaskDidCompleteBlock:(nullable void (^)(NSURLSession *session, NSURLSessionTask *task, NSError * _Nullable error))block;
|
||||
|
||||
///-------------------------------------------
|
||||
/// @name Setting Data Task Delegate Callbacks
|
||||
@@ -366,35 +386,35 @@
|
||||
|
||||
@param block A block object to be executed when a data task has received a response. The block returns the disposition of the session response, and takes three arguments: the session, the data task, and the received response.
|
||||
*/
|
||||
- (void)setDataTaskDidReceiveResponseBlock:(NSURLSessionResponseDisposition (^)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLResponse *response))block;
|
||||
- (void)setDataTaskDidReceiveResponseBlock:(nullable NSURLSessionResponseDisposition (^)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLResponse *response))block;
|
||||
|
||||
/**
|
||||
Sets a block to be executed when a data task has become a download task, as handled by the `NSURLSessionDataDelegate` method `URLSession:dataTask:didBecomeDownloadTask:`.
|
||||
|
||||
|
||||
@param block A block object to be executed when a data task has become a download task. The block has no return value, and takes three arguments: the session, the data task, and the download task it has become.
|
||||
*/
|
||||
- (void)setDataTaskDidBecomeDownloadTaskBlock:(void (^)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLSessionDownloadTask *downloadTask))block;
|
||||
- (void)setDataTaskDidBecomeDownloadTaskBlock:(nullable void (^)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLSessionDownloadTask *downloadTask))block;
|
||||
|
||||
/**
|
||||
Sets a block to be executed when a data task receives data, as handled by the `NSURLSessionDataDelegate` method `URLSession:dataTask:didReceiveData:`.
|
||||
|
||||
|
||||
@param block A block object to be called when an undetermined number of bytes have been downloaded from the server. This block has no return value and takes three arguments: the session, the data task, and the data received. This block may be called multiple times, and will execute on the session manager operation queue.
|
||||
*/
|
||||
- (void)setDataTaskDidReceiveDataBlock:(void (^)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSData *data))block;
|
||||
- (void)setDataTaskDidReceiveDataBlock:(nullable void (^)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSData *data))block;
|
||||
|
||||
/**
|
||||
Sets a block to be executed to determine the caching behavior of a data task, as handled by the `NSURLSessionDataDelegate` method `URLSession:dataTask:willCacheResponse:completionHandler:`.
|
||||
|
||||
|
||||
@param block A block object to be executed to determine the caching behavior of a data task. The block returns the response to cache, and takes three arguments: the session, the data task, and the proposed cached URL response.
|
||||
*/
|
||||
- (void)setDataTaskWillCacheResponseBlock:(NSCachedURLResponse * (^)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSCachedURLResponse *proposedResponse))block;
|
||||
- (void)setDataTaskWillCacheResponseBlock:(nullable NSCachedURLResponse * (^)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSCachedURLResponse *proposedResponse))block;
|
||||
|
||||
/**
|
||||
Sets a block to be executed once all messages enqueued for a session have been delivered, as handled by the `NSURLSessionDataDelegate` method `URLSessionDidFinishEventsForBackgroundURLSession:`.
|
||||
|
||||
|
||||
@param block A block object to be executed once all messages enqueued for a session have been delivered. The block has no return value and takes a single argument: the session.
|
||||
*/
|
||||
- (void)setDidFinishEventsForBackgroundURLSessionBlock:(void (^)(NSURLSession *session))block;
|
||||
- (void)setDidFinishEventsForBackgroundURLSessionBlock:(nullable void (^)(NSURLSession *session))block;
|
||||
|
||||
///-----------------------------------------------
|
||||
/// @name Setting Download Task Delegate Callbacks
|
||||
@@ -402,128 +422,79 @@
|
||||
|
||||
/**
|
||||
Sets a block to be executed when a download task has completed a download, as handled by the `NSURLSessionDownloadDelegate` method `URLSession:downloadTask:didFinishDownloadingToURL:`.
|
||||
|
||||
|
||||
@param block A block object to be executed when a download task has completed. The block returns the URL the download should be moved to, and takes three arguments: the session, the download task, and the temporary location of the downloaded file. If the file manager encounters an error while attempting to move the temporary file to the destination, an `AFURLSessionDownloadTaskDidFailToMoveFileNotification` will be posted, with the download task as its object, and the user info of the error.
|
||||
*/
|
||||
- (void)setDownloadTaskDidFinishDownloadingBlock:(NSURL * (^)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, NSURL *location))block;
|
||||
- (void)setDownloadTaskDidFinishDownloadingBlock:(nullable NSURL * _Nullable (^)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, NSURL *location))block;
|
||||
|
||||
/**
|
||||
Sets a block to be executed periodically to track download progress, as handled by the `NSURLSessionDownloadDelegate` method `URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesWritten:totalBytesExpectedToWrite:`.
|
||||
|
||||
|
||||
@param block A block object to be called when an undetermined number of bytes have been downloaded from the server. This block has no return value and takes five arguments: the session, the download task, the number of bytes read since the last time the download progress block was called, the total bytes read, and the total bytes expected to be read during the request, as initially determined by the expected content size of the `NSHTTPURLResponse` object. This block may be called multiple times, and will execute on the session manager operation queue.
|
||||
*/
|
||||
- (void)setDownloadTaskDidWriteDataBlock:(void (^)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite))block;
|
||||
- (void)setDownloadTaskDidWriteDataBlock:(nullable void (^)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite))block;
|
||||
|
||||
/**
|
||||
Sets a block to be executed when a download task has been resumed, as handled by the `NSURLSessionDownloadDelegate` method `URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes:`.
|
||||
|
||||
|
||||
@param block A block object to be executed when a download task has been resumed. The block has no return value and takes four arguments: the session, the download task, the file offset of the resumed download, and the total number of bytes expected to be downloaded.
|
||||
*/
|
||||
- (void)setDownloadTaskDidResumeBlock:(void (^)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, int64_t fileOffset, int64_t expectedTotalBytes))block;
|
||||
- (void)setDownloadTaskDidResumeBlock:(nullable void (^)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, int64_t fileOffset, int64_t expectedTotalBytes))block;
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
|
||||
///--------------------
|
||||
/// @name Notifications
|
||||
///--------------------
|
||||
|
||||
/**
|
||||
Posted when a task begins executing.
|
||||
|
||||
@deprecated Use `AFNetworkingTaskDidResumeNotification` instead.
|
||||
*/
|
||||
extern NSString * const AFNetworkingTaskDidStartNotification DEPRECATED_ATTRIBUTE;
|
||||
|
||||
/**
|
||||
Posted when a task resumes.
|
||||
*/
|
||||
extern NSString * const AFNetworkingTaskDidResumeNotification;
|
||||
|
||||
/**
|
||||
Posted when a task finishes executing. Includes a userInfo dictionary with additional information about the task.
|
||||
|
||||
@deprecated Use `AFNetworkingTaskDidCompleteNotification` instead.
|
||||
*/
|
||||
extern NSString * const AFNetworkingTaskDidFinishNotification DEPRECATED_ATTRIBUTE;
|
||||
FOUNDATION_EXPORT NSString * const AFNetworkingTaskDidResumeNotification;
|
||||
|
||||
/**
|
||||
Posted when a task finishes executing. Includes a userInfo dictionary with additional information about the task.
|
||||
*/
|
||||
extern NSString * const AFNetworkingTaskDidCompleteNotification;
|
||||
FOUNDATION_EXPORT NSString * const AFNetworkingTaskDidCompleteNotification;
|
||||
|
||||
/**
|
||||
Posted when a task suspends its execution.
|
||||
*/
|
||||
extern NSString * const AFNetworkingTaskDidSuspendNotification;
|
||||
FOUNDATION_EXPORT NSString * const AFNetworkingTaskDidSuspendNotification;
|
||||
|
||||
/**
|
||||
Posted when a session is invalidated.
|
||||
*/
|
||||
extern NSString * const AFURLSessionDidInvalidateNotification;
|
||||
FOUNDATION_EXPORT NSString * const AFURLSessionDidInvalidateNotification;
|
||||
|
||||
/**
|
||||
Posted when a session download task encountered an error when moving the temporary download file to a specified destination.
|
||||
*/
|
||||
extern NSString * const AFURLSessionDownloadTaskDidFailToMoveFileNotification;
|
||||
FOUNDATION_EXPORT NSString * const AFURLSessionDownloadTaskDidFailToMoveFileNotification;
|
||||
|
||||
/**
|
||||
The raw response data of the task. Included in the userInfo dictionary of the `AFNetworkingTaskDidFinishNotification` if response data exists for the task.
|
||||
|
||||
@deprecated Use `AFNetworkingTaskDidCompleteResponseDataKey` instead.
|
||||
The raw response data of the task. Included in the userInfo dictionary of the `AFNetworkingTaskDidCompleteNotification` if response data exists for the task.
|
||||
*/
|
||||
extern NSString * const AFNetworkingTaskDidFinishResponseDataKey DEPRECATED_ATTRIBUTE;
|
||||
FOUNDATION_EXPORT NSString * const AFNetworkingTaskDidCompleteResponseDataKey;
|
||||
|
||||
/**
|
||||
The raw response data of the task. Included in the userInfo dictionary of the `AFNetworkingTaskDidFinishNotification` if response data exists for the task.
|
||||
The serialized response object of the task. Included in the userInfo dictionary of the `AFNetworkingTaskDidCompleteNotification` if the response was serialized.
|
||||
*/
|
||||
extern NSString * const AFNetworkingTaskDidCompleteResponseDataKey;
|
||||
FOUNDATION_EXPORT NSString * const AFNetworkingTaskDidCompleteSerializedResponseKey;
|
||||
|
||||
/**
|
||||
The serialized response object of the task. Included in the userInfo dictionary of the `AFNetworkingTaskDidFinishNotification` if the response was serialized.
|
||||
|
||||
@deprecated Use `AFNetworkingTaskDidCompleteSerializedResponseKey` instead.
|
||||
The response serializer used to serialize the response. Included in the userInfo dictionary of the `AFNetworkingTaskDidCompleteNotification` if the task has an associated response serializer.
|
||||
*/
|
||||
extern NSString * const AFNetworkingTaskDidFinishSerializedResponseKey DEPRECATED_ATTRIBUTE;
|
||||
FOUNDATION_EXPORT NSString * const AFNetworkingTaskDidCompleteResponseSerializerKey;
|
||||
|
||||
/**
|
||||
The serialized response object of the task. Included in the userInfo dictionary of the `AFNetworkingTaskDidFinishNotification` if the response was serialized.
|
||||
The file path associated with the download task. Included in the userInfo dictionary of the `AFNetworkingTaskDidCompleteNotification` if an the response data has been stored directly to disk.
|
||||
*/
|
||||
extern NSString * const AFNetworkingTaskDidCompleteSerializedResponseKey;
|
||||
FOUNDATION_EXPORT NSString * const AFNetworkingTaskDidCompleteAssetPathKey;
|
||||
|
||||
/**
|
||||
The response serializer used to serialize the response. Included in the userInfo dictionary of the `AFNetworkingTaskDidFinishNotification` if the task has an associated response serializer.
|
||||
|
||||
@deprecated Use `AFNetworkingTaskDidCompleteResponseSerializerKey` instead.
|
||||
Any error associated with the task, or the serialization of the response. Included in the userInfo dictionary of the `AFNetworkingTaskDidCompleteNotification` if an error exists.
|
||||
*/
|
||||
extern NSString * const AFNetworkingTaskDidFinishResponseSerializerKey DEPRECATED_ATTRIBUTE;
|
||||
FOUNDATION_EXPORT NSString * const AFNetworkingTaskDidCompleteErrorKey;
|
||||
|
||||
/**
|
||||
The response serializer used to serialize the response. Included in the userInfo dictionary of the `AFNetworkingTaskDidFinishNotification` if the task has an associated response serializer.
|
||||
*/
|
||||
extern NSString * const AFNetworkingTaskDidCompleteResponseSerializerKey;
|
||||
|
||||
/**
|
||||
The file path associated with the download task. Included in the userInfo dictionary of the `AFNetworkingTaskDidFinishNotification` if an the response data has been stored directly to disk.
|
||||
|
||||
@deprecated Use `AFNetworkingTaskDidCompleteAssetPathKey` instead.
|
||||
*/
|
||||
extern NSString * const AFNetworkingTaskDidFinishAssetPathKey DEPRECATED_ATTRIBUTE;
|
||||
|
||||
/**
|
||||
The file path associated with the download task. Included in the userInfo dictionary of the `AFNetworkingTaskDidFinishNotification` if an the response data has been stored directly to disk.
|
||||
*/
|
||||
extern NSString * const AFNetworkingTaskDidCompleteAssetPathKey;
|
||||
|
||||
/**
|
||||
Any error associated with the task, or the serialization of the response. Included in the userInfo dictionary of the `AFNetworkingTaskDidFinishNotification` if an error exists.
|
||||
|
||||
@deprecated Use `AFNetworkingTaskDidCompleteErrorKey` instead.
|
||||
*/
|
||||
extern NSString * const AFNetworkingTaskDidFinishErrorKey DEPRECATED_ATTRIBUTE;
|
||||
|
||||
/**
|
||||
Any error associated with the task, or the serialization of the response. Included in the userInfo dictionary of the `AFNetworkingTaskDidFinishNotification` if an error exists.
|
||||
*/
|
||||
extern NSString * const AFNetworkingTaskDidCompleteErrorKey;
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// AFURLSessionManager.m
|
||||
//
|
||||
// Copyright (c) 2013-2014 AFNetworking (http://afnetworking.com)
|
||||
// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ )
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -8,10 +7,10 @@
|
||||
// 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
|
||||
@@ -21,8 +20,13 @@
|
||||
// THE SOFTWARE.
|
||||
|
||||
#import "AFURLSessionManager.h"
|
||||
#import <objc/runtime.h>
|
||||
|
||||
#if (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000) || (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 1090)
|
||||
#ifndef NSFoundationVersionNumber_iOS_8_0
|
||||
#define NSFoundationVersionNumber_With_Fixed_5871104061079552_bug 1140.11
|
||||
#else
|
||||
#define NSFoundationVersionNumber_With_Fixed_5871104061079552_bug NSFoundationVersionNumber_iOS_8_0
|
||||
#endif
|
||||
|
||||
static dispatch_queue_t url_session_manager_creation_queue() {
|
||||
static dispatch_queue_t af_url_session_manager_creation_queue;
|
||||
@@ -34,6 +38,17 @@ static dispatch_queue_t url_session_manager_creation_queue() {
|
||||
return af_url_session_manager_creation_queue;
|
||||
}
|
||||
|
||||
static void url_session_manager_create_task_safely(dispatch_block_t block) {
|
||||
if (NSFoundationVersionNumber < NSFoundationVersionNumber_With_Fixed_5871104061079552_bug) {
|
||||
// Fix of bug
|
||||
// Open Radar:http://openradar.appspot.com/radar?id=5871104061079552 (status: Fixed in iOS8)
|
||||
// Issue about:https://github.com/AFNetworking/AFNetworking/issues/2093
|
||||
dispatch_sync(url_session_manager_creation_queue(), block);
|
||||
} else {
|
||||
block();
|
||||
}
|
||||
}
|
||||
|
||||
static dispatch_queue_t url_session_manager_processing_queue() {
|
||||
static dispatch_queue_t af_url_session_manager_processing_queue;
|
||||
static dispatch_once_t onceToken;
|
||||
@@ -60,27 +75,16 @@ NSString * const AFNetworkingTaskDidSuspendNotification = @"com.alamofire.networ
|
||||
NSString * const AFURLSessionDidInvalidateNotification = @"com.alamofire.networking.session.invalidate";
|
||||
NSString * const AFURLSessionDownloadTaskDidFailToMoveFileNotification = @"com.alamofire.networking.session.download.file-manager-error";
|
||||
|
||||
NSString * const AFNetworkingTaskDidStartNotification = @"com.alamofire.networking.task.resume"; // Deprecated
|
||||
NSString * const AFNetworkingTaskDidFinishNotification = @"com.alamofire.networking.task.complete"; // Deprecated
|
||||
|
||||
NSString * const AFNetworkingTaskDidCompleteSerializedResponseKey = @"com.alamofire.networking.task.complete.serializedresponse";
|
||||
NSString * const AFNetworkingTaskDidCompleteResponseSerializerKey = @"com.alamofire.networking.task.complete.responseserializer";
|
||||
NSString * const AFNetworkingTaskDidCompleteResponseDataKey = @"com.alamofire.networking.complete.finish.responsedata";
|
||||
NSString * const AFNetworkingTaskDidCompleteErrorKey = @"com.alamofire.networking.task.complete.error";
|
||||
NSString * const AFNetworkingTaskDidCompleteAssetPathKey = @"com.alamofire.networking.task.complete.assetpath";
|
||||
|
||||
NSString * const AFNetworkingTaskDidFinishSerializedResponseKey = @"com.alamofire.networking.task.complete.serializedresponse"; // Deprecated
|
||||
NSString * const AFNetworkingTaskDidFinishResponseSerializerKey = @"com.alamofire.networking.task.complete.responseserializer"; // Deprecated
|
||||
NSString * const AFNetworkingTaskDidFinishResponseDataKey = @"com.alamofire.networking.complete.finish.responsedata"; // Deprecated
|
||||
NSString * const AFNetworkingTaskDidFinishErrorKey = @"com.alamofire.networking.task.complete.error"; // Deprecated
|
||||
NSString * const AFNetworkingTaskDidFinishAssetPathKey = @"com.alamofire.networking.task.complete.assetpath"; // Deprecated
|
||||
|
||||
static NSString * const AFURLSessionManagerLockName = @"com.alamofire.networking.session.manager.lock";
|
||||
|
||||
static NSUInteger const AFMaximumNumberOfAttemptsToRecreateBackgroundSessionUploadTask = 3;
|
||||
|
||||
static void * AFTaskStateChangedContext = &AFTaskStateChangedContext;
|
||||
|
||||
typedef void (^AFURLSessionDidBecomeInvalidBlock)(NSURLSession *session, NSError *error);
|
||||
typedef NSURLSessionAuthChallengeDisposition (^AFURLSessionDidReceiveAuthenticationChallengeBlock)(NSURLSession *session, NSURLAuthenticationChallenge *challenge, NSURLCredential * __autoreleasing *credential);
|
||||
|
||||
@@ -100,17 +104,22 @@ typedef NSCachedURLResponse * (^AFURLSessionDataTaskWillCacheResponseBlock)(NSUR
|
||||
typedef NSURL * (^AFURLSessionDownloadTaskDidFinishDownloadingBlock)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, NSURL *location);
|
||||
typedef void (^AFURLSessionDownloadTaskDidWriteDataBlock)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite);
|
||||
typedef void (^AFURLSessionDownloadTaskDidResumeBlock)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, int64_t fileOffset, int64_t expectedTotalBytes);
|
||||
typedef void (^AFURLSessionTaskProgressBlock)(NSProgress *);
|
||||
|
||||
typedef void (^AFURLSessionTaskCompletionHandler)(NSURLResponse *response, id responseObject, NSError *error);
|
||||
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@interface AFURLSessionManagerTaskDelegate : NSObject <NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate>
|
||||
@property (nonatomic, weak) AFURLSessionManager *manager;
|
||||
@property (nonatomic, strong) NSMutableData *mutableData;
|
||||
@property (nonatomic, strong) NSProgress *progress;
|
||||
@property (nonatomic, strong) NSProgress *uploadProgress;
|
||||
@property (nonatomic, strong) NSProgress *downloadProgress;
|
||||
@property (nonatomic, copy) NSURL *downloadFileURL;
|
||||
@property (nonatomic, copy) AFURLSessionDownloadTaskDidFinishDownloadingBlock downloadTaskDidFinishDownloading;
|
||||
@property (nonatomic, copy) AFURLSessionTaskProgressBlock uploadProgressBlock;
|
||||
@property (nonatomic, copy) AFURLSessionTaskProgressBlock downloadProgressBlock;
|
||||
@property (nonatomic, copy) AFURLSessionTaskCompletionHandler completionHandler;
|
||||
@end
|
||||
|
||||
@@ -123,30 +132,123 @@ typedef void (^AFURLSessionTaskCompletionHandler)(NSURLResponse *response, id re
|
||||
}
|
||||
|
||||
self.mutableData = [NSMutableData data];
|
||||
self.uploadProgress = [[NSProgress alloc] initWithParent:nil userInfo:nil];
|
||||
self.uploadProgress.totalUnitCount = NSURLSessionTransferSizeUnknown;
|
||||
|
||||
self.progress = [NSProgress progressWithTotalUnitCount:0];
|
||||
|
||||
self.downloadProgress = [[NSProgress alloc] initWithParent:nil userInfo:nil];
|
||||
self.downloadProgress.totalUnitCount = NSURLSessionTransferSizeUnknown;
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - NSProgress Tracking
|
||||
|
||||
- (void)setupProgressForTask:(NSURLSessionTask *)task {
|
||||
__weak __typeof__(task) weakTask = task;
|
||||
|
||||
self.uploadProgress.totalUnitCount = task.countOfBytesExpectedToSend;
|
||||
self.downloadProgress.totalUnitCount = task.countOfBytesExpectedToReceive;
|
||||
[self.uploadProgress setCancellable:YES];
|
||||
[self.uploadProgress setCancellationHandler:^{
|
||||
__typeof__(weakTask) strongTask = weakTask;
|
||||
[strongTask cancel];
|
||||
}];
|
||||
[self.uploadProgress setPausable:YES];
|
||||
[self.uploadProgress setPausingHandler:^{
|
||||
__typeof__(weakTask) strongTask = weakTask;
|
||||
[strongTask suspend];
|
||||
}];
|
||||
if ([self.uploadProgress respondsToSelector:@selector(setResumingHandler:)]) {
|
||||
[self.uploadProgress setResumingHandler:^{
|
||||
__typeof__(weakTask) strongTask = weakTask;
|
||||
[strongTask resume];
|
||||
}];
|
||||
}
|
||||
|
||||
[self.downloadProgress setCancellable:YES];
|
||||
[self.downloadProgress setCancellationHandler:^{
|
||||
__typeof__(weakTask) strongTask = weakTask;
|
||||
[strongTask cancel];
|
||||
}];
|
||||
[self.downloadProgress setPausable:YES];
|
||||
[self.downloadProgress setPausingHandler:^{
|
||||
__typeof__(weakTask) strongTask = weakTask;
|
||||
[strongTask suspend];
|
||||
}];
|
||||
|
||||
if ([self.downloadProgress respondsToSelector:@selector(setResumingHandler:)]) {
|
||||
[self.downloadProgress setResumingHandler:^{
|
||||
__typeof__(weakTask) strongTask = weakTask;
|
||||
[strongTask resume];
|
||||
}];
|
||||
}
|
||||
|
||||
[task addObserver:self
|
||||
forKeyPath:NSStringFromSelector(@selector(countOfBytesReceived))
|
||||
options:NSKeyValueObservingOptionNew
|
||||
context:NULL];
|
||||
[task addObserver:self
|
||||
forKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToReceive))
|
||||
options:NSKeyValueObservingOptionNew
|
||||
context:NULL];
|
||||
|
||||
[task addObserver:self
|
||||
forKeyPath:NSStringFromSelector(@selector(countOfBytesSent))
|
||||
options:NSKeyValueObservingOptionNew
|
||||
context:NULL];
|
||||
[task addObserver:self
|
||||
forKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToSend))
|
||||
options:NSKeyValueObservingOptionNew
|
||||
context:NULL];
|
||||
|
||||
[self.downloadProgress addObserver:self
|
||||
forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
|
||||
options:NSKeyValueObservingOptionNew
|
||||
context:NULL];
|
||||
[self.uploadProgress addObserver:self
|
||||
forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
|
||||
options:NSKeyValueObservingOptionNew
|
||||
context:NULL];
|
||||
}
|
||||
|
||||
- (void)cleanUpProgressForTask:(NSURLSessionTask *)task {
|
||||
[task removeObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesReceived))];
|
||||
[task removeObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToReceive))];
|
||||
[task removeObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesSent))];
|
||||
[task removeObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToSend))];
|
||||
[self.downloadProgress removeObserver:self forKeyPath:NSStringFromSelector(@selector(fractionCompleted))];
|
||||
[self.uploadProgress removeObserver:self forKeyPath:NSStringFromSelector(@selector(fractionCompleted))];
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
|
||||
if ([object isKindOfClass:[NSURLSessionTask class]] || [object isKindOfClass:[NSURLSessionDownloadTask class]]) {
|
||||
if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesReceived))]) {
|
||||
self.downloadProgress.completedUnitCount = [change[NSKeyValueChangeNewKey] longLongValue];
|
||||
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesExpectedToReceive))]) {
|
||||
self.downloadProgress.totalUnitCount = [change[NSKeyValueChangeNewKey] longLongValue];
|
||||
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesSent))]) {
|
||||
self.uploadProgress.completedUnitCount = [change[NSKeyValueChangeNewKey] longLongValue];
|
||||
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesExpectedToSend))]) {
|
||||
self.uploadProgress.totalUnitCount = [change[NSKeyValueChangeNewKey] longLongValue];
|
||||
}
|
||||
}
|
||||
else if ([object isEqual:self.downloadProgress]) {
|
||||
if (self.downloadProgressBlock) {
|
||||
self.downloadProgressBlock(object);
|
||||
}
|
||||
}
|
||||
else if ([object isEqual:self.uploadProgress]) {
|
||||
if (self.uploadProgressBlock) {
|
||||
self.uploadProgressBlock(object);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - NSURLSessionTaskDelegate
|
||||
|
||||
- (void)URLSession:(__unused NSURLSession *)session
|
||||
task:(__unused NSURLSessionTask *)task
|
||||
didSendBodyData:(__unused int64_t)bytesSent
|
||||
totalBytesSent:(int64_t)totalBytesSent
|
||||
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
|
||||
{
|
||||
self.progress.totalUnitCount = totalBytesExpectedToSend;
|
||||
self.progress.completedUnitCount = totalBytesSent;
|
||||
}
|
||||
|
||||
- (void)URLSession:(__unused NSURLSession *)session
|
||||
task:(NSURLSessionTask *)task
|
||||
didCompleteWithError:(NSError *)error
|
||||
{
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wgnu"
|
||||
__strong AFURLSessionManager *manager = self.manager;
|
||||
|
||||
__block id responseObject = nil;
|
||||
@@ -154,10 +256,18 @@ didCompleteWithError:(NSError *)error
|
||||
__block NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
|
||||
userInfo[AFNetworkingTaskDidCompleteResponseSerializerKey] = manager.responseSerializer;
|
||||
|
||||
//Performance Improvement from #2672
|
||||
NSData *data = nil;
|
||||
if (self.mutableData) {
|
||||
data = [self.mutableData copy];
|
||||
//We no longer need the reference, so nil it out to gain back some memory.
|
||||
self.mutableData = nil;
|
||||
}
|
||||
|
||||
if (self.downloadFileURL) {
|
||||
userInfo[AFNetworkingTaskDidCompleteAssetPathKey] = self.downloadFileURL;
|
||||
} else if (self.mutableData) {
|
||||
userInfo[AFNetworkingTaskDidCompleteResponseDataKey] = [NSData dataWithData:self.mutableData];
|
||||
} else if (data) {
|
||||
userInfo[AFNetworkingTaskDidCompleteResponseDataKey] = data;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
@@ -175,7 +285,7 @@ didCompleteWithError:(NSError *)error
|
||||
} else {
|
||||
dispatch_async(url_session_manager_processing_queue(), ^{
|
||||
NSError *serializationError = nil;
|
||||
responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:[NSData dataWithData:self.mutableData] error:&serializationError];
|
||||
responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];
|
||||
|
||||
if (self.downloadFileURL) {
|
||||
responseObject = self.downloadFileURL;
|
||||
@@ -193,14 +303,13 @@ didCompleteWithError:(NSError *)error
|
||||
if (self.completionHandler) {
|
||||
self.completionHandler(task.response, responseObject, serializationError);
|
||||
}
|
||||
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
|
||||
#pragma mark - NSURLSessionDataTaskDelegate
|
||||
@@ -233,24 +342,133 @@ didFinishDownloadingToURL:(NSURL *)location
|
||||
}
|
||||
}
|
||||
|
||||
- (void)URLSession:(__unused NSURLSession *)session
|
||||
downloadTask:(__unused NSURLSessionDownloadTask *)downloadTask
|
||||
didWriteData:(__unused int64_t)bytesWritten
|
||||
totalBytesWritten:(int64_t)totalBytesWritten
|
||||
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
|
||||
{
|
||||
self.progress.totalUnitCount = totalBytesExpectedToWrite;
|
||||
self.progress.completedUnitCount = totalBytesWritten;
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
/**
|
||||
* A workaround for issues related to key-value observing the `state` of an `NSURLSessionTask`.
|
||||
*
|
||||
* See:
|
||||
* - https://github.com/AFNetworking/AFNetworking/issues/1477
|
||||
* - https://github.com/AFNetworking/AFNetworking/issues/2638
|
||||
* - https://github.com/AFNetworking/AFNetworking/pull/2702
|
||||
*/
|
||||
|
||||
static inline void af_swizzleSelector(Class theClass, SEL originalSelector, SEL swizzledSelector) {
|
||||
Method originalMethod = class_getInstanceMethod(theClass, originalSelector);
|
||||
Method swizzledMethod = class_getInstanceMethod(theClass, swizzledSelector);
|
||||
method_exchangeImplementations(originalMethod, swizzledMethod);
|
||||
}
|
||||
|
||||
- (void)URLSession:(__unused NSURLSession *)session
|
||||
downloadTask:(__unused NSURLSessionDownloadTask *)downloadTask
|
||||
didResumeAtOffset:(int64_t)fileOffset
|
||||
expectedTotalBytes:(int64_t)expectedTotalBytes {
|
||||
self.progress.totalUnitCount = expectedTotalBytes;
|
||||
self.progress.completedUnitCount = fileOffset;
|
||||
static inline BOOL af_addMethod(Class theClass, SEL selector, Method method) {
|
||||
return class_addMethod(theClass, selector, method_getImplementation(method), method_getTypeEncoding(method));
|
||||
}
|
||||
|
||||
static NSString * const AFNSURLSessionTaskDidResumeNotification = @"com.alamofire.networking.nsurlsessiontask.resume";
|
||||
static NSString * const AFNSURLSessionTaskDidSuspendNotification = @"com.alamofire.networking.nsurlsessiontask.suspend";
|
||||
|
||||
@interface _AFURLSessionTaskSwizzling : NSObject
|
||||
|
||||
@end
|
||||
|
||||
@implementation _AFURLSessionTaskSwizzling
|
||||
|
||||
+ (void)load {
|
||||
/**
|
||||
WARNING: Trouble Ahead
|
||||
https://github.com/AFNetworking/AFNetworking/pull/2702
|
||||
*/
|
||||
|
||||
if (NSClassFromString(@"NSURLSessionTask")) {
|
||||
/**
|
||||
iOS 7 and iOS 8 differ in NSURLSessionTask implementation, which makes the next bit of code a bit tricky.
|
||||
Many Unit Tests have been built to validate as much of this behavior has possible.
|
||||
Here is what we know:
|
||||
- NSURLSessionTasks are implemented with class clusters, meaning the class you request from the API isn't actually the type of class you will get back.
|
||||
- Simply referencing `[NSURLSessionTask class]` will not work. You need to ask an `NSURLSession` to actually create an object, and grab the class from there.
|
||||
- On iOS 7, `localDataTask` is a `__NSCFLocalDataTask`, which inherits from `__NSCFLocalSessionTask`, which inherits from `__NSCFURLSessionTask`.
|
||||
- On iOS 8, `localDataTask` is a `__NSCFLocalDataTask`, which inherits from `__NSCFLocalSessionTask`, which inherits from `NSURLSessionTask`.
|
||||
- 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`
|
||||
3) Check to see if the current class has an implementation of resume. If so, continue to step 4.
|
||||
4) Grab the super class of the current class.
|
||||
5) Grab a pointer for the current class to the current implementation of `resume`.
|
||||
6) Grab a pointer for the super class to the current implementation of `resume`.
|
||||
7) If the current class implementation of `resume` is not equal to the super class implementation of `resume` AND the current implementation of `resume` is not equal to the original implementation of `af_resume`, THEN swizzle the methods
|
||||
8) Set the current class to the super class, and repeat steps 3-8
|
||||
*/
|
||||
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
|
||||
NSURLSession * session = [NSURLSession sessionWithConfiguration:configuration];
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wnonnull"
|
||||
NSURLSessionDataTask *localDataTask = [session dataTaskWithURL:nil];
|
||||
#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)));
|
||||
IMP superclassResumeIMP = method_getImplementation(class_getInstanceMethod(superClass, @selector(resume)));
|
||||
if (classResumeIMP != superclassResumeIMP &&
|
||||
originalAFResumeIMP != classResumeIMP) {
|
||||
[self swizzleResumeAndSuspendMethodForClass:currentClass];
|
||||
}
|
||||
currentClass = [currentClass superclass];
|
||||
}
|
||||
|
||||
[localDataTask cancel];
|
||||
[session finishTasksAndInvalidate];
|
||||
}
|
||||
}
|
||||
|
||||
+ (void)swizzleResumeAndSuspendMethodForClass:(Class)theClass {
|
||||
Method afResumeMethod = class_getInstanceMethod(self, @selector(af_resume));
|
||||
Method afSuspendMethod = class_getInstanceMethod(self, @selector(af_suspend));
|
||||
|
||||
if (af_addMethod(theClass, @selector(af_resume), afResumeMethod)) {
|
||||
af_swizzleSelector(theClass, @selector(resume), @selector(af_resume));
|
||||
}
|
||||
|
||||
if (af_addMethod(theClass, @selector(af_suspend), afSuspendMethod)) {
|
||||
af_swizzleSelector(theClass, @selector(suspend), @selector(af_suspend));
|
||||
}
|
||||
}
|
||||
|
||||
- (NSURLSessionTaskState)state {
|
||||
NSAssert(NO, @"State method should never be called in the actual dummy class");
|
||||
return NSURLSessionTaskStateCanceling;
|
||||
}
|
||||
|
||||
- (void)af_resume {
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)af_suspend {
|
||||
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];
|
||||
}
|
||||
}
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
@@ -260,6 +478,7 @@ expectedTotalBytes:(int64_t)expectedTotalBytes {
|
||||
@property (readwrite, nonatomic, strong) NSOperationQueue *operationQueue;
|
||||
@property (readwrite, nonatomic, strong) NSURLSession *session;
|
||||
@property (readwrite, nonatomic, strong) NSMutableDictionary *mutableTaskDelegatesKeyedByTaskIdentifier;
|
||||
@property (readonly, nonatomic, copy) NSString *taskDescriptionForSessionTasks;
|
||||
@property (readwrite, nonatomic, strong) NSLock *lock;
|
||||
@property (readwrite, nonatomic, copy) AFURLSessionDidBecomeInvalidBlock sessionDidBecomeInvalid;
|
||||
@property (readwrite, nonatomic, copy) AFURLSessionDidReceiveAuthenticationChallengeBlock sessionDidReceiveAuthenticationChallenge;
|
||||
@@ -305,22 +524,24 @@ expectedTotalBytes:(int64_t)expectedTotalBytes {
|
||||
|
||||
self.securityPolicy = [AFSecurityPolicy defaultPolicy];
|
||||
|
||||
#if !TARGET_OS_WATCH
|
||||
self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];
|
||||
#endif
|
||||
|
||||
self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init];
|
||||
|
||||
self.lock = [[NSLock alloc] init];
|
||||
self.lock.name = AFURLSessionManagerLockName;
|
||||
|
||||
|
||||
[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
|
||||
for (NSURLSessionDataTask *task in dataTasks) {
|
||||
[self addDelegateForDataTask:task completionHandler:nil];
|
||||
[self addDelegateForDataTask:task uploadProgress:nil downloadProgress:nil completionHandler:nil];
|
||||
}
|
||||
|
||||
|
||||
for (NSURLSessionUploadTask *uploadTask in uploadTasks) {
|
||||
[self addDelegateForUploadTask:uploadTask progress:nil completionHandler:nil];
|
||||
}
|
||||
|
||||
|
||||
for (NSURLSessionDownloadTask *downloadTask in downloadTasks) {
|
||||
[self addDelegateForDownloadTask:downloadTask progress:nil destination:nil completionHandler:nil];
|
||||
}
|
||||
@@ -329,6 +550,38 @@ expectedTotalBytes:(int64_t)expectedTotalBytes {
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (NSString *)taskDescriptionForSessionTasks {
|
||||
return [NSString stringWithFormat:@"%p", self];
|
||||
}
|
||||
|
||||
- (void)taskDidResume:(NSNotification *)notification {
|
||||
NSURLSessionTask *task = notification.object;
|
||||
if ([task respondsToSelector:@selector(taskDescription)]) {
|
||||
if ([task.taskDescription isEqualToString:self.taskDescriptionForSessionTasks]) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidResumeNotification object:task];
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)taskDidSuspend:(NSNotification *)notification {
|
||||
NSURLSessionTask *task = notification.object;
|
||||
if ([task respondsToSelector:@selector(taskDescription)]) {
|
||||
if ([task.taskDescription isEqualToString:self.taskDescriptionForSessionTasks]) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidSuspendNotification object:task];
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (AFURLSessionManagerTaskDelegate *)delegateForTask:(NSURLSessionTask *)task {
|
||||
@@ -348,55 +601,46 @@ expectedTotalBytes:(int64_t)expectedTotalBytes {
|
||||
NSParameterAssert(task);
|
||||
NSParameterAssert(delegate);
|
||||
|
||||
[task addObserver:self forKeyPath:NSStringFromSelector(@selector(state)) options:(NSKeyValueObservingOptions)(NSKeyValueObservingOptionOld |NSKeyValueObservingOptionNew) context:AFTaskStateChangedContext];
|
||||
[self.lock lock];
|
||||
self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
|
||||
[delegate setupProgressForTask:task];
|
||||
[self addNotificationObserverForTask:task];
|
||||
[self.lock unlock];
|
||||
}
|
||||
|
||||
- (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTask
|
||||
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
|
||||
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
|
||||
completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
|
||||
{
|
||||
AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] init];
|
||||
delegate.manager = self;
|
||||
delegate.completionHandler = completionHandler;
|
||||
|
||||
dataTask.taskDescription = self.taskDescriptionForSessionTasks;
|
||||
[self setDelegate:delegate forTask:dataTask];
|
||||
|
||||
delegate.uploadProgressBlock = uploadProgressBlock;
|
||||
delegate.downloadProgressBlock = downloadProgressBlock;
|
||||
}
|
||||
|
||||
- (void)addDelegateForUploadTask:(NSURLSessionUploadTask *)uploadTask
|
||||
progress:(NSProgress * __autoreleasing *)progress
|
||||
progress:(void (^)(NSProgress *uploadProgress)) uploadProgressBlock
|
||||
completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
|
||||
{
|
||||
AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] init];
|
||||
delegate.manager = self;
|
||||
delegate.completionHandler = completionHandler;
|
||||
|
||||
int64_t totalUnitCount = uploadTask.countOfBytesExpectedToSend;
|
||||
if(totalUnitCount == NSURLSessionTransferSizeUnknown) {
|
||||
NSString *contentLength = [uploadTask.originalRequest valueForHTTPHeaderField:@"Content-Length"];
|
||||
if(contentLength) {
|
||||
totalUnitCount = (int64_t) [contentLength longLongValue];
|
||||
}
|
||||
}
|
||||
|
||||
delegate.progress = [NSProgress progressWithTotalUnitCount:totalUnitCount];
|
||||
delegate.progress.pausingHandler = ^{
|
||||
[uploadTask suspend];
|
||||
};
|
||||
delegate.progress.cancellationHandler = ^{
|
||||
[uploadTask cancel];
|
||||
};
|
||||
|
||||
if (progress) {
|
||||
*progress = delegate.progress;
|
||||
}
|
||||
uploadTask.taskDescription = self.taskDescriptionForSessionTasks;
|
||||
|
||||
[self setDelegate:delegate forTask:uploadTask];
|
||||
|
||||
delegate.uploadProgressBlock = uploadProgressBlock;
|
||||
}
|
||||
|
||||
- (void)addDelegateForDownloadTask:(NSURLSessionDownloadTask *)downloadTask
|
||||
progress:(NSProgress * __autoreleasing *)progress
|
||||
progress:(void (^)(NSProgress *downloadProgress)) downloadProgressBlock
|
||||
destination:(NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
|
||||
completionHandler:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler
|
||||
{
|
||||
@@ -410,28 +654,24 @@ expectedTotalBytes:(int64_t)expectedTotalBytes {
|
||||
};
|
||||
}
|
||||
|
||||
if (progress) {
|
||||
*progress = delegate.progress;
|
||||
}
|
||||
downloadTask.taskDescription = self.taskDescriptionForSessionTasks;
|
||||
|
||||
[self setDelegate:delegate forTask:downloadTask];
|
||||
|
||||
delegate.downloadProgressBlock = downloadProgressBlock;
|
||||
}
|
||||
|
||||
- (void)removeDelegateForTask:(NSURLSessionTask *)task {
|
||||
NSParameterAssert(task);
|
||||
|
||||
[task removeObserver:self forKeyPath:NSStringFromSelector(@selector(state)) context:AFTaskStateChangedContext];
|
||||
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];
|
||||
[self.lock lock];
|
||||
[delegate cleanUpProgressForTask:task];
|
||||
[self removeNotificationObserverForTask:task];
|
||||
[self.mutableTaskDelegatesKeyedByTaskIdentifier removeObjectForKey:@(task.taskIdentifier)];
|
||||
[self.lock unlock];
|
||||
}
|
||||
|
||||
- (void)removeAllDelegates {
|
||||
[self.lock lock];
|
||||
[self.mutableTaskDelegatesKeyedByTaskIdentifier removeAllObjects];
|
||||
[self.lock unlock];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (NSArray *)tasksForKeyPath:(NSString *)keyPath {
|
||||
@@ -492,17 +732,36 @@ expectedTotalBytes:(int64_t)expectedTotalBytes {
|
||||
_responseSerializer = responseSerializer;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
- (void)addNotificationObserverForTask:(NSURLSessionTask *)task {
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskDidResume:) name:AFNSURLSessionTaskDidResumeNotification object:task];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskDidSuspend:) name:AFNSURLSessionTaskDidSuspendNotification object:task];
|
||||
}
|
||||
|
||||
- (void)removeNotificationObserverForTask:(NSURLSessionTask *)task {
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:AFNSURLSessionTaskDidSuspendNotification object:task];
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:AFNSURLSessionTaskDidResumeNotification object:task];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
|
||||
completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
|
||||
{
|
||||
return [self dataTaskWithRequest:request uploadProgress:nil downloadProgress:nil completionHandler:completionHandler];
|
||||
}
|
||||
|
||||
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
|
||||
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
|
||||
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
|
||||
completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler {
|
||||
|
||||
__block NSURLSessionDataTask *dataTask = nil;
|
||||
dispatch_sync(url_session_manager_creation_queue(), ^{
|
||||
url_session_manager_create_task_safely(^{
|
||||
dataTask = [self.session dataTaskWithRequest:request];
|
||||
});
|
||||
|
||||
[self addDelegateForDataTask:dataTask completionHandler:completionHandler];
|
||||
[self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];
|
||||
|
||||
return dataTask;
|
||||
}
|
||||
@@ -511,11 +770,11 @@ expectedTotalBytes:(int64_t)expectedTotalBytes {
|
||||
|
||||
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request
|
||||
fromFile:(NSURL *)fileURL
|
||||
progress:(NSProgress * __autoreleasing *)progress
|
||||
progress:(void (^)(NSProgress *uploadProgress)) uploadProgressBlock
|
||||
completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
|
||||
{
|
||||
__block NSURLSessionUploadTask *uploadTask = nil;
|
||||
dispatch_sync(url_session_manager_creation_queue(), ^{
|
||||
url_session_manager_create_task_safely(^{
|
||||
uploadTask = [self.session uploadTaskWithRequest:request fromFile:fileURL];
|
||||
});
|
||||
|
||||
@@ -525,36 +784,36 @@ expectedTotalBytes:(int64_t)expectedTotalBytes {
|
||||
}
|
||||
}
|
||||
|
||||
[self addDelegateForUploadTask:uploadTask progress:progress completionHandler:completionHandler];
|
||||
[self addDelegateForUploadTask:uploadTask progress:uploadProgressBlock completionHandler:completionHandler];
|
||||
|
||||
return uploadTask;
|
||||
}
|
||||
|
||||
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request
|
||||
fromData:(NSData *)bodyData
|
||||
progress:(NSProgress * __autoreleasing *)progress
|
||||
progress:(void (^)(NSProgress *uploadProgress)) uploadProgressBlock
|
||||
completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
|
||||
{
|
||||
__block NSURLSessionUploadTask *uploadTask = nil;
|
||||
dispatch_sync(url_session_manager_creation_queue(), ^{
|
||||
url_session_manager_create_task_safely(^{
|
||||
uploadTask = [self.session uploadTaskWithRequest:request fromData:bodyData];
|
||||
});
|
||||
|
||||
[self addDelegateForUploadTask:uploadTask progress:progress completionHandler:completionHandler];
|
||||
[self addDelegateForUploadTask:uploadTask progress:uploadProgressBlock completionHandler:completionHandler];
|
||||
|
||||
return uploadTask;
|
||||
}
|
||||
|
||||
- (NSURLSessionUploadTask *)uploadTaskWithStreamedRequest:(NSURLRequest *)request
|
||||
progress:(NSProgress * __autoreleasing *)progress
|
||||
progress:(void (^)(NSProgress *uploadProgress)) uploadProgressBlock
|
||||
completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
|
||||
{
|
||||
__block NSURLSessionUploadTask *uploadTask = nil;
|
||||
dispatch_sync(url_session_manager_creation_queue(), ^{
|
||||
url_session_manager_create_task_safely(^{
|
||||
uploadTask = [self.session uploadTaskWithStreamedRequest:request];
|
||||
});
|
||||
|
||||
[self addDelegateForUploadTask:uploadTask progress:progress completionHandler:completionHandler];
|
||||
[self addDelegateForUploadTask:uploadTask progress:uploadProgressBlock completionHandler:completionHandler];
|
||||
|
||||
return uploadTask;
|
||||
}
|
||||
@@ -562,43 +821,42 @@ expectedTotalBytes:(int64_t)expectedTotalBytes {
|
||||
#pragma mark -
|
||||
|
||||
- (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request
|
||||
progress:(NSProgress * __autoreleasing *)progress
|
||||
progress:(void (^)(NSProgress *downloadProgress)) downloadProgressBlock
|
||||
destination:(NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
|
||||
completionHandler:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler
|
||||
{
|
||||
__block NSURLSessionDownloadTask *downloadTask = nil;
|
||||
dispatch_sync(url_session_manager_creation_queue(), ^{
|
||||
url_session_manager_create_task_safely(^{
|
||||
downloadTask = [self.session downloadTaskWithRequest:request];
|
||||
});
|
||||
|
||||
[self addDelegateForDownloadTask:downloadTask progress:progress destination:destination completionHandler:completionHandler];
|
||||
[self addDelegateForDownloadTask:downloadTask progress:downloadProgressBlock destination:destination completionHandler:completionHandler];
|
||||
|
||||
return downloadTask;
|
||||
}
|
||||
|
||||
- (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData
|
||||
progress:(NSProgress * __autoreleasing *)progress
|
||||
progress:(void (^)(NSProgress *downloadProgress)) downloadProgressBlock
|
||||
destination:(NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
|
||||
completionHandler:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler
|
||||
{
|
||||
__block NSURLSessionDownloadTask *downloadTask = nil;
|
||||
dispatch_sync(url_session_manager_creation_queue(), ^{
|
||||
url_session_manager_create_task_safely(^{
|
||||
downloadTask = [self.session downloadTaskWithResumeData:resumeData];
|
||||
});
|
||||
|
||||
[self addDelegateForDownloadTask:downloadTask progress:progress destination:destination completionHandler:completionHandler];
|
||||
[self addDelegateForDownloadTask:downloadTask progress:downloadProgressBlock destination:destination completionHandler:completionHandler];
|
||||
|
||||
return downloadTask;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (NSProgress *)uploadProgressForTask:(NSURLSessionUploadTask *)uploadTask {
|
||||
return [[self delegateForTask:uploadTask] progress];
|
||||
- (NSProgress *)uploadProgressForTask:(NSURLSessionTask *)task {
|
||||
return [[self delegateForTask:task] uploadProgress];
|
||||
}
|
||||
|
||||
- (NSProgress *)downloadProgressForTask:(NSURLSessionDownloadTask *)downloadTask {
|
||||
return [[self delegateForTask:downloadTask] progress];
|
||||
- (NSProgress *)downloadProgressForTask:(NSURLSessionTask *)task {
|
||||
return [[self delegateForTask:task] downloadProgress];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
@@ -689,42 +947,6 @@ expectedTotalBytes:(int64_t)expectedTotalBytes {
|
||||
return [[self class] instancesRespondToSelector:selector];
|
||||
}
|
||||
|
||||
#pragma mark - NSKeyValueObserving
|
||||
|
||||
- (void)observeValueForKeyPath:(NSString *)keyPath
|
||||
ofObject:(id)object
|
||||
change:(NSDictionary *)change
|
||||
context:(void *)context
|
||||
{
|
||||
if (context == AFTaskStateChangedContext && [keyPath isEqualToString:@"state"]) {
|
||||
if (change[NSKeyValueChangeOldKey] && change[NSKeyValueChangeNewKey] && [change[NSKeyValueChangeNewKey] isEqual:change[NSKeyValueChangeOldKey]]) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *notificationName = nil;
|
||||
switch ([(NSURLSessionTask *)object state]) {
|
||||
case NSURLSessionTaskStateRunning:
|
||||
notificationName = AFNetworkingTaskDidResumeNotification;
|
||||
break;
|
||||
case NSURLSessionTaskStateSuspended:
|
||||
notificationName = AFNetworkingTaskDidSuspendNotification;
|
||||
break;
|
||||
case NSURLSessionTaskStateCompleted:
|
||||
// AFNetworkingTaskDidFinishNotification posted by task completion handlers
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (notificationName) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:notificationName object:object];
|
||||
});
|
||||
}
|
||||
} else {
|
||||
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - NSURLSessionDelegate
|
||||
|
||||
- (void)URLSession:(NSURLSession *)session
|
||||
@@ -734,15 +956,6 @@ didBecomeInvalidWithError:(NSError *)error
|
||||
self.sessionDidBecomeInvalid(session, error);
|
||||
}
|
||||
|
||||
[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
|
||||
NSArray *tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"];
|
||||
for (NSURLSessionTask *task in tasks) {
|
||||
[task removeObserver:self forKeyPath:NSStringFromSelector(@selector(state)) context:AFTaskStateChangedContext];
|
||||
}
|
||||
|
||||
[self removeAllDelegates];
|
||||
}];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDidInvalidateNotification object:session];
|
||||
}
|
||||
|
||||
@@ -765,7 +978,7 @@ didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
|
||||
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
|
||||
}
|
||||
} else {
|
||||
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
|
||||
disposition = NSURLSessionAuthChallengeRejectProtectionSpace;
|
||||
}
|
||||
} else {
|
||||
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
|
||||
@@ -812,7 +1025,7 @@ didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
|
||||
disposition = NSURLSessionAuthChallengeUseCredential;
|
||||
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
|
||||
} else {
|
||||
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
|
||||
disposition = NSURLSessionAuthChallengeRejectProtectionSpace;
|
||||
}
|
||||
} else {
|
||||
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
|
||||
@@ -829,7 +1042,7 @@ didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
|
||||
needNewBodyStream:(void (^)(NSInputStream *bodyStream))completionHandler
|
||||
{
|
||||
NSInputStream *inputStream = nil;
|
||||
|
||||
|
||||
if (self.taskNeedNewBodyStream) {
|
||||
inputStream = self.taskNeedNewBodyStream(session, task);
|
||||
} else if (task.originalRequest.HTTPBodyStream && [task.originalRequest.HTTPBodyStream conformsToProtocol:@protocol(NSCopying)]) {
|
||||
@@ -847,7 +1060,7 @@ didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
|
||||
totalBytesSent:(int64_t)totalBytesSent
|
||||
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
|
||||
{
|
||||
|
||||
|
||||
int64_t totalUnitCount = totalBytesExpectedToSend;
|
||||
if(totalUnitCount == NSURLSessionTransferSizeUnknown) {
|
||||
NSString *contentLength = [task.originalRequest valueForHTTPHeaderField:@"Content-Length"];
|
||||
@@ -855,9 +1068,6 @@ totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
|
||||
totalUnitCount = (int64_t) [contentLength longLongValue];
|
||||
}
|
||||
}
|
||||
|
||||
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];
|
||||
[delegate URLSession:session task:task didSendBodyData:bytesSent totalBytesSent:totalBytesSent totalBytesExpectedToSend:totalUnitCount];
|
||||
|
||||
if (self.taskDidSendBodyData) {
|
||||
self.taskDidSendBodyData(session, task, bytesSent, totalBytesSent, totalUnitCount);
|
||||
@@ -880,7 +1090,6 @@ didCompleteWithError:(NSError *)error
|
||||
if (self.taskDidComplete) {
|
||||
self.taskDidComplete(session, task, error);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#pragma mark - NSURLSessionDataDelegate
|
||||
@@ -920,6 +1129,7 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask
|
||||
dataTask:(NSURLSessionDataTask *)dataTask
|
||||
didReceiveData:(NSData *)data
|
||||
{
|
||||
|
||||
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask];
|
||||
[delegate URLSession:session dataTask:dataTask didReceiveData:data];
|
||||
|
||||
@@ -958,9 +1168,11 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask
|
||||
downloadTask:(NSURLSessionDownloadTask *)downloadTask
|
||||
didFinishDownloadingToURL:(NSURL *)location
|
||||
{
|
||||
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:downloadTask];
|
||||
if (self.downloadTaskDidFinishDownloading) {
|
||||
NSURL *fileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location);
|
||||
if (fileURL) {
|
||||
delegate.downloadFileURL = fileURL;
|
||||
NSError *error = nil;
|
||||
[[NSFileManager defaultManager] moveItemAtURL:location toURL:fileURL error:&error];
|
||||
if (error) {
|
||||
@@ -970,8 +1182,7 @@ didFinishDownloadingToURL:(NSURL *)location
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:downloadTask];
|
||||
|
||||
if (delegate) {
|
||||
[delegate URLSession:session downloadTask:downloadTask didFinishDownloadingToURL:location];
|
||||
}
|
||||
@@ -983,9 +1194,6 @@ didFinishDownloadingToURL:(NSURL *)location
|
||||
totalBytesWritten:(int64_t)totalBytesWritten
|
||||
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
|
||||
{
|
||||
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:downloadTask];
|
||||
[delegate URLSession:session downloadTask:downloadTask didWriteData:bytesWritten totalBytesWritten:totalBytesWritten totalBytesExpectedToWrite:totalBytesExpectedToWrite];
|
||||
|
||||
if (self.downloadTaskDidWriteData) {
|
||||
self.downloadTaskDidWriteData(session, downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite);
|
||||
}
|
||||
@@ -996,9 +1204,6 @@ totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
|
||||
didResumeAtOffset:(int64_t)fileOffset
|
||||
expectedTotalBytes:(int64_t)expectedTotalBytes
|
||||
{
|
||||
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:downloadTask];
|
||||
[delegate URLSession:session downloadTask:downloadTask didResumeAtOffset:fileOffset expectedTotalBytes:expectedTotalBytes];
|
||||
|
||||
if (self.downloadTaskDidResume) {
|
||||
self.downloadTaskDidResume(session, downloadTask, fileOffset, expectedTotalBytes);
|
||||
}
|
||||
@@ -1010,7 +1215,7 @@ expectedTotalBytes:(int64_t)expectedTotalBytes
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (id)initWithCoder:(NSCoder *)decoder {
|
||||
- (instancetype)initWithCoder:(NSCoder *)decoder {
|
||||
NSURLSessionConfiguration *configuration = [decoder decodeObjectOfClass:[NSURLSessionConfiguration class] forKey:@"sessionConfiguration"];
|
||||
|
||||
self = [self initWithSessionConfiguration:configuration];
|
||||
@@ -1027,10 +1232,8 @@ expectedTotalBytes:(int64_t)expectedTotalBytes
|
||||
|
||||
#pragma mark - NSCopying
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone {
|
||||
- (instancetype)copyWithZone:(NSZone *)zone {
|
||||
return [[[self class] allocWithZone:zone] initWithSessionConfiguration:self.session.configuration];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import <Cordova/CDVPlugin.h>
|
||||
#import <Cordova/CDVJSON.h>
|
||||
|
||||
@interface CordovaHttpPlugin : CDVPlugin
|
||||
|
||||
- (void)useBasicAuth:(CDVInvokedUrlCommand*)command;
|
||||
- (void)setHeader:(CDVInvokedUrlCommand*)command;
|
||||
- (void)enableSSLPinning:(CDVInvokedUrlCommand*)command;
|
||||
- (void)acceptAllCerts:(CDVInvokedUrlCommand*)command;
|
||||
- (void)setServerTrustMode:(CDVInvokedUrlCommand*)command;
|
||||
- (void)disableRedirect:(CDVInvokedUrlCommand*)command;
|
||||
- (void)post:(CDVInvokedUrlCommand*)command;
|
||||
- (void)get:(CDVInvokedUrlCommand*)command;
|
||||
- (void)put:(CDVInvokedUrlCommand*)command;
|
||||
- (void)patch:(CDVInvokedUrlCommand*)command;
|
||||
- (void)delete:(CDVInvokedUrlCommand*)command;
|
||||
- (void)uploadFile:(CDVInvokedUrlCommand*)command;
|
||||
- (void)downloadFile:(CDVInvokedUrlCommand*)command;
|
||||
|
||||
@end
|
||||
@end
|
||||
|
||||
@@ -1,239 +1,565 @@
|
||||
#import "CordovaHttpPlugin.h"
|
||||
#import "CDVFile.h"
|
||||
#import "TextResponseSerializer.h"
|
||||
#import "HttpManager.h"
|
||||
#import "TextRequestSerializer.h"
|
||||
#import "AFHTTPSessionManager.h"
|
||||
#import "SDNetworkActivityIndicator.h"
|
||||
|
||||
@interface CordovaHttpPlugin()
|
||||
|
||||
- (void)setRequestHeaders:(NSDictionary*)headers;
|
||||
- (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;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation CordovaHttpPlugin {
|
||||
AFHTTPRequestSerializer *requestSerializer;
|
||||
AFSecurityPolicy *securityPolicy;
|
||||
bool redirect;
|
||||
}
|
||||
|
||||
- (void)pluginInitialize {
|
||||
requestSerializer = [AFHTTPRequestSerializer serializer];
|
||||
securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeNone];
|
||||
redirect = true;
|
||||
}
|
||||
|
||||
- (void)setRequestHeaders:(NSDictionary*)headers {
|
||||
[HttpManager sharedClient].requestSerializer = [AFHTTPRequestSerializer serializer];
|
||||
[requestSerializer.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
|
||||
[[HttpManager sharedClient].requestSerializer setValue:obj forHTTPHeaderField:key];
|
||||
}];
|
||||
[headers enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
|
||||
[[HttpManager sharedClient].requestSerializer setValue:obj forHTTPHeaderField:key];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)useBasicAuth:(CDVInvokedUrlCommand*)command {
|
||||
NSString *username = [command.arguments objectAtIndex:0];
|
||||
NSString *password = [command.arguments objectAtIndex:1];
|
||||
|
||||
[requestSerializer setAuthorizationHeaderFieldWithUsername:username password:password];
|
||||
|
||||
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
|
||||
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
|
||||
}
|
||||
|
||||
- (void)setHeader:(CDVInvokedUrlCommand*)command {
|
||||
NSString *header = [command.arguments objectAtIndex:0];
|
||||
NSString *value = [command.arguments objectAtIndex:1];
|
||||
|
||||
[requestSerializer setValue:value forHTTPHeaderField: header];
|
||||
|
||||
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
|
||||
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
|
||||
}
|
||||
|
||||
- (void)enableSSLPinning:(CDVInvokedUrlCommand*)command {
|
||||
bool enable = [[command.arguments objectAtIndex:0] boolValue];
|
||||
if (enable) {
|
||||
[HttpManager sharedClient].securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
|
||||
[HttpManager sharedClient].securityPolicy.validatesCertificateChain = NO;
|
||||
- (void)setRequestSerializer:(NSString*)serializerName forManager:(AFHTTPSessionManager*)manager {
|
||||
if ([serializerName isEqualToString:@"json"]) {
|
||||
manager.requestSerializer = [AFJSONRequestSerializer serializer];
|
||||
} else if ([serializerName isEqualToString:@"utf8"]) {
|
||||
manager.requestSerializer = [TextRequestSerializer serializer];
|
||||
} else {
|
||||
[HttpManager sharedClient].securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeNone];
|
||||
manager.requestSerializer = [AFHTTPRequestSerializer serializer];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
- (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) {
|
||||
return request;
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)handleSuccess:(NSMutableDictionary*)dictionary withResponse:(NSHTTPURLResponse*)response andData:(id)data {
|
||||
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 (data != nil) {
|
||||
[dictionary setObject:data forKey:@"data"];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)handleError:(NSMutableDictionary*)dictionary withResponse:(NSHTTPURLResponse*)response error:(NSError*)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"];
|
||||
}
|
||||
} else {
|
||||
[dictionary setObject:[self getStatusCode:error] forKey:@"status"];
|
||||
[dictionary setObject:[error localizedDescription] forKey:@"error"];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)handleException:(NSException*)exception withCommand:(CDVInvokedUrlCommand*)command {
|
||||
CordovaHttpPlugin* __weak weakSelf = self;
|
||||
|
||||
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
|
||||
[dictionary setValue:exception.userInfo forKey:@"error"];
|
||||
[dictionary setObject:[NSNumber numberWithInt:-1] forKey:@"status"];
|
||||
|
||||
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary];
|
||||
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
|
||||
}
|
||||
|
||||
- (NSNumber*)getStatusCode:(NSError*) error {
|
||||
switch ([error code]) {
|
||||
case -1001:
|
||||
// timeout
|
||||
return [NSNumber numberWithInt:-4];
|
||||
case -1002:
|
||||
// unsupported URL
|
||||
return [NSNumber numberWithInt:-5];
|
||||
case -1003:
|
||||
// server not found
|
||||
return [NSNumber numberWithInt:-3];
|
||||
case -1009:
|
||||
// no connection
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSMutableDictionary*)copyHeaderFields:(NSDictionary *)headerFields {
|
||||
NSMutableDictionary *headerFieldsCopy = [[NSMutableDictionary alloc] initWithCapacity:headerFields.count];
|
||||
NSString *headerKeyCopy;
|
||||
|
||||
for (NSString *headerKey in headerFields.allKeys) {
|
||||
headerKeyCopy = [[headerKey mutableCopy] lowercaseString];
|
||||
[headerFieldsCopy setValue:[headerFields objectForKey:headerKey] forKey:headerKeyCopy];
|
||||
}
|
||||
|
||||
return headerFieldsCopy;
|
||||
}
|
||||
|
||||
- (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)acceptAllCerts:(CDVInvokedUrlCommand*)command {
|
||||
- (void)setTimeout:(NSTimeInterval)timeout forManager:(AFHTTPSessionManager*)manager {
|
||||
[manager.requestSerializer setTimeoutInterval:timeout];
|
||||
}
|
||||
|
||||
- (void)disableRedirect:(CDVInvokedUrlCommand*)command {
|
||||
CDVPluginResult* pluginResult = nil;
|
||||
bool allow = [[command.arguments objectAtIndex:0] boolValue];
|
||||
|
||||
[HttpManager sharedClient].securityPolicy.allowInvalidCertificates = allow;
|
||||
|
||||
bool disable = [[command.arguments objectAtIndex:0] boolValue];
|
||||
|
||||
redirect = !disable;
|
||||
|
||||
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
|
||||
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
|
||||
}
|
||||
|
||||
- (void)post:(CDVInvokedUrlCommand*)command {
|
||||
HttpManager *manager = [HttpManager sharedClient];
|
||||
NSString *url = [command.arguments objectAtIndex:0];
|
||||
NSDictionary *parameters = [command.arguments objectAtIndex:1];
|
||||
NSDictionary *headers = [command.arguments objectAtIndex:2];
|
||||
[self setRequestHeaders: headers];
|
||||
|
||||
CordovaHttpPlugin* __weak weakSelf = self;
|
||||
manager.responseSerializer = [TextResponseSerializer serializer];
|
||||
[manager POST:url parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
|
||||
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
|
||||
[dictionary setObject:[NSNumber numberWithInt:operation.response.statusCode] forKey:@"status"];
|
||||
[dictionary setObject:responseObject forKey:@"data"];
|
||||
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary];
|
||||
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
|
||||
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
|
||||
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
|
||||
[dictionary setObject:[NSNumber numberWithInt:operation.response.statusCode] forKey:@"status"];
|
||||
[dictionary setObject:[error localizedDescription] forKey:@"error"];
|
||||
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary];
|
||||
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
|
||||
}];
|
||||
- (void)get:(CDVInvokedUrlCommand*)command {
|
||||
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
|
||||
manager.securityPolicy = securityPolicy;
|
||||
|
||||
NSString *url = [command.arguments objectAtIndex:0];
|
||||
NSDictionary *headers = [command.arguments objectAtIndex:1];
|
||||
NSTimeInterval timeoutInSeconds = [[command.arguments objectAtIndex:2] 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];
|
||||
[[SDNetworkActivityIndicator sharedActivityIndicator] startActivity];
|
||||
|
||||
@try {
|
||||
[manager GET:url parameters: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];
|
||||
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
|
||||
} 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];
|
||||
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
|
||||
}];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
|
||||
[self handleException:exception withCommand:command];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)get:(CDVInvokedUrlCommand*)command {
|
||||
HttpManager *manager = [HttpManager sharedClient];
|
||||
NSString *url = [command.arguments objectAtIndex:0];
|
||||
NSDictionary *parameters = [command.arguments objectAtIndex:1];
|
||||
NSDictionary *headers = [command.arguments objectAtIndex:2];
|
||||
[self setRequestHeaders: headers];
|
||||
|
||||
CordovaHttpPlugin* __weak weakSelf = self;
|
||||
|
||||
manager.responseSerializer = [TextResponseSerializer serializer];
|
||||
[manager GET:url parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
|
||||
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
|
||||
[dictionary setObject:[NSNumber numberWithInt:operation.response.statusCode] forKey:@"status"];
|
||||
[dictionary setObject:responseObject forKey:@"data"];
|
||||
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary];
|
||||
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
|
||||
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
|
||||
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
|
||||
[dictionary setObject:[NSNumber numberWithInt:operation.response.statusCode] forKey:@"status"];
|
||||
[dictionary setObject:[error localizedDescription] forKey:@"error"];
|
||||
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary];
|
||||
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
|
||||
}];
|
||||
- (void)head:(CDVInvokedUrlCommand*)command {
|
||||
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
|
||||
manager.securityPolicy = securityPolicy;
|
||||
|
||||
NSString *url = [command.arguments objectAtIndex:0];
|
||||
NSDictionary *headers = [command.arguments objectAtIndex:1];
|
||||
NSTimeInterval timeoutInSeconds = [[command.arguments objectAtIndex:2] doubleValue];
|
||||
|
||||
[self setRequestHeaders: headers forManager: manager];
|
||||
[self setTimeout:timeoutInSeconds forManager:manager];
|
||||
[self setRedirect: manager];
|
||||
|
||||
CordovaHttpPlugin* __weak weakSelf = self;
|
||||
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
|
||||
[[SDNetworkActivityIndicator sharedActivityIndicator] startActivity];
|
||||
|
||||
@try {
|
||||
[manager HEAD:url parameters:nil 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];
|
||||
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
|
||||
} 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];
|
||||
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
|
||||
}];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
|
||||
[self handleException:exception withCommand:command];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)delete:(CDVInvokedUrlCommand*)command {
|
||||
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
|
||||
manager.securityPolicy = securityPolicy;
|
||||
|
||||
NSString *url = [command.arguments objectAtIndex:0];
|
||||
NSDictionary *headers = [command.arguments objectAtIndex:1];
|
||||
NSTimeInterval timeoutInSeconds = [[command.arguments objectAtIndex:2] 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];
|
||||
[[SDNetworkActivityIndicator sharedActivityIndicator] startActivity];
|
||||
|
||||
@try {
|
||||
[manager DELETE:url parameters: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];
|
||||
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
|
||||
} 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];
|
||||
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
|
||||
}];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
|
||||
[self handleException:exception withCommand:command];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)post:(CDVInvokedUrlCommand*)command {
|
||||
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
|
||||
manager.securityPolicy = securityPolicy;
|
||||
|
||||
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];
|
||||
|
||||
[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];
|
||||
[[SDNetworkActivityIndicator sharedActivityIndicator] startActivity];
|
||||
|
||||
@try {
|
||||
[manager POST:url parameters:data 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];
|
||||
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
|
||||
} 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];
|
||||
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
|
||||
}];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
|
||||
[self handleException:exception withCommand:command];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)put:(CDVInvokedUrlCommand*)command {
|
||||
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
|
||||
manager.securityPolicy = securityPolicy;
|
||||
|
||||
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];
|
||||
|
||||
[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];
|
||||
[[SDNetworkActivityIndicator sharedActivityIndicator] startActivity];
|
||||
|
||||
@try {
|
||||
[manager PUT:url parameters:data 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];
|
||||
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
|
||||
} 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];
|
||||
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
|
||||
}];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
|
||||
[self handleException:exception withCommand:command];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)patch:(CDVInvokedUrlCommand*)command {
|
||||
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
|
||||
manager.securityPolicy = securityPolicy;
|
||||
|
||||
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];
|
||||
|
||||
[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];
|
||||
[[SDNetworkActivityIndicator sharedActivityIndicator] startActivity];
|
||||
|
||||
@try {
|
||||
[manager PATCH:url parameters:data 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];
|
||||
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
|
||||
} 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];
|
||||
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
|
||||
}];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
|
||||
[self handleException:exception withCommand:command];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)uploadFile:(CDVInvokedUrlCommand*)command {
|
||||
HttpManager *manager = [HttpManager sharedClient];
|
||||
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];
|
||||
|
||||
NSURL *fileURL = [NSURL fileURLWithPath: filePath];
|
||||
|
||||
[self setRequestHeaders: headers];
|
||||
|
||||
NSDictionary *headers = [command.arguments objectAtIndex:1];
|
||||
NSString *filePath = [command.arguments objectAtIndex: 2];
|
||||
NSString *name = [command.arguments objectAtIndex: 3];
|
||||
NSTimeInterval timeoutInSeconds = [[command.arguments objectAtIndex:4] doubleValue];
|
||||
|
||||
NSURL *fileURL = [NSURL URLWithString: filePath];
|
||||
|
||||
[self setRequestHeaders: headers forManager: manager];
|
||||
[self setTimeout:timeoutInSeconds forManager:manager];
|
||||
[self setRedirect: manager];
|
||||
|
||||
CordovaHttpPlugin* __weak weakSelf = self;
|
||||
manager.responseSerializer = [TextResponseSerializer serializer];
|
||||
[manager POST:url parameters:parameters constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
|
||||
NSError *error;
|
||||
[formData appendPartWithFileURL:fileURL name:name error:&error];
|
||||
if (error) {
|
||||
[[SDNetworkActivityIndicator sharedActivityIndicator] startActivity];
|
||||
|
||||
@try {
|
||||
[manager POST:url parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
|
||||
NSError *error;
|
||||
[formData appendPartWithFileURL:fileURL name:name error:&error];
|
||||
if (error) {
|
||||
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) {
|
||||
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
|
||||
[dictionary setObject:[NSNumber numberWithInt:500] forKey:@"status"];
|
||||
[dictionary setObject:@"Could not add image to post body." forKey:@"error"];
|
||||
[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) {
|
||||
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];
|
||||
return;
|
||||
}
|
||||
} success:^(AFHTTPRequestOperation *operation, id responseObject) {
|
||||
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
|
||||
[dictionary setObject:[NSNumber numberWithInt:operation.response.statusCode] forKey:@"status"];
|
||||
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary];
|
||||
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
|
||||
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
|
||||
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
|
||||
[dictionary setObject:[NSNumber numberWithInt:operation.response.statusCode] forKey:@"status"];
|
||||
[dictionary setObject:[error localizedDescription] forKey:@"error"];
|
||||
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary];
|
||||
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
|
||||
}];
|
||||
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
|
||||
}];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
|
||||
[self handleException:exception withCommand:command];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
- (void)downloadFile:(CDVInvokedUrlCommand*)command {
|
||||
HttpManager *manager = [HttpManager sharedClient];
|
||||
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];
|
||||
|
||||
[self setRequestHeaders: headers];
|
||||
|
||||
NSDictionary *headers = [command.arguments objectAtIndex:1];
|
||||
NSString *filePath = [command.arguments objectAtIndex: 2];
|
||||
NSTimeInterval timeoutInSeconds = [[command.arguments objectAtIndex:3] doubleValue];
|
||||
|
||||
[self setRequestHeaders: headers forManager: manager];
|
||||
[self setTimeout:timeoutInSeconds forManager:manager];
|
||||
[self setRedirect: manager];
|
||||
|
||||
if ([filePath hasPrefix:@"file://"]) {
|
||||
filePath = [filePath substringFromIndex:7];
|
||||
}
|
||||
|
||||
CordovaHttpPlugin* __weak weakSelf = self;
|
||||
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
|
||||
[manager GET:url parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
|
||||
/*
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Modified by Andrew Stephan for Sync OnSet
|
||||
*
|
||||
*/
|
||||
// Download response is okay; begin streaming output to file
|
||||
NSString* parentPath = [filePath stringByDeletingLastPathComponent];
|
||||
|
||||
// create parent directories if needed
|
||||
NSError *error;
|
||||
if ([[NSFileManager defaultManager] createDirectoryAtPath:parentPath withIntermediateDirectories:YES attributes:nil error:&error] == NO) {
|
||||
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
|
||||
[dictionary setObject:[NSNumber numberWithInt:500] forKey:@"status"];
|
||||
if (error) {
|
||||
[dictionary setObject:[NSString stringWithFormat:@"Could not create path to save downloaded file: %@", [error localizedDescription]] forKey:@"error"];
|
||||
} else {
|
||||
[dictionary setObject:@"Could not create path to save downloaded file" forKey:@"error"];
|
||||
[[SDNetworkActivityIndicator sharedActivityIndicator] startActivity];
|
||||
|
||||
@try {
|
||||
[manager GET:url parameters:nil success:^(NSURLSessionTask *task, id responseObject) {
|
||||
/*
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Modified by Andrew Stephan for Sync OnSet
|
||||
*
|
||||
*/
|
||||
// Download response is okay; begin streaming output to file
|
||||
NSString* parentPath = [filePath stringByDeletingLastPathComponent];
|
||||
|
||||
// create parent directories if needed
|
||||
NSError *error;
|
||||
if ([[NSFileManager defaultManager] createDirectoryAtPath:parentPath withIntermediateDirectories:YES attributes:nil error:&error] == NO) {
|
||||
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
|
||||
[dictionary setObject:[NSNumber numberWithInt:500] forKey:@"status"];
|
||||
if (error) {
|
||||
[dictionary setObject:[NSString stringWithFormat:@"Could not create path to save downloaded file: %@", [error localizedDescription]] forKey:@"error"];
|
||||
} else {
|
||||
[dictionary setObject:@"Could not create path to save downloaded file" forKey:@"error"];
|
||||
}
|
||||
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary];
|
||||
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
|
||||
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
|
||||
return;
|
||||
}
|
||||
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary];
|
||||
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
|
||||
return;
|
||||
}
|
||||
NSData *data = (NSData *)responseObject;
|
||||
if (![data writeToFile:filePath atomically:YES]) {
|
||||
NSData *data = (NSData *)responseObject;
|
||||
if (![data writeToFile:filePath atomically:YES]) {
|
||||
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
|
||||
[dictionary setObject:[NSNumber numberWithInt:500] forKey:@"status"];
|
||||
[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;
|
||||
}
|
||||
|
||||
id filePlugin = [self.commandDelegate getCommandInstance:@"File"];
|
||||
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
|
||||
[dictionary setObject:[NSNumber numberWithInt:500] forKey:@"status"];
|
||||
[dictionary setObject:@"Could not write the data to the given filePath." forKey:@"error"];
|
||||
[self handleSuccess:dictionary withResponse:(NSHTTPURLResponse*)task.response andData:nil];
|
||||
[dictionary setObject:[filePlugin getDirectoryEntry:filePath isDirectory:NO] forKey:@"file"];
|
||||
|
||||
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary];
|
||||
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
|
||||
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
|
||||
} failure:^(NSURLSessionTask *task, NSError *error) {
|
||||
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];
|
||||
return;
|
||||
}
|
||||
|
||||
CDVFile *file = [[CDVFile alloc] init];
|
||||
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
|
||||
[dictionary setObject:[NSNumber numberWithInt:operation.response.statusCode] forKey:@"status"];
|
||||
[dictionary setObject:[file getDirectoryEntry:filePath isDirectory:NO] forKey:@"file"];
|
||||
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary];
|
||||
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
|
||||
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
|
||||
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
|
||||
[dictionary setObject:[NSNumber numberWithInt:operation.response.statusCode] forKey:@"status"];
|
||||
[dictionary setObject:[error localizedDescription] forKey:@"error"];
|
||||
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary];
|
||||
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
|
||||
}];
|
||||
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
|
||||
}];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
|
||||
[self handleException:exception withCommand:command];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
// Copyright (c) 2012 Mattt Thompson (http://mattt.me/)
|
||||
//
|
||||
// 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.
|
||||
// Modified by Andrew Stephan
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "AFHTTPRequestOperationManager.h"
|
||||
|
||||
@interface HttpManager : AFHTTPRequestOperationManager
|
||||
|
||||
+ (instancetype)sharedClient;
|
||||
|
||||
@end
|
||||
@@ -1,35 +0,0 @@
|
||||
// Copyright (c) 2012 Mattt Thompson (http://mattt.me/)
|
||||
//
|
||||
// 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.
|
||||
// Modified by Andrew Stephan
|
||||
#import "HttpManager.h"
|
||||
|
||||
@implementation HttpManager
|
||||
|
||||
+ (instancetype)sharedClient {
|
||||
static HttpManager *_sharedClient = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
_sharedClient = [HttpManager manager];
|
||||
});
|
||||
|
||||
return _sharedClient;
|
||||
}
|
||||
|
||||
@end
|
||||
20
src/ios/SDNetworkActivityIndicator/LICENSE
Normal file
20
src/ios/SDNetworkActivityIndicator/LICENSE
Normal 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.
|
||||
|
||||
52
src/ios/SDNetworkActivityIndicator/README.md
Normal file
52
src/ios/SDNetworkActivityIndicator/README.md
Normal 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.
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
8
src/ios/TextRequestSerializer.h
Normal file
8
src/ios/TextRequestSerializer.h
Normal file
@@ -0,0 +1,8 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "AFURLRequestSerialization.h"
|
||||
|
||||
@interface TextRequestSerializer : AFHTTPRequestSerializer
|
||||
|
||||
+ (instancetype)serializer;
|
||||
|
||||
@end
|
||||
53
src/ios/TextRequestSerializer.m
Normal file
53
src/ios/TextRequestSerializer.m
Normal file
@@ -0,0 +1,53 @@
|
||||
#import "TextRequestSerializer.h"
|
||||
|
||||
@implementation TextRequestSerializer
|
||||
|
||||
+ (instancetype)serializer
|
||||
{
|
||||
TextRequestSerializer *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:@"text/plain; charset=utf-8" forHTTPHeaderField:@"Content-Type"];
|
||||
}
|
||||
|
||||
[mutableRequest setHTTPBody: [[parameters valueForKey:@"text"] dataUsingEncoding:NSUTF8StringEncoding]];
|
||||
}
|
||||
|
||||
return mutableRequest;
|
||||
}
|
||||
|
||||
#pragma mark - NSSecureCoding
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)decoder {
|
||||
self = [super initWithCoder:decoder];
|
||||
if (!self) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -5,4 +5,6 @@
|
||||
|
||||
+ (instancetype)serializer;
|
||||
|
||||
@end
|
||||
FOUNDATION_EXPORT NSString * const AFNetworkingOperationFailingURLResponseBodyKey;
|
||||
|
||||
@end
|
||||
|
||||
@@ -1,31 +1,126 @@
|
||||
#import "TextResponseSerializer.h"
|
||||
|
||||
static BOOL AFErrorOrUnderlyingErrorHasCode(NSError *error, NSInteger code) {
|
||||
if (error.code == code) {
|
||||
return YES;
|
||||
} else if (error.userInfo[NSUnderlyingErrorKey]) {
|
||||
return AFErrorOrUnderlyingErrorHasCode(error.userInfo[NSUnderlyingErrorKey], code);
|
||||
}
|
||||
NSString * const AFNetworkingOperationFailingURLResponseBodyKey = @"com.alamofire.serialization.response.error.body";
|
||||
NSStringEncoding const SupportedEncodings[6] = { NSUTF8StringEncoding, NSWindowsCP1252StringEncoding, NSISOLatin1StringEncoding, NSISOLatin2StringEncoding, NSASCIIStringEncoding, NSUnicodeStringEncoding };
|
||||
|
||||
return NO;
|
||||
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 TextResponseSerializer
|
||||
|
||||
+ (instancetype)serializer {
|
||||
TextResponseSerializer *serializer = [[self alloc] init];
|
||||
return serializer;
|
||||
TextResponseSerializer *serializer = [[self alloc] init];
|
||||
return serializer;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (!self) {
|
||||
return nil;
|
||||
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);
|
||||
}
|
||||
|
||||
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
|
||||
decoded:(NSString **)decoded
|
||||
error:(NSError * __autoreleasing *)error
|
||||
{
|
||||
BOOL responseIsValid = YES;
|
||||
NSError *validationError = nil;
|
||||
|
||||
if (response && [response isKindOfClass:[NSHTTPURLResponse class]]) {
|
||||
if (data) {
|
||||
*decoded = [self decodeResponseData:data withEncoding:[self getEncoding:response]];
|
||||
}
|
||||
|
||||
self.acceptableContentTypes = [NSSet setWithObjects:@"text/plain", @"text/html", @"text/json", @"application/json", @"text/xml", @"application/xml", @"text/css", nil];
|
||||
if (data && !*decoded) {
|
||||
NSMutableDictionary *mutableUserInfo = [@{
|
||||
NSURLErrorFailingURLErrorKey:[response URL],
|
||||
AFNetworkingOperationFailingURLResponseErrorKey: response,
|
||||
AFNetworkingOperationFailingURLResponseDataErrorKey: data,
|
||||
AFNetworkingOperationFailingURLResponseBodyKey: @"Could not decode response data due to invalid or unknown charset encoding",
|
||||
} mutableCopy];
|
||||
|
||||
return self;
|
||||
validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorBadServerResponse userInfo:mutableUserInfo], validationError);
|
||||
responseIsValid = NO;
|
||||
} else 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;
|
||||
mutableUserInfo[AFNetworkingOperationFailingURLResponseBodyKey] = *decoded;
|
||||
}
|
||||
|
||||
validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorBadServerResponse userInfo:mutableUserInfo], validationError);
|
||||
responseIsValid = NO;
|
||||
}
|
||||
}
|
||||
|
||||
if (error && !responseIsValid) {
|
||||
*error = validationError;
|
||||
}
|
||||
|
||||
return responseIsValid;
|
||||
}
|
||||
|
||||
#pragma mark - AFURLResponseSerialization
|
||||
@@ -34,25 +129,15 @@ static BOOL AFErrorOrUnderlyingErrorHasCode(NSError *error, NSInteger code) {
|
||||
data:(NSData *)data
|
||||
error:(NSError *__autoreleasing *)error
|
||||
{
|
||||
if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) {
|
||||
if (AFErrorOrUnderlyingErrorHasCode(*error, NSURLErrorCannotDecodeContentData)) {
|
||||
return nil;
|
||||
}
|
||||
NSString* decoded = nil;
|
||||
|
||||
if (![self validateResponse:(NSHTTPURLResponse *)response data:data decoded:&decoded error:error]) {
|
||||
if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
// Workaround for behavior of Rails to return a single space for `head :ok` (a workaround for a bug in Safari), which is not interpreted as valid input by NSJSONSerialization.
|
||||
// See https://github.com/rails/rails/issues/1742
|
||||
NSStringEncoding stringEncoding = self.stringEncoding;
|
||||
if (response.textEncodingName) {
|
||||
CFStringEncoding encoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName);
|
||||
if (encoding != kCFStringEncodingInvalidId) {
|
||||
stringEncoding = CFStringConvertEncodingToNSStringEncoding(encoding);
|
||||
}
|
||||
}
|
||||
|
||||
NSString *responseString = [[NSString alloc] initWithData:data encoding:stringEncoding];
|
||||
|
||||
return responseString;
|
||||
return decoded;
|
||||
}
|
||||
|
||||
@end
|
||||
@end
|
||||
|
||||
14
test/e2e-app-template/build.json
Normal file
14
test/e2e-app-template/build.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"ios": {
|
||||
"debug": {
|
||||
"buildFlag": [
|
||||
"-UseModernBuildSystem=0"
|
||||
]
|
||||
},
|
||||
"release": {
|
||||
"buildFlag": [
|
||||
"-UseModernBuildSystem=0"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
30
test/e2e-app-template/config.xml
Normal file
30
test/e2e-app-template/config.xml
Normal file
@@ -0,0 +1,30 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<widget id="com.ilkimen.http.demo" version="1.0.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
|
||||
<name>HttpDemo</name>
|
||||
<description>
|
||||
A sample Apache Cordova application that demonstrates advanced HTTP plugin.
|
||||
</description>
|
||||
<author email="dev@cordova.apache.org" href="http://cordova.io">
|
||||
Sefa Ilkimen
|
||||
</author>
|
||||
<content src="index.html" />
|
||||
<access origin="*" />
|
||||
<allow-intent href="http://*/*" />
|
||||
<allow-intent href="https://*/*" />
|
||||
<allow-intent href="tel:*" />
|
||||
<allow-intent href="sms:*" />
|
||||
<allow-intent href="mailto:*" />
|
||||
<allow-intent href="geo:*" />
|
||||
<platform name="android">
|
||||
<allow-intent href="market:*" />
|
||||
</platform>
|
||||
<platform name="ios">
|
||||
<allow-intent href="itms:*" />
|
||||
<allow-intent href="itms-apps:*" />
|
||||
</platform>
|
||||
<engine name="android" spec="7.1.0" />
|
||||
<engine name="browser" spec="5.0.0" />
|
||||
<engine name="ios" spec="4.4.0" />
|
||||
<plugin name="cordova-plugin-file" spec="6.0.1" />
|
||||
<preference name="AndroidPersistentFileLocation" value="Internal" />
|
||||
</widget>
|
||||
26
test/e2e-app-template/package.json
Normal file
26
test/e2e-app-template/package.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"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": "7.1.0",
|
||||
"cordova-browser": "5.0.0",
|
||||
"cordova-ios": "4.4.0"
|
||||
},
|
||||
"cordova": {
|
||||
"platforms": [
|
||||
"android",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
"devDependencies": {}
|
||||
}
|
||||
0
test/e2e-app-template/www/certificates/.gitkeep
Normal file
0
test/e2e-app-template/www/certificates/.gitkeep
Normal file
34
test/e2e-app-template/www/index.css
Normal file
34
test/e2e-app-template/www/index.css
Normal file
@@ -0,0 +1,34 @@
|
||||
* {
|
||||
-webkit-tap-highlight-color: rgba(0,0,0,0); /* make transparent link selection, adjust last value opacity 0 to 1.0 */
|
||||
}
|
||||
|
||||
body {
|
||||
-webkit-touch-callout: none; /* prevent callout to copy image, etc when tap to hold */
|
||||
-webkit-text-size-adjust: none; /* prevent webkit from resizing text to fit */
|
||||
-webkit-user-select: none; /* prevent copy paste, to allow, change 'none' to 'text' */
|
||||
height:100%;
|
||||
margin:0px;
|
||||
padding:20px 0;
|
||||
width:100%;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
button, input, textarea {
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
button {
|
||||
height: 2.5em;
|
||||
font-size: 10pt;
|
||||
}
|
||||
|
||||
input {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 12pt;
|
||||
text-align: center;
|
||||
}
|
||||
21
test/e2e-app-template/www/index.html
Normal file
21
test/e2e-app-template/www/index.html
Normal file
@@ -0,0 +1,21 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<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:;">
|
||||
<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">
|
||||
<link rel="stylesheet" type="text/css" href="index.css">
|
||||
</head>
|
||||
<body>
|
||||
<h1 id="descriptionLbl">Advanced HTTP test suite</h1>
|
||||
<input value="idle" id="statusInput" readonly></input>
|
||||
<textarea rows="16" cols="50" id="expectedTextarea" readonly>Click next to run first test.</textarea>
|
||||
<textarea rows="16" cols="50" id="resultTextarea" readonly></textarea>
|
||||
<button id="nextBtn">Run next test</button>
|
||||
|
||||
<script type="text/javascript" src="cordova.js"></script>
|
||||
<script type="text/javascript" src="e2e-specs.js"></script>
|
||||
<script type="text/javascript" src="index.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
107
test/e2e-app-template/www/index.js
Normal file
107
test/e2e-app-template/www/index.js
Normal file
@@ -0,0 +1,107 @@
|
||||
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();
|
||||
598
test/e2e-specs.js
Normal file
598
test/e2e-specs.js
Normal file
@@ -0,0 +1,598 @@
|
||||
const hooks = {
|
||||
onBeforeEachTest: function(done) {
|
||||
cordova.plugin.http.clearCookies();
|
||||
helpers.setDefaultServerTrustMode(done);
|
||||
}
|
||||
};
|
||||
|
||||
const helpers = {
|
||||
setDefaultServerTrustMode: function(done) { cordova.plugin.http.setServerTrustMode('default', done, done); },
|
||||
setNoCheckServerTrustMode: function(done) { cordova.plugin.http.setServerTrustMode('nocheck', done, done); },
|
||||
setPinnedServerTrustMode: function(done) { cordova.plugin.http.setServerTrustMode('pinned', 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 messageFactory = {
|
||||
sslTrustAnchor: function() { return 'TLS connection could not be established: javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.' },
|
||||
invalidCertificate: function(domain) { return 'The certificate for this server is invalid. You might be connecting to a server that is pretending to be “' + domain + '” which could put your confidential information at risk.' }
|
||||
}
|
||||
|
||||
const tests = [
|
||||
{
|
||||
description: 'should reject self signed cert (GET)',
|
||||
expected: 'rejected: {"status":-2, ...',
|
||||
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: -2, error: targetInfo.isAndroid ? messageFactory.sslTrustAnchor() : messageFactory.invalidCertificate('self-signed.badssl.com') });
|
||||
}
|
||||
},
|
||||
{
|
||||
description: 'should reject self signed cert (PUT)',
|
||||
expected: 'rejected: {"status":-2, ...',
|
||||
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: -2, error: targetInfo.isAndroid ? messageFactory.sslTrustAnchor() : messageFactory.invalidCertificate('self-signed.badssl.com') });
|
||||
}
|
||||
},
|
||||
{
|
||||
description: 'should reject self signed cert (POST)',
|
||||
expected: 'rejected: {"status":-2, ...',
|
||||
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: -2, error: targetInfo.isAndroid ? messageFactory.sslTrustAnchor() : messageFactory.invalidCertificate('self-signed.badssl.com') });
|
||||
}
|
||||
},
|
||||
{
|
||||
description: 'should reject self signed cert (PATCH)',
|
||||
expected: 'rejected: {"status":-2, ...',
|
||||
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: -2, error: targetInfo.isAndroid ? messageFactory.sslTrustAnchor() : messageFactory.invalidCertificate('self-signed.badssl.com') });
|
||||
}
|
||||
},
|
||||
{
|
||||
description: 'should reject self signed cert (DELETE)',
|
||||
expected: 'rejected: {"status":-2, ...',
|
||||
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: -2, error: targetInfo.isAndroid ? messageFactory.sslTrustAnchor() : messageFactory.invalidCertificate('self-signed.badssl.com') });
|
||||
}
|
||||
},
|
||||
{
|
||||
description: 'should accept bad cert (GET)',
|
||||
expected: 'resolved: {"status":200, ...',
|
||||
before: helpers.setNoCheckServerTrustMode,
|
||||
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.setNoCheckServerTrustMode,
|
||||
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.setNoCheckServerTrustMode,
|
||||
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.setNoCheckServerTrustMode,
|
||||
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.setNoCheckServerTrustMode,
|
||||
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.setNoCheckServerTrustMode,
|
||||
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.include('httpbin.org/get?myArray[]=val1&myArray[]=val2&myArray[]=val3&myString=testString');
|
||||
}
|
||||
},
|
||||
{
|
||||
description: 'should throw on non-string values in local header object #54',
|
||||
expected: 'throwed: {"message": "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('throwed');
|
||||
result.message.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
|
||||
.should.not.have.property('Cookie');
|
||||
}
|
||||
},
|
||||
{
|
||||
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.setPinnedServerTrustMode,
|
||||
func: function(resolve, reject) {
|
||||
cordova.plugin.http.get('https://httpbin.org', {}, {}, resolve, reject);
|
||||
},
|
||||
validationFunc: function(driver, result) {
|
||||
result.type.should.be.equal('resolved');
|
||||
result.data.status.should.be.equal(200);
|
||||
}
|
||||
},
|
||||
{
|
||||
description: 'should reject when pinned cert does not match received server cert (GET)',
|
||||
expected: 'rejected: {"status": -2 ...',
|
||||
before: helpers.setPinnedServerTrustMode,
|
||||
func: function(resolve, reject) {
|
||||
cordova.plugin.http.get('https://sha512.badssl.com/', {}, {}, resolve, reject);
|
||||
},
|
||||
validationFunc: function(driver, result, targetInfo) {
|
||||
result.type.should.be.equal('rejected');
|
||||
result.data.should.be.eql({ status: -2, error: targetInfo.isAndroid ? messageFactory.sslTrustAnchor() : messageFactory.invalidCertificate('sha512.badssl.com') });
|
||||
}
|
||||
},
|
||||
{
|
||||
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');
|
||||
}
|
||||
},
|
||||
{
|
||||
description: 'should handle gzip encoded response correctly',
|
||||
expected: 'resolved: {"status": 200, "headers": "{\\"Content-Encoding\\": \\"gzip\\" ...',
|
||||
func: function(resolve, reject) { cordova.plugin.http.get('http://httpbin.org/gzip', {}, {}, resolve, reject); },
|
||||
validationFunc: function(driver, result) {
|
||||
result.type.should.be.equal('resolved');
|
||||
result.data.status.should.be.equal(200);
|
||||
JSON.parse(result.data.data).gzipped.should.be.equal(true);
|
||||
}
|
||||
},
|
||||
{
|
||||
description: 'should send empty string correctly',
|
||||
expected: 'resolved: {"status": 200, "data": "{\\"json\\":\\"\\" ...',
|
||||
before: helpers.setUtf8StringSerializer,
|
||||
func: function(resolve, reject) { cordova.plugin.http.post('http://httpbin.org/anything', '', {}, resolve, reject); },
|
||||
validationFunc: function(driver, result) {
|
||||
result.type.should.be.equal('resolved');
|
||||
JSON.parse(result.data.data).data.should.be.equal('');
|
||||
}
|
||||
},
|
||||
{
|
||||
description: 'shouldn\'t escape forward slashes #184',
|
||||
expected: 'resolved: {"status": 200, "data": "{\\"json\\":\\"/\\" ...',
|
||||
before: helpers.setJsonSerializer,
|
||||
func: function(resolve, reject) { cordova.plugin.http.post('http://httpbin.org/anything', { testString: '/' }, {}, resolve, reject); },
|
||||
validationFunc: function(driver, result) {
|
||||
result.type.should.be.equal('resolved');
|
||||
JSON.parse(result.data.data).json.testString.should.be.equal('/');
|
||||
}
|
||||
},
|
||||
{
|
||||
description: 'should not double encode spaces in url path #195',
|
||||
expected: 'resolved: {"status": 200, "data": "{\\"url\\":\\"https://httpbin.org/anything/containing spaces in url\\" ...',
|
||||
func: function(resolve, reject) { cordova.plugin.http.get('https://httpbin.org/anything/containing%20spaces%20in%20url', {}, {}, resolve, reject); },
|
||||
validationFunc: function(driver, result) {
|
||||
result.type.should.be.equal('resolved');
|
||||
JSON.parse(result.data.data).url.should.be.equal('https://httpbin.org/anything/containing spaces in url');
|
||||
}
|
||||
},
|
||||
{
|
||||
description: 'should encode spaces in url query correctly',
|
||||
expected: 'resolved: {"status": 200, "data": "{\\"url\\":\\"https://httpbin.org/anything?query key=very long query value with spaces\\" ...',
|
||||
func: function(resolve, reject) { cordova.plugin.http.get('https://httpbin.org/anything', { 'query key': 'very long query value with spaces' }, {}, resolve, reject); },
|
||||
validationFunc: function(driver, result) {
|
||||
result.type.should.be.equal('resolved');
|
||||
JSON.parse(result.data.data).url.should.be.equal('https://httpbin.org/anything?query key=very long query value with spaces');
|
||||
}
|
||||
},
|
||||
{
|
||||
description: 'should download a file from given HTTPS URL to given path in local filesystem #197',
|
||||
expected: 'resolved: {"content": "<?xml version=\'1.0\' encoding=\'us-ascii\'?>\\n\\n<!-- A SAMPLE set of slides -->" ...',
|
||||
func: function(resolve, reject) {
|
||||
var sourceUrl = 'https://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>");
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = { tests: tests, hooks: hooks };
|
||||
}
|
||||
10
test/e2e-tooling/helpers/apps.js
Normal file
10
test/e2e-tooling/helpers/apps.js
Normal file
@@ -0,0 +1,10 @@
|
||||
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/app/build/outputs/apk/debug/app-debug.apk');
|
||||
}
|
||||
60
test/e2e-tooling/helpers/caps.js
Normal file
60
test/e2e-tooling/helpers/caps.js
Normal file
@@ -0,0 +1,60 @@
|
||||
const local = {
|
||||
iosDevice: {
|
||||
platformName: 'iOS',
|
||||
platformVersion: '10.3',
|
||||
deviceName: 'iPhone 6',
|
||||
autoWebview: true,
|
||||
app: undefined // will be set later
|
||||
},
|
||||
iosEmulator: {
|
||||
platformName: 'iOS',
|
||||
platformVersion: '11.0',
|
||||
deviceName: 'iPhone Simulator',
|
||||
autoWebview: true,
|
||||
app: undefined // will be set later
|
||||
},
|
||||
androidEmulator: {
|
||||
platformName: 'Android',
|
||||
platformVersion: '5.1',
|
||||
deviceName: 'Android Emulator',
|
||||
autoWebview: true,
|
||||
fullReset: 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;
|
||||
}
|
||||
13
test/e2e-tooling/helpers/logging.js
Normal file
13
test/e2e-tooling/helpers/logging.js
Normal file
@@ -0,0 +1,13 @@
|
||||
exports.configure = driver => {
|
||||
driver.on('status', info => {
|
||||
console.log(info.cyan);
|
||||
});
|
||||
|
||||
driver.on('command', (meth, path, data) => {
|
||||
console.log(' > ' + meth.yellow, path.grey, data || '');
|
||||
});
|
||||
|
||||
driver.on('http', (meth, path, data) => {
|
||||
console.log(' > ' + meth.magenta, path, (data || '').grey);
|
||||
});
|
||||
};
|
||||
16
test/e2e-tooling/helpers/server.js
Normal file
16
test/e2e-tooling/helpers/server.js
Normal file
@@ -0,0 +1,16 @@
|
||||
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;
|
||||
}
|
||||
12
test/e2e-tooling/helpers/setup.js
Normal file
12
test/e2e-tooling/helpers/setup.js
Normal file
@@ -0,0 +1,12 @@
|
||||
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;
|
||||
94
test/e2e-tooling/test.js
Normal file
94
test/e2e-tooling/test.js
Normal file
@@ -0,0 +1,94 @@
|
||||
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('../e2e-specs');
|
||||
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(index + ': ' + definition.description, function() {
|
||||
return clickNext()
|
||||
.then(() => validateTestIndex(index))
|
||||
.then(() => validateTestTitle(definition.description))
|
||||
.then(() => waitToBeFinished(definition.timeout || 10000))
|
||||
.then(() => validateResult(definition))
|
||||
});
|
||||
});
|
||||
});
|
||||
264
test/js-specs.js
Normal file
264
test/js-specs.js
Normal file
@@ -0,0 +1,264 @@
|
||||
const chai = require('chai');
|
||||
const mock = require('mock-require');
|
||||
const should = chai.should();
|
||||
|
||||
describe('Advanced HTTP public interface', function () {
|
||||
let http = {};
|
||||
|
||||
const noop = () => { /* intentionally doing nothing */ };
|
||||
|
||||
const getDependenciesBlueprint = () => {
|
||||
const messages = require('../www/messages');
|
||||
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 WebStorageCookieStore = require('../www/local-storage-store')(ToughCookie, lodash);
|
||||
const cookieHandler = require('../www/cookie-handler')(null, ToughCookie, WebStorageCookieStore);
|
||||
const helpers = require('../www/helpers')(jsUtil, cookieHandler, messages);
|
||||
const urlUtil = require('../www/url-util')(jsUtil);
|
||||
|
||||
return { exec: noop, cookieHandler, urlUtil: urlUtil, helpers, globalConfigs };
|
||||
};
|
||||
|
||||
const loadHttp = (deps) => {
|
||||
http = require('../www/public-interface')(deps.exec, deps.cookieHandler, deps.urlUtil, deps.helpers, deps.globalConfigs);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
// mocked btoa function (base 64 encoding strings)
|
||||
global.btoa = decoded => new Buffer(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('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', () => {
|
||||
(function () { http.setHeader('*', 'cookie', 'value') }).should.throw();
|
||||
});
|
||||
});
|
||||
|
||||
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¶m%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¶m1=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¶m1=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);
|
||||
|
||||
it('merges empty header sets correctly', () => {
|
||||
helpers.mergeHeaders({}, {}).should.eql({});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getCookieHeader(url)', function () {
|
||||
it('resolves cookie header correctly when no cookie is set #198', () => {
|
||||
const helpers = require('../www/helpers')(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, { getCookieString: () => 'cookie=value' }, null);
|
||||
|
||||
helpers.getCookieHeader('http://ilkimen.net').should.eql({ Cookie: 'cookie=value' });
|
||||
});
|
||||
});
|
||||
})
|
||||
19
www/advanced-http.js
Normal file
19
www/advanced-http.js
Normal file
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* A native HTTP Plugin for Cordova / PhoneGap.
|
||||
*/
|
||||
|
||||
var pluginId = module.id.slice(0, module.id.lastIndexOf('.'));
|
||||
|
||||
var exec = require('cordova/exec');
|
||||
var messages = require(pluginId + '.messages');
|
||||
var globalConfigs = require(pluginId + '.global-configs');
|
||||
var jsUtil = require(pluginId + '.js-util');
|
||||
var ToughCookie = require(pluginId + '.tough-cookie');
|
||||
var lodash = require(pluginId + '.lodash');
|
||||
var WebStorageCookieStore = require(pluginId + '.local-storage-store')(ToughCookie, lodash);
|
||||
var cookieHandler = require(pluginId + '.cookie-handler')(window.localStorage, ToughCookie, WebStorageCookieStore);
|
||||
var helpers = require(pluginId + '.helpers')(jsUtil, cookieHandler, messages);
|
||||
var urlUtil = require(pluginId + '.url-util')(jsUtil);
|
||||
var publicInterface = require(pluginId + '.public-interface')(exec, cookieHandler, urlUtil, helpers, globalConfigs);
|
||||
|
||||
module.exports = publicInterface;
|
||||
70
www/cookie-handler.js
Normal file
70
www/cookie-handler.js
Normal file
@@ -0,0 +1,70 @@
|
||||
module.exports = function init(storage, ToughCookie, WebStorageCookieStore) {
|
||||
var storeKey = '__advancedHttpCookieStore__';
|
||||
|
||||
var store = new WebStorageCookieStore(storage, storeKey);
|
||||
var cookieJar = new ToughCookie.CookieJar(store);
|
||||
|
||||
return {
|
||||
setCookieFromString: setCookieFromString,
|
||||
setCookie: setCookie,
|
||||
getCookieString: getCookieString,
|
||||
clearCookies: clearCookies,
|
||||
removeCookies: removeCookies
|
||||
};
|
||||
|
||||
function splitCookieString(cookieStr) {
|
||||
var cookieParts = cookieStr.split(',');
|
||||
var splitCookies = [];
|
||||
var processedCookie = null;
|
||||
|
||||
for (var i = 0; i < cookieParts.length; ++i) {
|
||||
if (cookieParts[i].substr(-11, 8).toLowerCase() === 'expires=') {
|
||||
processedCookie = cookieParts[i] + ',' + cookieParts[i + 1];
|
||||
i++;
|
||||
} else {
|
||||
processedCookie = cookieParts[i];
|
||||
}
|
||||
|
||||
processedCookie = processedCookie.trim();
|
||||
splitCookies.push(processedCookie);
|
||||
}
|
||||
|
||||
return splitCookies;
|
||||
}
|
||||
|
||||
function setCookieFromString(url, cookieStr) {
|
||||
if (!cookieStr) return;
|
||||
|
||||
var cookies = splitCookieString(cookieStr);
|
||||
|
||||
for (var i = 0; i < cookies.length; ++i) {
|
||||
cookieJar.setCookieSync(cookies[i], url, { ignoreError: true });
|
||||
}
|
||||
}
|
||||
|
||||
function setCookie(url, cookie, options) {
|
||||
options = options || {};
|
||||
options.ignoreError = false;
|
||||
cookieJar.setCookieSync(cookie, url, options);
|
||||
}
|
||||
|
||||
function getCookieString(url) {
|
||||
return cookieJar.getCookieStringSync(url);
|
||||
}
|
||||
|
||||
function clearCookies() {
|
||||
window.localStorage.removeItem(storeKey);
|
||||
}
|
||||
|
||||
function removeCookies(url, cb) {
|
||||
cookieJar.getCookies(url, function (error, cookies) {
|
||||
if (!cookies || cookies.length === 0) {
|
||||
return cb(null, []);
|
||||
}
|
||||
|
||||
var domain = cookies[0].domain;
|
||||
|
||||
cookieJar.store.removeCookies(domain, null, cb);
|
||||
});
|
||||
}
|
||||
};
|
||||
131
www/cordovaHTTP.js
vendored
131
www/cordovaHTTP.js
vendored
@@ -1,131 +0,0 @@
|
||||
/*global angular*/
|
||||
|
||||
/*
|
||||
* An HTTP Plugin for PhoneGap.
|
||||
*/
|
||||
|
||||
var exec = require('cordova/exec');
|
||||
|
||||
var http = {
|
||||
useBasicAuth: function(username, password, success, failure) {
|
||||
return exec(success, failure, "CordovaHttpPlugin", "useBasicAuth", [username, password]);
|
||||
},
|
||||
setHeader: function(header, value, success, failure) {
|
||||
return exec(success, failure, "CordovaHttpPlugin", "setHeader", [header, value]);
|
||||
},
|
||||
enableSSLPinning: function(enable, success, failure) {
|
||||
return exec(success, failure, "CordovaHttpPlugin", "enableSSLPinning", [enable]);
|
||||
},
|
||||
acceptAllCerts: function(allow, success, failure) {
|
||||
return exec(success, failure, "CordovaHttpPlugin", "acceptAllCerts", [allow]);
|
||||
},
|
||||
post: function(url, params, headers, success, failure) {
|
||||
return exec(success, failure, "CordovaHttpPlugin", "post", [url, params, headers]);
|
||||
},
|
||||
get: function(url, params, headers, success, failure) {
|
||||
return exec(success, failure, "CordovaHttpPlugin", "get", [url, params, headers]);
|
||||
},
|
||||
uploadFile: function(url, params, headers, filePath, name, success, failure) {
|
||||
return exec(success, failure, "CordovaHttpPlugin", "uploadFile", [url, params, headers, filePath, name]);
|
||||
},
|
||||
downloadFile: function(url, params, headers, filePath, success, failure) {
|
||||
/*
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Modified by Andrew Stephan for Sync OnSet
|
||||
*
|
||||
*/
|
||||
var win = function(result) {
|
||||
var entry = new (require('org.apache.cordova.file.FileEntry'))();
|
||||
entry.isDirectory = false;
|
||||
entry.isFile = true;
|
||||
entry.name = result.file.name;
|
||||
entry.fullPath = result.file.fullPath;
|
||||
success(entry);
|
||||
};
|
||||
return exec(win, failure, "CordovaHttpPlugin", "downloadFile", [url, params, headers, filePath]);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = http;
|
||||
|
||||
if (typeof angular !== "undefined") {
|
||||
angular.module('cordovaHTTP', []).factory('cordovaHTTP', function($timeout, $q) {
|
||||
function makePromise(fn, args, async) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
var success = function(response) {
|
||||
if (async) {
|
||||
$timeout(function() {
|
||||
deferred.resolve(response);
|
||||
});
|
||||
} else {
|
||||
deferred.resolve(response);
|
||||
}
|
||||
};
|
||||
|
||||
var fail = function(response) {
|
||||
if (async) {
|
||||
$timeout(function() {
|
||||
deferred.reject(response);
|
||||
});
|
||||
} else {
|
||||
deferred.reject(response);
|
||||
}
|
||||
};
|
||||
|
||||
args.push(success);
|
||||
args.push(fail);
|
||||
|
||||
fn.apply(http, args);
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
var cordovaHTTP = {
|
||||
useBasicAuth: function(username, password) {
|
||||
return makePromise(http.useBasicAuth, [username, password]);
|
||||
},
|
||||
setHeader: function(header, value) {
|
||||
return makePromise(http.setHeader, [header, value]);
|
||||
},
|
||||
enableSSLPinning: function(enable) {
|
||||
return makePromise(http.enableSSLPinning, [enable]);
|
||||
},
|
||||
acceptAllCerts: function(allow) {
|
||||
return makePromise(http.acceptAllCerts, [allow]);
|
||||
},
|
||||
post: function(url, params, headers) {
|
||||
return makePromise(http.post, [url, params, headers], true);
|
||||
},
|
||||
get: function(url, params, headers) {
|
||||
return makePromise(http.get, [url, params, headers], true);
|
||||
},
|
||||
uploadFile: function(url, params, headers, filePath, name) {
|
||||
return makePromise(http.uploadFile, [url, params, headers, filePath, name], true);
|
||||
},
|
||||
downloadFile: function(url, params, headers, filePath) {
|
||||
return makePromise(http.downloadFile, [url, params, headers, filePath], true);
|
||||
}
|
||||
};
|
||||
return cordovaHTTP;
|
||||
});
|
||||
} else {
|
||||
window.cordovaHTTP = http;
|
||||
}
|
||||
7
www/global-configs.js
Normal file
7
www/global-configs.js
Normal file
@@ -0,0 +1,7 @@
|
||||
var globalConfigs = {
|
||||
headers: {},
|
||||
serializer: 'urlencoded',
|
||||
timeout: 60.0,
|
||||
};
|
||||
|
||||
module.exports = globalConfigs;
|
||||
256
www/helpers.js
Normal file
256
www/helpers.js
Normal file
@@ -0,0 +1,256 @@
|
||||
module.exports = function init(jsUtil, cookieHandler, messages) {
|
||||
var validSerializers = ['urlencoded', 'json', 'utf8'];
|
||||
var validCertModes = ['default', 'nocheck', 'pinned', 'legacy'];
|
||||
var validClientAuthModes = ['none', 'systemstore', 'file'];
|
||||
var validHttpMethods = ['get', 'put', 'post', 'patch', 'head', 'delete', 'upload', 'download'];
|
||||
|
||||
var interface = {
|
||||
b64EncodeUnicode: b64EncodeUnicode,
|
||||
checkSerializer: checkSerializer,
|
||||
checkSSLCertMode: checkSSLCertMode,
|
||||
checkClientAuthMode: checkClientAuthMode,
|
||||
checkForBlacklistedHeaderKey: checkForBlacklistedHeaderKey,
|
||||
checkForInvalidHeaderValue: checkForInvalidHeaderValue,
|
||||
injectCookieHandler: injectCookieHandler,
|
||||
injectFileEntryHandler: injectFileEntryHandler,
|
||||
getMergedHeaders: getMergedHeaders,
|
||||
getProcessedData: getProcessedData,
|
||||
handleMissingCallbacks: handleMissingCallbacks,
|
||||
handleMissingOptions: handleMissingOptions
|
||||
};
|
||||
|
||||
// expose all functions for testing purposes
|
||||
if (init.debug) {
|
||||
interface.mergeHeaders = mergeHeaders;
|
||||
interface.checkForValidStringValue = checkForValidStringValue;
|
||||
interface.checkKeyValuePairObject = checkKeyValuePairObject;
|
||||
interface.checkHttpMethod = checkHttpMethod;
|
||||
interface.checkTimeoutValue = checkTimeoutValue;
|
||||
interface.checkHeadersObject = checkHeadersObject;
|
||||
interface.checkParamsObject = checkParamsObject;
|
||||
interface.resolveCookieString = resolveCookieString;
|
||||
interface.createFileEntry = createFileEntry;
|
||||
interface.getCookieHeader = getCookieHeader;
|
||||
interface.getMatchingHostHeaders = getMatchingHostHeaders;
|
||||
interface.getAllowedDataTypes = getAllowedDataTypes;
|
||||
}
|
||||
|
||||
return interface;
|
||||
|
||||
// Thanks Mozilla: https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding#The_.22Unicode_Problem.22
|
||||
function b64EncodeUnicode(str) {
|
||||
return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function (match, p1) {
|
||||
return String.fromCharCode('0x' + p1);
|
||||
}));
|
||||
}
|
||||
|
||||
function mergeHeaders(globalHeaders, localHeaders) {
|
||||
var globalKeys = Object.keys(globalHeaders);
|
||||
var key;
|
||||
|
||||
for (var i = 0; i < globalKeys.length; i++) {
|
||||
key = globalKeys[i];
|
||||
|
||||
if (!localHeaders.hasOwnProperty(key)) {
|
||||
localHeaders[key] = globalHeaders[key];
|
||||
}
|
||||
}
|
||||
|
||||
return localHeaders;
|
||||
}
|
||||
|
||||
function checkForValidStringValue(list, value, onInvalidValueMessage) {
|
||||
if (jsUtil.getTypeOf(value) !== 'String') {
|
||||
throw new Error(onInvalidValueMessage + ' ' + list.join(', '));
|
||||
}
|
||||
|
||||
value = value.trim().toLowerCase();
|
||||
|
||||
if (list.indexOf(value) === -1) {
|
||||
throw new Error(onInvalidValueMessage + ' ' + list.join(', '));
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
function checkKeyValuePairObject(obj, allowedChildren, onInvalidValueMessage) {
|
||||
if (jsUtil.getTypeOf(obj) !== 'Object') {
|
||||
throw new Error(onInvalidValueMessage);
|
||||
}
|
||||
|
||||
var keys = Object.keys(obj);
|
||||
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
if (allowedChildren.indexOf(jsUtil.getTypeOf(obj[keys[i]])) === -1) {
|
||||
throw new Error(onInvalidValueMessage);
|
||||
}
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
function checkHttpMethod(method) {
|
||||
return checkForValidStringValue(validHttpMethods, method, messages.INVALID_HTTP_METHOD);
|
||||
}
|
||||
|
||||
function checkSerializer(serializer) {
|
||||
return checkForValidStringValue(validSerializers, serializer, messages.INVALID_DATA_SERIALIZER);
|
||||
}
|
||||
|
||||
function checkSSLCertMode(mode) {
|
||||
return checkForValidStringValue(validCertModes, mode, messages.INVALID_SSL_CERT_MODE);
|
||||
}
|
||||
|
||||
function checkClientAuthMode(mode) {
|
||||
return checkForValidStringValue(validClientAuthModes, mode, messages.INVALID_CLIENT_AUTH_MODE);
|
||||
}
|
||||
|
||||
function checkForBlacklistedHeaderKey(key) {
|
||||
if (key.toLowerCase() === 'cookie') {
|
||||
throw new Error(messages.ADDING_COOKIES_NOT_SUPPORTED);
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
function checkForInvalidHeaderValue(value) {
|
||||
if (jsUtil.getTypeOf(value) !== 'String') {
|
||||
throw new Error(messages.INVALID_HEADERS_VALUE);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
function checkTimeoutValue(timeout) {
|
||||
if (jsUtil.getTypeOf(timeout) !== 'Number' || timeout < 0) {
|
||||
throw new Error(messages.INVALID_TIMEOUT_VALUE);
|
||||
}
|
||||
|
||||
return timeout;
|
||||
}
|
||||
|
||||
function checkHeadersObject(headers) {
|
||||
return checkKeyValuePairObject(headers, ['String'], messages.INVALID_HEADERS_VALUE);
|
||||
}
|
||||
|
||||
function checkParamsObject(params) {
|
||||
return checkKeyValuePairObject(params, ['String', 'Array'], messages.INVALID_PARAMS_VALUE);
|
||||
}
|
||||
|
||||
function resolveCookieString(headers) {
|
||||
var keys = Object.keys(headers || {});
|
||||
|
||||
for (var i = 0; i < keys.length; ++i) {
|
||||
if (keys[i].match(/^set-cookie$/i)) {
|
||||
return headers[keys[i]];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function createFileEntry(rawEntry) {
|
||||
var entry = new (require('cordova-plugin-file.FileEntry'))();
|
||||
|
||||
entry.isDirectory = rawEntry.isDirectory;
|
||||
entry.isFile = rawEntry.isFile;
|
||||
entry.name = rawEntry.name;
|
||||
entry.fullPath = rawEntry.fullPath;
|
||||
entry.filesystem = new FileSystem(rawEntry.filesystemName || (rawEntry.filesystem == window.PERSISTENT ? 'persistent' : 'temporary'));
|
||||
entry.nativeURL = rawEntry.nativeURL;
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
function injectCookieHandler(url, cb) {
|
||||
return function (response) {
|
||||
cookieHandler.setCookieFromString(url, resolveCookieString(response.headers));
|
||||
cb(response);
|
||||
}
|
||||
}
|
||||
|
||||
function injectFileEntryHandler(cb) {
|
||||
return function (response) {
|
||||
cb(createFileEntry(response.file));
|
||||
}
|
||||
}
|
||||
|
||||
function getCookieHeader(url) {
|
||||
var cookieString = cookieHandler.getCookieString(url);
|
||||
|
||||
if (cookieString.length) {
|
||||
return { Cookie: cookieHandler.getCookieString(url) };
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
function getMatchingHostHeaders(url, headersList) {
|
||||
var matches = url.match(/^https?\:\/\/([^\/?#]+)(?:[\/?#]|$)/i);
|
||||
var domain = matches && matches[1];
|
||||
|
||||
return headersList[domain] || null;
|
||||
}
|
||||
|
||||
function getMergedHeaders(url, requestHeaders, predefinedHeaders) {
|
||||
var globalHeaders = predefinedHeaders['*'] || {};
|
||||
var hostHeaders = getMatchingHostHeaders(url, predefinedHeaders) || {};
|
||||
var mergedHeaders = mergeHeaders(globalHeaders, hostHeaders);
|
||||
|
||||
mergedHeaders = mergeHeaders(mergedHeaders, requestHeaders);
|
||||
mergedHeaders = mergeHeaders(mergedHeaders, getCookieHeader(url));
|
||||
|
||||
return mergedHeaders;
|
||||
}
|
||||
|
||||
function getAllowedDataTypes(dataSerializer) {
|
||||
switch (dataSerializer) {
|
||||
case 'utf8':
|
||||
return ['String'];
|
||||
case 'urlencoded':
|
||||
return ['Object'];
|
||||
default:
|
||||
return ['Array', 'Object'];
|
||||
}
|
||||
}
|
||||
|
||||
function getProcessedData(data, dataSerializer) {
|
||||
var currentDataType = jsUtil.getTypeOf(data);
|
||||
var allowedDataTypes = getAllowedDataTypes(dataSerializer);
|
||||
|
||||
if (allowedDataTypes.indexOf(currentDataType) === -1) {
|
||||
throw new Error(messages.DATA_TYPE_MISMATCH + ' ' + allowedDataTypes.join(', '));
|
||||
}
|
||||
|
||||
if (dataSerializer === 'utf8') {
|
||||
data = { text: data };
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
function handleMissingCallbacks(successFn, failFn) {
|
||||
if (jsUtil.getTypeOf(successFn) !== 'Function') {
|
||||
throw new Error(messages.MANDATORY_SUCCESS);
|
||||
}
|
||||
|
||||
if (jsUtil.getTypeOf(failFn) !== 'Function') {
|
||||
throw new Error(messages.MANDATORY_FAIL);
|
||||
}
|
||||
}
|
||||
|
||||
function handleMissingOptions(options, globals) {
|
||||
options = options || {};
|
||||
|
||||
return {
|
||||
method: checkHttpMethod(options.method || validHttpMethods[0]),
|
||||
serializer: checkSerializer(options.serializer || globals.serializer),
|
||||
timeout: checkTimeoutValue(options.timeout || globals.timeout),
|
||||
headers: checkHeadersObject(options.headers || {}),
|
||||
params: checkParamsObject(options.params || {}),
|
||||
data: jsUtil.getTypeOf(options.data) === 'Undefined' ? null : options.data,
|
||||
filePath: options.filePath || '',
|
||||
name: options.name || ''
|
||||
};
|
||||
}
|
||||
};
|
||||
26
www/js-util.js
Normal file
26
www/js-util.js
Normal file
@@ -0,0 +1,26 @@
|
||||
module.exports = {
|
||||
// typeof is not working reliably in JS
|
||||
getTypeOf: function (object) {
|
||||
switch (Object.prototype.toString.call(object)) {
|
||||
case '[object Array]':
|
||||
return 'Array';
|
||||
case '[object Boolean]':
|
||||
return 'Boolean';
|
||||
case '[object Function]':
|
||||
return 'Function';
|
||||
case '[object Null]':
|
||||
return 'Null';
|
||||
case '[object Number]':
|
||||
return 'Number';
|
||||
case '[object Object]':
|
||||
return 'Object';
|
||||
case '[object String]':
|
||||
return 'String';
|
||||
case '[object Undefined]':
|
||||
return 'Undefined';
|
||||
default:
|
||||
return 'Unknown';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
181
www/local-storage-store.js
Normal file
181
www/local-storage-store.js
Normal file
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2016 Exponent
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Based on "tough-cookie-web-storage-store" v1.0.0
|
||||
* Thanks James Ide: https://github.com/exponentjs/tough-cookie-web-storage-store
|
||||
*
|
||||
* Modified by Sefa Ilkimen for cordova plugin integration
|
||||
*
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
module.exports = function init(ToughCookie, _) {
|
||||
function WebStorageCookieStore(storage, storeKey) {
|
||||
ToughCookie.Store.call(this);
|
||||
this._storage = storage;
|
||||
this._storeKey = storeKey || '__cookieStore__';
|
||||
this.synchronous = true;
|
||||
}
|
||||
|
||||
WebStorageCookieStore.prototype = Object.create(ToughCookie.Store);
|
||||
|
||||
WebStorageCookieStore.prototype.findCookie = function (domain, path, key, callback) {
|
||||
var store = this._readStore();
|
||||
var cookie = _.get(store, [domain, path, key], null);
|
||||
|
||||
callback(null, ToughCookie.Cookie.fromJSON(cookie));
|
||||
};
|
||||
|
||||
WebStorageCookieStore.prototype.findCookies = function (domain, path, callback) {
|
||||
if (!domain) {
|
||||
callback(null, []);
|
||||
return;
|
||||
}
|
||||
|
||||
var that = this;
|
||||
var cookies = [];
|
||||
var store = this._readStore();
|
||||
var domains = ToughCookie.permuteDomain(domain) || [domain];
|
||||
|
||||
domains.forEach(function (domain) {
|
||||
if (!store[domain]) {
|
||||
return;
|
||||
}
|
||||
|
||||
var matchingPaths = Object.keys(store[domain]);
|
||||
|
||||
if (path != null) {
|
||||
matchingPaths = matchingPaths.filter(function (cookiePath) {
|
||||
return that._isOnPath(cookiePath, path);
|
||||
});
|
||||
}
|
||||
|
||||
matchingPaths.forEach(function (path) {
|
||||
Array.prototype.push.apply(cookies, _.values(store[domain][path]));
|
||||
});
|
||||
});
|
||||
|
||||
cookies = cookies.map(function (cookie) {
|
||||
return ToughCookie.Cookie.fromJSON(cookie);
|
||||
});
|
||||
|
||||
callback(null, cookies);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns whether `cookiePath` is on the given `urlPath`
|
||||
*/
|
||||
WebStorageCookieStore.prototype._isOnPath = function (cookiePath, urlPath) {
|
||||
if (!cookiePath) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cookiePath === urlPath) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (urlPath.indexOf(cookiePath) !== 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cookiePath[cookiePath.length - 1] !== '/' && urlPath[cookiePath.length] !== '/') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
WebStorageCookieStore.prototype.putCookie = function (cookie, callback) {
|
||||
var store = this._readStore();
|
||||
|
||||
_.set(store, [cookie.domain, cookie.path, cookie.key], cookie);
|
||||
this._writeStore(store);
|
||||
callback(null);
|
||||
};
|
||||
|
||||
WebStorageCookieStore.prototype.updateCookie = function (oldCookie, newCookie, callback) {
|
||||
this.putCookie(newCookie, callback);
|
||||
};
|
||||
|
||||
|
||||
WebStorageCookieStore.prototype.removeCookie = function (domain, path, key, callback) {
|
||||
var store = this._readStore();
|
||||
|
||||
_.unset(store, [domain, path, key]);
|
||||
this._writeStore(store);
|
||||
callback(null);
|
||||
};
|
||||
|
||||
WebStorageCookieStore.prototype.removeCookies = function (domain, path, callback) {
|
||||
var store = this._readStore();
|
||||
|
||||
if (path == null) {
|
||||
_.unset(store, [domain]);
|
||||
} else {
|
||||
_.unset(store, [domain, path]);
|
||||
}
|
||||
|
||||
this._writeStore(store);
|
||||
callback(null);
|
||||
};
|
||||
|
||||
WebStorageCookieStore.prototype.getAllCookies = function (callback) {
|
||||
var cookies = [];
|
||||
var store = this._readStore();
|
||||
|
||||
Object.keys(store).forEach(function (domain) {
|
||||
Object.keys(store[domain]).forEach(function (path) {
|
||||
Array.protype.push.apply(cookies, _.values(store[domain][path]));
|
||||
});
|
||||
});
|
||||
|
||||
cookies = cookies.map(function (cookie) {
|
||||
return ToughCookie.Cookie.fromJSON(cookie);
|
||||
});
|
||||
|
||||
cookies.sort(function (c1, c2) {
|
||||
return (c1.creationIndex || 0) - (c2.creationIndex || 0);
|
||||
});
|
||||
|
||||
callback(null, cookies);
|
||||
};
|
||||
|
||||
WebStorageCookieStore.prototype._readStore = function () {
|
||||
var json = this._storage.getItem(this._storeKey);
|
||||
|
||||
if (json !== null) {
|
||||
try {
|
||||
return JSON.parse(json);
|
||||
} catch (e) { }
|
||||
}
|
||||
|
||||
return {};
|
||||
};
|
||||
|
||||
WebStorageCookieStore.prototype._writeStore = function (store) {
|
||||
this._storage.setItem(this._storeKey, JSON.stringify(store));
|
||||
};
|
||||
|
||||
return WebStorageCookieStore;
|
||||
};
|
||||
20
www/lodash.js
Normal file
20
www/lodash.js
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* @license
|
||||
* Lodash (Custom Build) lodash.com/license | Underscore.js 1.8.3 underscorejs.org/LICENSE
|
||||
* Build: `lodash include="get,set,unset,values" exports="node"`
|
||||
*/
|
||||
;(function(){function t(t,e){for(var r=-1,n=null==t?0:t.length,o=Array(n);++r<n;)o[r]=e(t[r],r,t);return o}function e(t){return function(e){return t(e)}}function r(e,r){return t(r,function(t){return e[t]})}function n(){}function o(t){var e=-1,r=null==t?0:t.length;for(this.clear();++e<r;){var n=t[e];this.set(n[0],n[1])}}function u(t){var e=-1,r=null==t?0:t.length;for(this.clear();++e<r;){var n=t[e];this.set(n[0],n[1])}}function i(t){var e=-1,r=null==t?0:t.length;for(this.clear();++e<r;){var n=t[e];this.set(n[0],n[1]);
|
||||
}}function c(t,e){for(var r=t.length;r--;)if(m(t[r][0],e))return r;return-1}function a(t,e){e=h(e,t);for(var r=0,n=e.length;null!=t&&r<n;)t=t[j(e[r++])];return r&&r==n?t:E}function l(t){if(null==t)return t===E?"[object Undefined]":"[object Null]";t=Object(t);var e;if(nt&&nt in t){var r=Q.call(t,nt),n=t[nt];try{t[nt]=E,e=true}catch(t){}var o=Y.call(t);e&&(r?t[nt]=n:delete t[nt]),e=o}else e=Y.call(t);return e}function s(t){return w(t)&&"[object Arguments]"==l(t)}function f(t){return w(t)&&z(t.length)&&!!M[l(t)];
|
||||
}function p(e){if(typeof e=="string")return e;if(ft(e))return t(e,p)+"";if(x(e))return at?at.call(e):"";var r=e+"";return"0"==r&&1/e==-T?"-0":r}function h(t,e){var r;return ft(t)?r=t:(ft(t)?r=false:(r=typeof t,r=!("number"!=r&&"symbol"!=r&&"boolean"!=r&&null!=t&&!x(t))||(B.test(t)||!I.test(t)||null!=e&&t in Object(e))),r=r?[t]:lt(F(t))),r}function y(t,e){var r=t.__data__,n=typeof e;return("string"==n||"number"==n||"symbol"==n||"boolean"==n?"__proto__"!==e:null===e)?r[typeof e=="string"?"string":"hash"]:r.map;
|
||||
}function b(t,e){var r=null==t?E:t[e];return(!S(r)||X&&X in r?0:(O(r)?Z:L).test(g(r)))?r:E}function _(t,e){return e=null==e?9007199254740991:e,!!e&&(typeof t=="number"||R.test(t))&&-1<t&&0==t%1&&t<e}function j(t){if(typeof t=="string"||x(t))return t;var e=t+"";return"0"==e&&1/t==-T?"-0":e}function g(t){if(null!=t){try{return K.call(t)}catch(t){}return t+""}return""}function v(t){var e=null==t?0:t.length;return e?t[e-1]:E}function d(t,e){function r(){var n=arguments,o=e?e.apply(this,n):n[0],u=r.cache;
|
||||
return u.has(o)?u.get(o):(n=t.apply(this,n),r.cache=u.set(o,n)||u,n)}if(typeof t!="function"||null!=e&&typeof e!="function")throw new TypeError("Expected a function");return r.cache=new(d.Cache||i),r}function m(t,e){return t===e||t!==t&&e!==e}function A(t){return null!=t&&z(t.length)&&!O(t)}function O(t){return!!S(t)&&(t=l(t),"[object Function]"==t||"[object GeneratorFunction]"==t||"[object AsyncFunction]"==t||"[object Proxy]"==t)}function z(t){return typeof t=="number"&&-1<t&&0==t%1&&9007199254740991>=t;
|
||||
}function S(t){var e=typeof t;return null!=t&&("object"==e||"function"==e)}function w(t){return null!=t&&typeof t=="object"}function x(t){return typeof t=="symbol"||w(t)&&"[object Symbol]"==l(t)}function F(t){return null==t?"":p(t)}function $(t){if(A(t)){var e=ft(t),r=!e&&st(t),n=!e&&!r&&pt(t),o=!e&&!r&&!n&&ht(t);if(e=e||r||n||o){for(var r=t.length,u=String,i=-1,c=Array(r);++i<r;)c[i]=u(i);r=c}else r=[];var a,u=r.length;for(a in t)!Q.call(t,a)||e&&("length"==a||n&&("offset"==a||"parent"==a)||o&&("buffer"==a||"byteLength"==a||"byteOffset"==a)||_(a,u))||r.push(a);
|
||||
t=r}else if(a=t&&t.constructor,t===(typeof a=="function"&&a.prototype||H)){a=[];for(n in Object(t))Q.call(t,n)&&"constructor"!=n&&a.push(n);t=a}else t=ut(t);return t}function k(){return false}var E,T=1/0,I=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,B=/^\w*$/,P=/^\./,U=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g,C=/\\(\\)?/g,L=/^\[object .+?Constructor\]$/,R=/^(?:0|[1-9]\d*)$/,M={};M["[object Float32Array]"]=M["[object Float64Array]"]=M["[object Int8Array]"]=M["[object Int16Array]"]=M["[object Int32Array]"]=M["[object Uint8Array]"]=M["[object Uint8ClampedArray]"]=M["[object Uint16Array]"]=M["[object Uint32Array]"]=true,
|
||||
M["[object Arguments]"]=M["[object Array]"]=M["[object ArrayBuffer]"]=M["[object Boolean]"]=M["[object DataView]"]=M["[object Date]"]=M["[object Error]"]=M["[object Function]"]=M["[object Map]"]=M["[object Number]"]=M["[object Object]"]=M["[object RegExp]"]=M["[object Set]"]=M["[object String]"]=M["[object WeakMap]"]=false;var N,D=typeof global=="object"&&global&&global.Object===Object&&global,V=typeof self=="object"&&self&&self.Object===Object&&self,q=D||V||Function("return this")(),G=(V=typeof exports=="object"&&exports&&!exports.nodeType&&exports)&&typeof module=="object"&&module&&!module.nodeType&&module,W=G&&G.exports===V,D=W&&D.process;
|
||||
t:{try{N=D&&D.binding&&D.binding("util");break t}catch(t){}N=void 0}N=N&&N.isTypedArray;var D=Array.prototype,H=Object.prototype,J=q["__core-js_shared__"],K=Function.prototype.toString,Q=H.hasOwnProperty,X=function(){var t=/[^.]+$/.exec(J&&J.keys&&J.keys.IE_PROTO||"");return t?"Symbol(src)_1."+t:""}(),Y=H.toString,Z=RegExp("^"+K.call(Q).replace(/[\\^$.*+?()[\]{}|]/g,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$"),tt=W?q.Buffer:E,W=q.Symbol,et=H.propertyIsEnumerable,rt=D.splice,nt=W?W.toStringTag:E,ot=function(){
|
||||
try{var t=b(Object,"defineProperty");return t({},"",{}),t}catch(t){}}(),D=tt?tt.isBuffer:E,ut=function(t,e){return function(r){return t(e(r))}}(Object.keys,Object),it=b(q,"Map"),ct=b(Object,"create"),at=(q=W?W.prototype:E)?q.toString:E;o.prototype.clear=function(){this.__data__=ct?ct(null):{},this.size=0},o.prototype.delete=function(t){return t=this.has(t)&&delete this.__data__[t],this.size-=t?1:0,t},o.prototype.get=function(t){var e=this.__data__;return ct?(t=e[t],"__lodash_hash_undefined__"===t?E:t):Q.call(e,t)?e[t]:E;
|
||||
},o.prototype.has=function(t){var e=this.__data__;return ct?e[t]!==E:Q.call(e,t)},o.prototype.set=function(t,e){var r=this.__data__;return this.size+=this.has(t)?0:1,r[t]=ct&&e===E?"__lodash_hash_undefined__":e,this},u.prototype.clear=function(){this.__data__=[],this.size=0},u.prototype.delete=function(t){var e=this.__data__;return t=c(e,t),!(0>t)&&(t==e.length-1?e.pop():rt.call(e,t,1),--this.size,true)},u.prototype.get=function(t){var e=this.__data__;return t=c(e,t),0>t?E:e[t][1]},u.prototype.has=function(t){
|
||||
return-1<c(this.__data__,t)},u.prototype.set=function(t,e){var r=this.__data__,n=c(r,t);return 0>n?(++this.size,r.push([t,e])):r[n][1]=e,this},i.prototype.clear=function(){this.size=0,this.__data__={hash:new o,map:new(it||u),string:new o}},i.prototype.delete=function(t){return t=y(this,t).delete(t),this.size-=t?1:0,t},i.prototype.get=function(t){return y(this,t).get(t)},i.prototype.has=function(t){return y(this,t).has(t)},i.prototype.set=function(t,e){var r=y(this,t),n=r.size;return r.set(t,e),this.size+=r.size==n?0:1,
|
||||
this};var lt=function(t){t=d(t,function(t){return 500===e.size&&e.clear(),t});var e=t.cache;return t}(function(t){var e=[];return P.test(t)&&e.push(""),t.replace(U,function(t,r,n,o){e.push(n?o.replace(C,"$1"):r||t)}),e});d.Cache=i;var st=s(function(){return arguments}())?s:function(t){return w(t)&&Q.call(t,"callee")&&!et.call(t,"callee")},ft=Array.isArray,pt=D||k,ht=N?e(N):f;n.keys=$,n.memoize=d,n.set=function(t,e,r){if(null!=t&&S(t)){e=h(e,t);for(var n=-1,o=e.length,u=o-1,i=t;null!=i&&++n<o;){var c=j(e[n]),a=r;
|
||||
if(n!=u){var l=i[c],a=E;a===E&&(a=S(l)?l:_(e[n+1])?[]:{})}var s=i,l=c,f=s[l];Q.call(s,l)&&m(f,a)&&(a!==E||l in s)||("__proto__"==l&&ot?ot(s,l,{configurable:true,enumerable:true,value:a,writable:true}):s[l]=a),i=i[c]}}return t},n.unset=function(t,e){var r;if(null==t)r=true;else{var n=t,o=r=h(e,n);if(!(2>o.length)){var u=0,i=-1,c=-1,l=o.length;for(0>u&&(u=-u>l?0:l+u),i=i>l?l:i,0>i&&(i+=l),l=u>i?0:i-u>>>0,u>>>=0,i=Array(l);++c<l;)i[c]=o[c+u];n=a(n,i)}r=j(v(r)),r=!(null!=n&&Q.call(n,r))||delete n[r]}return r;
|
||||
},n.values=function(t){return null==t?[]:r(t,$(t))},n.eq=m,n.get=function(t,e,r){return t=null==t?E:a(t,e),t===E?r:t},n.isArguments=st,n.isArray=ft,n.isArrayLike=A,n.isBuffer=pt,n.isFunction=O,n.isLength=z,n.isObject=S,n.isObjectLike=w,n.isSymbol=x,n.isTypedArray=ht,n.last=v,n.stubFalse=k,n.toString=F,n.VERSION="4.17.1",G&&((G.exports=n)._=n,V._=n)}).call(this);
|
||||
13
www/messages.js
Normal file
13
www/messages.js
Normal file
@@ -0,0 +1,13 @@
|
||||
module.exports = {
|
||||
ADDING_COOKIES_NOT_SUPPORTED: 'advanced-http: "setHeader" does not support adding cookies, please use "setCookie" function instead',
|
||||
DATA_TYPE_MISMATCH: 'advanced-http: "data" argument supports only following data types:',
|
||||
MANDATORY_SUCCESS: 'advanced-http: missing mandatory "onSuccess" callback function',
|
||||
MANDATORY_FAIL: 'advanced-http: missing mandatory "onFail" callback function',
|
||||
INVALID_HTTP_METHOD: 'advanced-http: invalid HTTP method, supported methods are:',
|
||||
INVALID_DATA_SERIALIZER: 'advanced-http: invalid serializer, supported serializers are:',
|
||||
INVALID_SSL_CERT_MODE: 'advanced-http: invalid SSL cert mode, supported modes are:',
|
||||
INVALID_CLIENT_AUTH_MODE: 'advanced-http: invalid client certificate authentication mode, supported modes are:',
|
||||
INVALID_HEADERS_VALUE: 'advanced-http: header values must be strings',
|
||||
INVALID_TIMEOUT_VALUE: 'advanced-http: invalid timeout value, needs to be a positive numeric value',
|
||||
INVALID_PARAMS_VALUE: 'advanced-http: invalid params object, needs to be an object with strings'
|
||||
};
|
||||
184
www/public-interface.js
Normal file
184
www/public-interface.js
Normal file
@@ -0,0 +1,184 @@
|
||||
module.exports = function init(exec, cookieHandler, urlUtil, helpers, globalConfigs) {
|
||||
const publicInterface = {
|
||||
getBasicAuthHeader: getBasicAuthHeader,
|
||||
useBasicAuth: useBasicAuth,
|
||||
getHeaders: getHeaders,
|
||||
setHeader: setHeader,
|
||||
getDataSerializer: getDataSerializer,
|
||||
setDataSerializer: setDataSerializer,
|
||||
setCookie: setCookie,
|
||||
clearCookies: clearCookies,
|
||||
removeCookies: removeCookies,
|
||||
getCookieString: getCookieString,
|
||||
getRequestTimeout: getRequestTimeout,
|
||||
setRequestTimeout: setRequestTimeout,
|
||||
// for being backward compatible
|
||||
setSSLCertMode: setServerTrustMode,
|
||||
setServerTrustMode: setServerTrustMode,
|
||||
setClientAuthMode: setClientAuthMode,
|
||||
disableRedirect: disableRedirect,
|
||||
sendRequest: sendRequest,
|
||||
post: post,
|
||||
get: get,
|
||||
put: put,
|
||||
patch: patch,
|
||||
delete: del,
|
||||
head: head,
|
||||
uploadFile: uploadFile,
|
||||
downloadFile: downloadFile
|
||||
};
|
||||
|
||||
function getBasicAuthHeader(username, password) {
|
||||
return { 'Authorization': 'Basic ' + helpers.b64EncodeUnicode(username + ':' + password) };
|
||||
}
|
||||
|
||||
function useBasicAuth(username, password) {
|
||||
this.setHeader('*', 'Authorization', 'Basic ' + helpers.b64EncodeUnicode(username + ':' + password));
|
||||
}
|
||||
|
||||
function getHeaders(host) {
|
||||
return globalConfigs.headers[host || '*'] || null;
|
||||
}
|
||||
|
||||
function setHeader() {
|
||||
// this one is for being backward compatible
|
||||
var host = '*';
|
||||
var header = arguments[0];
|
||||
var value = arguments[1];
|
||||
|
||||
if (arguments.length === 3) {
|
||||
host = arguments[0];
|
||||
header = arguments[1];
|
||||
value = arguments[2];
|
||||
}
|
||||
|
||||
helpers.checkForBlacklistedHeaderKey(header);
|
||||
helpers.checkForInvalidHeaderValue(value);
|
||||
|
||||
globalConfigs.headers[host] = globalConfigs.headers[host] || {};
|
||||
globalConfigs.headers[host][header] = value;
|
||||
}
|
||||
|
||||
function getDataSerializer() {
|
||||
return globalConfigs.serializer;
|
||||
}
|
||||
|
||||
function setDataSerializer(serializer) {
|
||||
globalConfigs.serializer = helpers.checkSerializer(serializer);
|
||||
}
|
||||
|
||||
function setCookie(url, cookie, options) {
|
||||
cookieHandler.setCookie(url, cookie, options);
|
||||
}
|
||||
|
||||
function clearCookies() {
|
||||
cookieHandler.clearCookies();
|
||||
}
|
||||
|
||||
function removeCookies(url, callback) {
|
||||
cookieHandler.removeCookies(url, callback);
|
||||
}
|
||||
|
||||
function getCookieString(url) {
|
||||
return cookieHandler.getCookieString(url);
|
||||
}
|
||||
|
||||
function getRequestTimeout() {
|
||||
return globalConfigs.timeout;
|
||||
}
|
||||
|
||||
function setRequestTimeout(timeout) {
|
||||
globalConfigs.timeout = timeout;
|
||||
}
|
||||
|
||||
function setServerTrustMode(mode, success, failure) {
|
||||
helpers.handleMissingCallbacks(success, failure);
|
||||
|
||||
return exec(success, failure, 'CordovaHttpPlugin', 'setServerTrustMode', [helpers.checkSSLCertMode(mode)]);
|
||||
}
|
||||
|
||||
function setClientAuthMode() {
|
||||
// filePath is an optional param
|
||||
var mode = arguments[0];
|
||||
var success = arguments[1];
|
||||
var failure = arguments[2];
|
||||
var filePath = null;
|
||||
|
||||
if (arguments.length === 4) {
|
||||
mode = arguments[0];
|
||||
filePath = arguments[1];
|
||||
success = arguments[2];
|
||||
failure = arguments[3];
|
||||
}
|
||||
|
||||
helpers.handleMissingCallbacks(success, failure);
|
||||
|
||||
return exec(success, failure, 'CordovaHttpPlugin', 'setClientAuthMode', [helpers.checkClientAuthMode(mode), filePath]);
|
||||
}
|
||||
|
||||
function disableRedirect(disable, success, failure) {
|
||||
helpers.handleMissingCallbacks(success, failure);
|
||||
|
||||
return exec(success, failure, 'CordovaHttpPlugin', 'disableRedirect', [!!disable]);
|
||||
}
|
||||
|
||||
function sendRequest(url, options, success, failure) {
|
||||
helpers.handleMissingCallbacks(success, failure);
|
||||
|
||||
options = helpers.handleMissingOptions(options, globalConfigs);
|
||||
url = urlUtil.appendQueryParamsString(url, urlUtil.serializeQueryParams(options.params, true));
|
||||
|
||||
var headers = helpers.getMergedHeaders(url, options.headers, globalConfigs.headers);
|
||||
var onSuccess = helpers.injectCookieHandler(url, success);
|
||||
var onFail = helpers.injectCookieHandler(url, failure);
|
||||
|
||||
switch (options.method) {
|
||||
case 'post':
|
||||
case 'put':
|
||||
case 'patch':
|
||||
var data = helpers.getProcessedData(options.data, options.serializer);
|
||||
return exec(onSuccess, onFail, 'CordovaHttpPlugin', options.method, [url, data, options.serializer, headers, options.timeout]);
|
||||
case 'upload':
|
||||
return exec(onSuccess, onFail, 'CordovaHttpPlugin', 'uploadFile', [url, headers, options.filePath, options.name, options.timeout]);
|
||||
case 'download':
|
||||
var onDownloadSuccess = helpers.injectCookieHandler(url, helpers.injectFileEntryHandler(success));
|
||||
return exec(onDownloadSuccess, onFail, 'CordovaHttpPlugin', 'downloadFile', [url, headers, options.filePath, options.timeout]);
|
||||
default:
|
||||
return exec(onSuccess, onFail, 'CordovaHttpPlugin', options.method, [url, headers, options.timeout]);
|
||||
}
|
||||
}
|
||||
|
||||
function post(url, data, headers, success, failure) {
|
||||
return publicInterface.sendRequest(url, { method: 'post', data: data, headers: headers }, success, failure);
|
||||
};
|
||||
|
||||
function get(url, params, headers, success, failure) {
|
||||
return publicInterface.sendRequest(url, { method: 'get', params: params, headers: headers }, success, failure);
|
||||
};
|
||||
|
||||
function put(url, data, headers, success, failure) {
|
||||
return publicInterface.sendRequest(url, { method: 'put', data: data, headers: headers }, success, failure);
|
||||
}
|
||||
|
||||
function patch(url, data, headers, success, failure) {
|
||||
return publicInterface.sendRequest(url, { method: 'patch', data: data, headers: headers }, success, failure);
|
||||
}
|
||||
|
||||
function del(url, params, headers, success, failure) {
|
||||
return publicInterface.sendRequest(url, { method: 'delete', params: params, headers: headers }, success, failure);
|
||||
}
|
||||
|
||||
function head(url, params, headers, success, failure) {
|
||||
return publicInterface.sendRequest(url, { method: 'head', params: params, headers: headers }, success, failure);
|
||||
}
|
||||
|
||||
function uploadFile(url, params, headers, filePath, name, success, failure) {
|
||||
return publicInterface.sendRequest(url, { method: 'upload', params: params, headers: headers, filePath: filePath, name: name }, success, failure);
|
||||
}
|
||||
|
||||
function downloadFile(url, params, headers, filePath, success, failure) {
|
||||
return publicInterface.sendRequest(url, { method: 'download', params: params, headers: headers, filePath: filePath }, success, failure);
|
||||
}
|
||||
|
||||
return publicInterface;
|
||||
}
|
||||
5089
www/umd-tough-cookie.js
Normal file
5089
www/umd-tough-cookie.js
Normal file
File diff suppressed because one or more lines are too long
103
www/url-util.js
Normal file
103
www/url-util.js
Normal file
@@ -0,0 +1,103 @@
|
||||
module.exports = function init(jsUtil) {
|
||||
return {
|
||||
parseUrl: parseUrl,
|
||||
appendQueryParamsString: appendQueryParamsString,
|
||||
serializeQueryParams: serializeQueryParams
|
||||
}
|
||||
|
||||
function parseUrl(url) {
|
||||
var match = url.match(/^(https?\:)\/\/(([^:\/?#]*)(?:\:([0-9]+))?)([\/]{0,1}[^?#]*)(\?[^#]*|)(#.*|)$/);
|
||||
|
||||
return match && {
|
||||
protocol: match[1],
|
||||
host: match[2],
|
||||
hostname: match[3],
|
||||
port: match[4] || '',
|
||||
pathname: match[5],
|
||||
search: match[6],
|
||||
hash: match[7]
|
||||
}
|
||||
}
|
||||
|
||||
function appendQueryParamsString(url, params) {
|
||||
if (!url.length || !params.length) {
|
||||
return url;
|
||||
}
|
||||
|
||||
var parsed = parseUrl(url);
|
||||
|
||||
return parsed.protocol
|
||||
+ '//'
|
||||
+ parsed.host
|
||||
+ parsed.pathname
|
||||
+ (parsed.search.length ? parsed.search + '&' + params : '?' + params)
|
||||
+ parsed.hash;
|
||||
}
|
||||
|
||||
function serializeQueryParams(params, encode) {
|
||||
return serializeObject('', params, encode);
|
||||
}
|
||||
|
||||
function serializeObject(parentKey, object, encode) {
|
||||
var parts = [];
|
||||
|
||||
for (var key in object) {
|
||||
if (!object.hasOwnProperty(key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var identifier = parentKey.length ? parentKey + '[' + key + ']' : key;
|
||||
|
||||
if (jsUtil.getTypeOf(object[key]) === 'Array') {
|
||||
parts.push(serializeArray(identifier, object[key], encode));
|
||||
continue;
|
||||
} else if (jsUtil.getTypeOf(object[key]) === 'Object') {
|
||||
parts.push(serializeObject(identifier, object[key], encode));
|
||||
continue;
|
||||
}
|
||||
|
||||
parts.push(serializeIdentifier(parentKey, key, encode) + '=' + serializeValue(object[key], encode));
|
||||
}
|
||||
|
||||
return parts.join('&');
|
||||
}
|
||||
|
||||
function serializeArray(parentKey, array, encode) {
|
||||
var parts = [];
|
||||
|
||||
for (var i = 0; i < array.length; ++i) {
|
||||
if (jsUtil.getTypeOf(array[i]) === 'Array') {
|
||||
parts.push(serializeArray(parentKey + '[]', array[i], encode));
|
||||
continue;
|
||||
} else if (jsUtil.getTypeOf(array[i]) === 'Object') {
|
||||
parts.push(serializeObject(parentKey + '[]' + array[i], encode));
|
||||
continue;
|
||||
}
|
||||
|
||||
parts.push(serializeIdentifier(parentKey, '', encode) + '=' + serializeValue(array[i], encode));
|
||||
}
|
||||
|
||||
return parts.join('&');
|
||||
}
|
||||
|
||||
function serializeIdentifier(parentKey, key, encode) {
|
||||
if (!parentKey.length) {
|
||||
return encode ? encodeURIComponent(key) : key;
|
||||
}
|
||||
|
||||
if (encode) {
|
||||
return encodeURIComponent(parentKey) + '[' + encodeURIComponent(key) + ']';
|
||||
} else {
|
||||
return parentKey + '[' + key + ']';
|
||||
}
|
||||
}
|
||||
|
||||
function serializeValue(value, encode) {
|
||||
if (encode) {
|
||||
return encodeURIComponent(value);
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user