diff --git a/.cordova/config.json b/.cordova/config.json deleted file mode 100644 index 5ec683f51..000000000 --- a/.cordova/config.json +++ /dev/null @@ -1 +0,0 @@ -{"id":"io.cordova.hellocordova","name":"HelloCordova"} \ No newline at end of file diff --git a/.github/issue_template.md b/.github/issue_template.md new file mode 100644 index 000000000..4b0bfbcd4 --- /dev/null +++ b/.github/issue_template.md @@ -0,0 +1,38 @@ +Please do not submit support requests or "How to" questions here. Instead, please use channels like [StackOverflow](http://stackoverflow.com/), the [Ionic forum](https://forum.ionicframework.com) or the [Ionic slack channel](http://ionicworldwide.herokuapp.com) + +For issues or feature requests related to the code **in this repository** file a Github issue and provide as much details as possible + +**Bug or feature request** + +[ ] I'm reporting a reproducible issue with the code +[ ] I'm reporting a feature request + +**Describe the Bug of feature request** + +A clear and concise description of what the bug or feature request is + +**Expected Behavior** + +A clear and concise description of what you expected to happen + +**Sample repo** + +A sample repo is needed for this issue to be fixed, please provide one in order to reproduce the problem. + +You could for example try to use the [cordova-plugin-facebook4-lab](https://github.com/peterpeterparker/cordova-plugin-facebook4-lab) to reproduce your issue. + +**Plugin version, OS, devices, etc** + +Please provide information about the plugin version, OS, devices etc. where you faced your issue + +**Additional Context** + +List any other information that is relevant to your issue. Stack traces, related issues, suggestions on how to fix, Stack Overflow links, forum links, screenshots, OS if applicable, etc. + +**Important note** + +Unfortunately since this is a project done in spare time, support requests or "How to" questions will be closed without any further comments + +If we consider your issue to not be well documented, it will be closed without further comments too + +Thx you in advance for your understanding diff --git a/.gitignore b/.gitignore index ffe4b5146..35a00153e 100644 --- a/.gitignore +++ b/.gitignore @@ -26,4 +26,9 @@ gen/ build/ local.properties -proguard/ \ No newline at end of file +proguard/ + +.idea +*.iml +/nbproject/private/ +nbproject diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..ca6308f78 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,255 @@ + +# [6.4.0](https://github.com/jeduan/cordova-plugin-facebook4/releases/tag/v6.4.0) + +## Fix + +* **Android:** add a check to test if the access token is expired in order to fetch a new one if that would be the case ([#853](https://github.com/jeduan/cordova-plugin-facebook4/pull/853)) + + +# [6.3.0](https://github.com/jeduan/cordova-plugin-facebook4/releases/tag/v6.3.0) + +## Features + +* **iOS:** Updating to CocoaPods CDN ([#848](https://github.com/jeduan/cordova-plugin-facebook4/pull/848)) +* **iOS:** Update Facebook iOS SDK v5.15.0 ([Facebook iOS SDK changelog](https://github.com/facebook/facebook-objc-sdk/blob/master/CHANGELOG.md) | Released: January 21 2020) +* **Android:** Set per default usage of Facebook Android SDK v5.13.0 ([Facebook Android SDK changelog](https://github.com/facebook/facebook-android-sdk/blob/master/CHANGELOG.md) | Released: December 11 2019) + +Note: I have upgraded the default Facebook Android SDK to v5.13.0 and not to its last available version, v5.15.0, as this most recent version contains a bug (see [facebook-android-sdk#655](https://github.com/facebook/facebook-android-sdk/issues/665)). + +## Docs + +* **sample repo**: If you are looking to test the plugin, would like to reproduce a bug or build issues, there is now a demo project for such purpose: [cordova-plugin-facebook4-lab](https://github.com/peterpeterparker/cordova-plugin-facebook4-lab) + + +# [6.2.0](https://github.com/jeduan/cordova-plugin-facebook4/releases/tag/v6.2.0) + +## Features + +* **iOS:** Update Facebook iOS SDK v5.7.0 ([Facebook iOS SDK changelog](https://github.com/facebook/facebook-objc-sdk/blob/master/CHANGELOG.md) | Released: September 30 2019) +* **Android:** Set per default usage of Facebook Android SDK v5.5.2 ([Facebook Android SDK changelog](https://github.com/facebook/facebook-android-sdk/blob/master/CHANGELOG.md) | Released: October 4 2019) + +## Docs + +* Document two known errors related to Cocoapods, Facebook iOS SDK and their related solutions + + +# [6.1.0](https://github.com/jeduan/cordova-plugin-facebook4/releases/tag/v6.1.0) + +## Features + +* **iOS:** Update Facebook iOS SDK v5.6.0 ([Facebook iOS SDK changelog](https://github.com/facebook/facebook-objc-sdk/blob/master/CHANGELOG.md) | Released: July 15 2019) +* **Android:** Set per default usage of Facebook Android SDK v5.5.1 ([Facebook Android SDK changelog](https://github.com/facebook/facebook-android-sdk/blob/master/CHANGELOG.md) | Released: July 29 2019) + +Thx [Francesco Tonini](https://github.com/francescotonini) for the PR 👍 + + +# [6.0.0](https://github.com/jeduan/cordova-plugin-facebook4/releases/tag/v6.0.0) + +## Breaking change + +* **iOS:** use the [CocoaPods dependency manager](https://cocoapods.org) in order to satisfy the iOS Facebook SDK library dependencies ([#639](https://github.com/jeduan/cordova-plugin-facebook4/issues/639)) + +### Notes + +See [iOS documentation](https://github.com/jeduan/cordova-plugin-facebook4/tree/master/docs/ios) for some notes about installation with Cocoapods. + +## Features + +* **iOS:** Update Facebook iOS SDK v5.2.3 ([Facebook iOS SDK changelog](https://github.com/facebook/facebook-objc-sdk/blob/master/CHANGELOG.md) | Released: July 15 2019) +* **Android:** Set per default usage of Facebook Android SDK v5.2.0 ([Facebook Android SDK changelog](https://github.com/facebook/facebook-android-sdk/blob/master/CHANGELOG.md) | Released: July 29 2019) + + +# [5.0.0](https://github.com/jeduan/cordova-plugin-facebook4/releases/tag/v5.0.0) + +## Features + +* **iOS:** Update Facebook iOS SDK v5.0.2 ([#751](https://github.com/jeduan/cordova-plugin-facebook4/pull/751) | [Facebook iOS SDK changelog](https://github.com/facebook/facebook-objc-sdk/blob/master/CHANGELOG.md) | Released: June 5 2019) +* **Android:** Set per default usage of Facebook Android SDK v5.0.20 ([#751](https://github.com/jeduan/cordova-plugin-facebook4/pull/751) | [Facebook Android SDK changelog](https://github.com/facebook/facebook-android-sdk/blob/master/CHANGELOG.md) | Released: June 7 2019) + +### Kudos +Thx [Guy Lando](https://github.com/guylando) for your amazing work and PR 👍 + + +# [4.2.1](https://github.com/jeduan/cordova-plugin-facebook4/releases/tag/v4.2.1) + +## Fix + +* **iOS**: fix iOS crash when `enableHybridAppEvents` has already been enabled ([#745](https://github.com/jeduan/cordova-plugin-facebook4/pull/746)) + +### Kudos +Thx [Regev Brody](https://github.com/regevbr) for the PR 👍 + + +# [4.2.0](https://github.com/jeduan/cordova-plugin-facebook4/releases/tag/v4.2.0) + +## Features + +* **iOS:** Update Facebook iOS SDK v4.40.0 ([#743](https://github.com/jeduan/cordova-plugin-facebook4/issues/743), [#740](https://github.com/jeduan/cordova-plugin-facebook4/issues/740) | [Facebook iOS SDK changelog](https://developers.facebook.com/docs/ios/change-log-4x) | Released: January 22 2019) +* **Android:** Set per default usage of Facebook Android SDK v4.40.0 ([#743](https://github.com/jeduan/cordova-plugin-facebook4/issues/743), [#740](https://github.com/jeduan/cordova-plugin-facebook4/issues/740) | [Facebook Android SDK changelog](https://developers.facebook.com/docs/android/change-log-4x) | Released: January 22 2019) + + +# [4.1.0](https://github.com/jeduan/cordova-plugin-facebook4/releases/tag/v4.1.0) + +## Features + +* **Facebook:** Allow hybrid apps to send AppEvents from the pixel ([#678](https://github.com/jeduan/cordova-plugin-facebook4/issues/678)) + +### Kudos + +Thx [Mehmet Sencer Karadayi](https://github.com/msencer) for the PR 👍 + + +# [4.0.0](https://github.com/jeduan/cordova-plugin-facebook4/releases/tag/v4.0.0) + +## Features + +* **Android:** Support for cordova-android >= v7 without any workaround ([#716](https://github.com/jeduan/cordova-plugin-facebook4/pull/716), [#599](https://github.com/jeduan/cordova-plugin-facebook4/issues/599)) + +## Breaking change + +If you already have the workaround in the `config.xml` of your application, you have to **remove** it respectively you **need** to **remove** the following piece of code of your `config.xml`: + +``` + + 123456789 + myApplication + +``` + +### Side notes + +Also note that this improvements is backwards compatible, if you use cordova-android < v7 you should still be able to use the plugin. + +### Kudos + +Thank you [Adrian Pascu](https://github.com/adipascu) for the PR and support 👍 + + +# [3.2.0](https://github.com/jeduan/cordova-plugin-facebook4/releases/tag/v3.2.0) + +## Features + +* **iOS:** Update Facebook iOS SDK v4.38.1 ([#725](https://github.com/jeduan/cordova-plugin-facebook4/issues/725) | [Facebook iOS SDK changelog](https://developers.facebook.com/docs/ios/change-log-4x) | Released: 1st November 2018) +* **Android:** Set per default usage of Facebook Android SDK v4.38.1 ([#725](https://github.com/jeduan/cordova-plugin-facebook4/issues/725) | [Facebook Android SDK changelog](https://developers.facebook.com/docs/android/change-log-4x) | Released: 1st November 2018) + + +# [3.1.0](https://github.com/jeduan/cordova-plugin-facebook4/releases/tag/v3.1.0) + +## Features + +* **iOS:** Update Facebook iOS SDK v4.37.0 ([#713](https://github.com/jeduan/cordova-plugin-facebook4/issues/713) | [Facebook iOS SDK changelog](https://developers.facebook.com/docs/ios/change-log-4x) | Released: 27th September 2018) +* **Android:** Set per default usage of Facebook Android SDK v4.37.0 ([#713](https://github.com/jeduan/cordova-plugin-facebook4/issues/713) | [Facebook Android SDK changelog](https://developers.facebook.com/docs/android/change-log-4x) | Released: 27th September 2018) + + +# [3.0.0](https://github.com/jeduan/cordova-plugin-facebook4/releases/tag/v3.0.0) + +## Features + +* **Android:** Update Facebook Android SDK reference to v4.36.0 (drop greedy v4.+ reference) ([#708](https://github.com/jeduan/cordova-plugin-facebook4/issues/708)) + +### Disclaimer + +This release does not contains that much but we bumped up it to a major release because as of now, each release will always be published with a synchronized Facebook SDK version for iOS and for Android + + +# [2.5.0](https://github.com/jeduan/cordova-plugin-facebook4/releases/tag/v2.5.0) + +## Breaking change + +* **Android:** `ANDROID_SDK_VERSION` was renamed to `FACEBOOK_ANDROID_SDK_VERSION` to avoid misunderstood. This variable is use to set the Facebook SDK version for Android not the Android SDK version ([#706](https://github.com/jeduan/cordova-plugin-facebook4/issues/706)) + +## Documentation + +* **Doc:** Add a note in the `README` regarding the Graph API version which is not set by the plugin itself + + +# [2.4.0](https://github.com/jeduan/cordova-plugin-facebook4/releases/tag/v2.4.0) + +## Features + +* **iOS:** Update Facebook iOS SDK v4.36.0 ([e5c3aba](https://github.com/jeduan/cordova-plugin-facebook4/commit/e5c3abafca2fb0fe6651ab4025cb0510735fb66b) | [Facebook changelog](https://developers.facebook.com/docs/ios/change-log-4x) | Released: 30th August 2018) + + +# [2.3.0](https://github.com/jeduan/cordova-plugin-facebook4/releases/tag/v2.3.0) + +## Features + +* **iOS:** Update Facebook iOS SDK v4.35.0 ([4bdddc9](https://github.com/jeduan/cordova-plugin-facebook4/commit/4bbddc9938f2b087472757723ede1d037182b9c6) | [Facebook changelog](https://developers.facebook.com/docs/ios/change-log-4x) | Released: 26th July 2018) + + +# [2.2.0](https://github.com/jeduan/cordova-plugin-facebook4/releases/tag/v2.2.0) + +## Features + +* **iOS:** Update Facebook iOS SDK v4.34.0 ([74bccb1](https://github.com/jeduan/cordova-plugin-facebook4/commit/74bccb1a4f8938024002d0f4e681b9a56b1d7f43) | [Facebook changelog](https://developers.facebook.com/docs/ios/change-log-4x)) + + +# [2.1.0](https://github.com/jeduan/cordova-plugin-facebook4/releases/tag/v2.1.0) + +## Features + +* **iOS:** Update of the Facebook SDK v4.33.0 for iOS 11 ([#653](https://github.com/jeduan/cordova-plugin-facebook4/issues/653)) + + +# [2.0.1](https://github.com/jeduan/cordova-plugin-facebook4/releases/tag/v2.0.1) + +## Bug fixes + +* **Android:** Fix null pointer exception leading to app crashing after a second login (flow example: login -> graphApi -> logout -> login -> crash) ([#568](https://github.com/jeduan/cordova-plugin-facebook4/issues/568)) + + +# [2.0.0](https://github.com/jeduan/cordova-plugin-facebook4/releases/tag/v2.0.0) + +**Breaking change:** As of February 5, 2018, Facebook doesn't support anymore App Invites, therefore these methods have been removed. + +This version introduce also a new optional variable for the Android installation. + +## Features + +* **iOS:** App Invites support removed ([#645](https://github.com/jeduan/cordova-plugin-facebook4/issues/645)) + +* **Android:** Add optional installation variable `ANDROID_SDK_VERSION` ([#550](https://github.com/jeduan/cordova-plugin-facebook4/issues/550))([#646](https://github.com/jeduan/cordova-plugin-facebook4/pull/646)) + +### Side note + +I did consider removing the methods for App Invites as a breaking change, even if these weren't already supported from Facebook since months. + +I followed the semantic versioning idea the Ionic team recently published in their [blog](https://blog.ionicframework.com/ionic-semantic-versioning-release-schedule-and-lts/) + +@peterpeterparker + + +# [1.10.1](https://github.com/jeduan/cordova-plugin-facebook4/releases/tag/v1.10.1) + +v1.10.1 Fix an issue regarding the versioning (v1.10 was missing in plugin.xml) + + +# [1.10.0](https://github.com/jeduan/cordova-plugin-facebook4/releases/tag/v1.10.0) + +v1.10.0 introduces the last Facebook SDK for iOS + +## Features + +* **iOS:** Update of the Facebook SDK v4.31.1 for iOS 11 ([#631](https://github.com/jeduan/cordova-plugin-facebook4/issues/631)) ([#634](https://github.com/jeduan/cordova-plugin-facebook4/pull/634)) + +* **Android:** Add a note in the README and Android Guide about the compatibility with cordova-android >= v7.0.0 + +### Disclaimer + +The introduction of the new Facebook SDK for iOS 11 add a new confirm modal to the login flow. + +This is the expected design flow defined by `Apple` which `Facebook` implemented. The `cordova-plugin-facebook4` can't modify this behavior (as far as I know, @peterpeterparker). + +#### iOS 11.3 + +On iOS 11.3, the user won't have the choice anymore between using the Facebook App or using Email/Phone number for Facebook login process but will go straight to the "Facebook - Accept permissions" screen. + +Therefore, with this version, the user will still face only one modal during the login flow. + +#### iOS >= 11 < 11.3 + +Because of the introduction of the new modal, the user might face two modals during the login flow. One asking him/her if he/she want to open Facebook and one again asking him/her as before if he/she want to open the Facebook App. + +About the subject, you could for example have a look to this [stackoverflow's post](https://stackoverflow.com/questions/45858774/ios-11-facebook-signin-is-showing-an-initial-system-alert/). + +This is improved with iOS 11.3. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 63625e9d7..ee8b4ed7a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,7 +10,7 @@ These are just guidelines, not rules, use your best judgement and feel free to p Before creating a Github issue make sure you... -- Check the [Troubleshooting Guide](TROUBLESHOOTING.md) for well known issues. +- Check the [Troubleshooting Guide](./docs/TROUBLESHOOTING.md) for well known issues. - Search the [issues list](https://github.com/Wizcorp/phonegap-facebook-plugin/issues) for existing closed or still open issues - Create a Github issue diff --git a/README.md b/README.md index 22b79f405..25fb7582c 100644 --- a/README.md +++ b/README.md @@ -1,43 +1,89 @@ -# Apache Cordova Facebook Plugin -This is the official plugin for Facebook in Apache Cordova/PhoneGap! +# Important Notice -The Facebook plugin for [Apache Cordova](http://incubator.apache.org/cordova/) allows you to use the same JavaScript code in your Cordova application as you use in your web application. However, unlike in the browser, the Cordova application will use the native Facebook app to perform Single Sign On for the user. If this is not possible then the sign on will degrade gracefully using the standard dialog based authentication. +This plugin is not actively maintained anymore since March 2019. -* Supported on PhoneGap (Cordova) v3.5.0 and above. -* This plugin is built for - * iOS FacebookSDK 3.21.1 - * Android FacebookSDK 3.21.1 -* GitHub URL : [https://github.com/Wizcorp/phonegap-facebook-plugin/](https://github.com/Wizcorp/phonegap-facebook-plugin/) +**IT IS NOT RECEIVING SECURITY UPDATES AND YOU MIGHT BE ADDING HARMFUL CODE TO YOUR PROJECT.** -## << --- Cordova Registry Warning [iOS] +Please check out [cordova-plugin-facebook-connect](https://github.com/cordova-plugin-facebook-connect/cordova-plugin-facebook-connect) for an active fork. +This repo is conserved for archival purposes only. -****Installing this plugin directly from Cordova Registry results in Xcode using a broken `FacebookSDK.framework`, this is because the current publish procedure to NPM breaks symlinks [CB-6092](https://issues.apache.org/jira/browse/CB-6092). Please install the plugin through a locally cloned copy or re-add the `FacebookSDK.framework` to Xcode after installation.**** -## ------------------------------------------ >> ------------------------------------------- -## Facebook Requirements and Set-Up -To use this plugin you will need to make sure you've registered your Facebook app with Facebook and have an `APP_ID` [https://developers.facebook.com/apps](https://developers.facebook.com/apps). -#### Install Guides -- [iOS Guide](platforms/ios/README.md) -- [Android Guide](platforms/android/README.md) +## cordova-plugin-facebook4 -- [Browser Guide](platforms/browser/README.md) +> Use Facebook SDK in Cordova projects -- [PhoneGap Build Guide](platforms/pg-build/README.md) -- [Troubleshooting Guide | F.A.Q.](TROUBLESHOOTING.md) +## Table of contents +- [Installation](#installation) +- [Usage](#usage) +- [Sample repo](#sample-repo) +- [Compatibility](#compatibility) +- [Facebook SDK](#facebook-sdk) +- [API](#api) -#### Example Apps +## Installation -`platforms/android` and `platforms/ios` contain example projects and all the native code for the plugin for both Android and iOS platforms. They also include versions of the Android and iOS Facebook SDKs. These are used during automatic installation. +See npm package for versions - https://www.npmjs.com/package/cordova-plugin-facebook4 + +Make sure you've registered your Facebook app with Facebook and have an `APP_ID` [https://developers.facebook.com/apps](https://developers.facebook.com/apps). + +```bash +$ cordova plugin add cordova-plugin-facebook4 --save --variable APP_ID="123456789" --variable APP_NAME="myApplication" +``` + +If you need to change your `APP_ID` after installation, it's recommended that you remove and then re-add the plugin as above. Note that changes to the `APP_ID` value in your `config.xml` file will *not* be propagated to the individual platform builds. + +IMPORTANT: This plugin works as is with cordova-ios 5 but if you use earlier version of cordova-ios then you need to add the code in the following comment to your CordovaLib/Classes/Public/CDVAppDelegate.m file which was added to your project as part of the cordova-ios ios platform template: https://github.com/apache/cordova-ios/issues/476#issuecomment-460907247 + +### Installation Guides + +- [iOS Guide](docs/ios/README.md) + +- [Android Guide](docs/android/README.md) + +- [Browser Guide](docs/browser/README.md) + +- [Troubleshooting Guide | F.A.Q.](docs/TROUBLESHOOTING.md) + +## Usage + +This is a fork of the [official plugin for Facebook](https://github.com/Wizcorp/phonegap-facebook-plugin/) in Apache Cordova that implements the latest Facebook SDK. Unless noted, this is a drop-in replacement. You don't have to replace your client code. + +The Facebook plugin for [Apache Cordova](http://cordova.apache.org/) allows you to use the same JavaScript code in your Cordova application as you use in your web application. However, unlike in the browser, the Cordova application will use the native Facebook app to perform Single Sign On for the user. If this is not possible then the sign on will degrade gracefully using the standard dialog based authentication. + +## Sample Repo + +If you are looking to test the plugin, would like to reproduce a bug or build issues, there is a demo project for such purpose: [cordova-plugin-facebook4-lab](https://github.com/peterpeterparker/cordova-plugin-facebook4-lab). + +## Compatibility + + * Cordova >= 5.0.0 + * cordova-android >= 4.0 + * cordova-ios >= 3.8 + * cordova-browser >= 3.6 + * Phonegap build (use phonegap-version >= cli-5.2.0, android-minSdkVersion>=15, and android-build-tool=gradle), see [example here](https://github.com/yoav-zibin/phonegap-tictactoe/blob/gh-pages/www/config.xml) + +## Facebook SDK + +This plugin use the SDKs provided by Facebook. More information about these in their documentation for [iOS](https://developers.facebook.com/docs/ios/) or [Android](https://developers.facebook.com/docs/android/) + +### Facebook SDK version + +As of v3.0.0, this plugin will always be released for iOS and for Android with a synchronized usage of the Facebook SDKs + +For example: v3.0.0 include the Facebook SDK iOS v4.36.0 and reference per default the Facebook SDK Android v4.36.0 too + +### Graph API version + +Please note that this plugin itself does not specify which Graph API version is used. The Graph API version is set by the Facebook SDK for iOS and Android (see [Facebook documentation about versioning](https://developers.facebook.com/docs/apps/versions/)) ## API @@ -45,8 +91,6 @@ To use this plugin you will need to make sure you've registered your Facebook ap `facebookConnectPlugin.login(Array strings of permissions, Function success, Function failure)` -**NOTE** : Developers should call `facebookConnectPlugin.browserInit()` before login - **Web App ONLY** (see [Web App Guide](platforms/web/README.md)) - Success function returns an Object like: { @@ -67,6 +111,14 @@ Failure function returns an error String. `facebookConnectPlugin.logout(Function success, Function failure)` +### Check permissions (iOS only) + +`facebookConnectPlugin.checkHasCorrectPermissions(Array strings of permissions, Function success, Function failure)` + +Success function returns a success string if all passed permissions are granted. + +Failure function returns an error String if any passed permissions are not granted. + ### Get Status `facebookConnectPlugin.getLoginStatus(Function success, Function failure)` @@ -92,22 +144,71 @@ For more information see: [Facebook Documentation](https://developers.facebook.c `facebookConnectPlugin.showDialog(Object options, Function success, Function failure)` Example options - -Feed Dialog: +Share Dialog: { - method: "feed", - link: "http://example.com", - caption: "Such caption, very feed." + method: "share", + href: "http://example.com", + caption: "Such caption, very feed.", + description: "Much description", + picture: 'http://example.com/image.png', + hashtag: '#myHashtag', + share_feedWeb: true, // iOS only } -App request: +#### iOS + +The default dialog mode is [`FBSDKShareDialogModeAutomatic`](https://developers.facebook.com/docs/reference/ios/current/constants/FBSDKShareDialogMode/). You can share that by adding a specific dialog mode parameter. The available share dialog modes are: `share_sheet`, `share_feedBrowser`, `share_native` and `share_feedWeb`. [Read more about share dialog modes](https://developers.facebook.com/docs/reference/ios/current/constants/FBSDKShareDialogMode/) + +`caption`, `description` and `picture` were deprecated in Facebok API [v2.9](https://developers.facebook.com/docs/graph-api/changelog/version2.9#gapi-deprecate) and therefore not supported anymore on iOS + +Game request: { method: "apprequests", - message: "Come on man, check out my application." + message: "Come on man, check out my application.", + data: data, + title: title, + actionType: 'askfor', + filters: 'app_non_users' + } + +Send Dialog: + + { + method: "send", + caption: "Check this out.", + link: "http://example.com", + description: "The site I told you about", + picture: "http://example.com/image.png" } + +Share dialog - Open Graph Story: (currently only fully available on Android, iOS currently does not support action_properties) + + { + var obj = {}; + + obj['og:type'] = 'objectname'; + obj['og:title'] = 'Some title'; + obj['og:url'] = 'https://en.wikipedia.org/wiki/Main_Page'; + obj['og:description'] = 'Some description.'; + + var ap = {}; + + ap['expires_in'] = 3600; + + var options = { + method: 'share_open_graph', // Required + action: 'actionname', // Required + action_properties: JSON.stringify(ap), // Optional + object: JSON.stringify(obj) // Required + }; + } + +In case you want to use custom actions/objects, just prepend the app namespace to the name (E.g: ` obj['og:type'] = 'appnamespace:objectname' `, `action: 'appnamespace:actionname'`. The namespace of a Facebook app is found on the Settings page. + -For options information see: [Facebook feed dialog documentation](https://developers.facebook.com/docs/sharing/reference/feed-dialog/v2.0), [Facebook share dialog documentation](https://developers.facebook.com/docs/sharing/reference/share-dialog) +For options information see: [Facebook share dialog documentation](https://developers.facebook.com/docs/sharing/reference/share-dialog) [Facebook send dialog documentation](https://developers.facebook.com/docs/sharing/reference/send-dialog) Success function returns an Object with `postId` as String or `from` and `to` information when doing `apprequest`. Failure function returns an error String. @@ -134,7 +235,7 @@ For more information see: - Graph Explorer - [https://developers.facebook.com/tools/explorer](https://developers.facebook.com/tools/explorer) - Graph API - [https://developers.facebook.com/docs/graph-api/](https://developers.facebook.com/docs/graph-api/) -# Events +### Events App events allow you to understand the makeup of users engaging with your app, measure the performance of your Facebook mobile app ads, and reach specific sets of your users with Facebook mobile app ads. @@ -146,7 +247,7 @@ Activation events are automatically tracked for you in the plugin. Events are listed on the [insights page](https://www.facebook.com/insights/) -### Log an Event +#### Log an Event `logEvent(String name, Object params, Number valueToSum, Function success, Function failure)` @@ -154,98 +255,116 @@ Events are listed on the [insights page](https://www.facebook.com/insights/) - **params**, extra data to log with the event (is optional) - **valueToSum**, a property which is an arbitrary number that can represent any value (e.g., a price or a quantity). When reported, all of the valueToSum properties will be summed together. For example, if 10 people each purchased one item that cost $10 (and passed in valueToSum) then they would be summed to report a number of $100. (is optional) -### Log a Purchase +#### Log a Purchase `logPurchase(Number value, String currency, Function success, Function failure)` **NOTE:** Both parameters are required. The currency specification is expected to be an [ISO 4217 currency code](http://en.wikipedia.org/wiki/ISO_4217) -## Sample Code +#### Manually log activation events + +`activateApp(Function success, Function failure)` ### Login In your `onDeviceReady` event add the following - var fbLoginSuccess = function (userData) { - alert("UserInfo: " + JSON.stringify(userData)); - } +```js +var fbLoginSuccess = function (userData) { + console.log("UserInfo: ", userData); +} - facebookConnectPlugin.login(["public_profile"], - fbLoginSuccess, - function (error) { alert("" + error) } - ); +facebookConnectPlugin.login(["public_profile"], fbLoginSuccess, + function loginError (error) { + console.error(error) + } +); +``` ### Get Access Token If you need the Facebook access token (for example, for validating the login on server side), do: +```js +var fbLoginSuccess = function (userData) { + console.log("UserInfo: ", userData); + facebookConnectPlugin.getAccessToken(function(token) { + console.log("Token: " + token); + }); +} - var fbLoginSuccess = function (userData) { - alert("UserInfo: " + JSON.stringify(userData)); - facebookConnectPlugin.getAccessToken(function(token) { - alert("Token: " + token); - }, function(err) { - alert("Could not get access token: " + err); - }); - } - - facebookConnectPlugin.login(["public_profile"], - fbLoginSuccess, - function (error) { alert("" + error) } - ); +facebookConnectPlugin.login(["public_profile"], fbLoginSuccess, + function (error) { + console.error(error) + } +); +``` ### Get Status and Post-to-wall For a more instructive example change the above `fbLoginSuccess` to; - var fbLoginSuccess = function (userData) { - alert("UserInfo: " + JSON.stringify(userData)); - facebookConnectPlugin.getLoginStatus( - function (status) { - alert("current status: " + JSON.stringify(status)); - - var options = { method:"feed" }; - facebookConnectPlugin.showDialog(options, - function (result) { - alert("Posted. " + JSON.stringify(result)); }, - function (e) { - alert("Failed: " + e); - }); - } - ); - }; +```js +var fbLoginSuccess = function (userData) { + console.log("UserInfo: ", userData); + facebookConnectPlugin.getLoginStatus(function onLoginStatus (status) { + console.log("current status: ", status); + facebookConnectPlugin.showDialog({ + method: "share" + }, function onShareSuccess (result) { + console.log("Posted. ", result); + }); + }); +}; +``` ### Getting a User's Birthday Using the graph api this is a very simple task: - facebookConnectPlugin.api("/?fields=id,email", ["user_birthday"], - function (result) { - alert("Result: " + JSON.stringify(result)); - /* alerts: - { - "id": "000000123456789", - "email": "myemail@example.com" - } - */ - }, - function (error) { - alert("Failed: " + error); - }); +```js +facebookConnectPlugin.api("/?fields=id,email", ["user_birthday"], + function onSuccess (result) { + console.log("Result: ", result); + /* logs: + { + "id": "000000123456789", + "email": "myemail@example.com" + } + */ + }, function onError (error) { + console.error("Failed: ", error); + } +); +``` ### Publish a Photo Send a photo to a user's feed +```js +facebookConnectPlugin.showDialog({ + method: "share", + picture:'https://www.google.co.jp/logos/doodles/2014/doodle-4-google-2014-japan-winner-5109465267306496.2-hp.png', + name:'Test Post', + message:'First photo post', + caption: 'Testing using phonegap plugin', + description: 'Posting photo using phonegap facebook plugin' + }, function (response) { + console.log(response) + }, function (response) { + console.log(response) + } +); ``` -facebookConnectPlugin.showDialog( - { - method: "feed", - picture:'https://www.google.co.jp/logos/doodles/2014/doodle-4-google-2014-japan-winner-5109465267306496.2-hp.png', - name:'Test Post', - message:'First photo post', - caption: 'Testing using phonegap plugin', - description: 'Posting photo using phonegap facebook plugin' - }, - function (response) { alert(JSON.stringify(response)) }, - function (response) { alert(JSON.stringify(response)) }); + +### Hybrid Mobile App Events + +Starting from Facebook SDK v4.34 for both iOS and Android, there is a new way of converting pixel events into mobile app events. For more information: [https://developers.facebook.com/docs/app-events/hybrid-app-events/](https://developers.facebook.com/docs/app-events/hybrid-app-events/) + +In order to enable this feature in your cordova app, please set the *FACEBOOK_HYBRID_APP_EVENTS* variable to "true"(default is false): +```bash +$ cordova plugin add cordova-plugin-facebook4 --save --variable APP_ID="123456789" --variable APP_NAME="myApplication" --variable FACEBOOK_HYBRID_APP_EVENTS="true" ``` +Please check [this repo](https://github.com/msencer/fb_hybrid_app_events_sample) for an example app using this feature. + +**NOTE(iOS):** This feature only works with WKWebView so until [Cordova iOS 5 is relased](https://cordova.apache.org/news/2018/08/01/future-cordova-ios-webview.html), an additional plugin (e.g cordova-plugin-wkwebview-engine) is needed. diff --git a/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md similarity index 92% rename from TROUBLESHOOTING.md rename to docs/TROUBLESHOOTING.md index 6ba1c2abc..df3febe59 100644 --- a/TROUBLESHOOTING.md +++ b/docs/TROUBLESHOOTING.md @@ -1,6 +1,6 @@ # Troubleshooting -Can't solve your issue here? Check the [existing issues](https://github.com/Wizcorp/phonegap-facebook-plugin/issues) on Github. If you still cannot find a solution please [create and issue after reading the contributing guide](CONTRIBUTING.md). +Can't solve your issue here? Check the [existing issues](https://github.com/Wizcorp/phonegap-facebook-plugin/issues) on Github. If you still cannot find a solution please [create and issue after reading the contributing guide](../CONTRIBUTING.md). When creating a Github issue **remember to**: @@ -9,8 +9,8 @@ When creating a Github issue **remember to**: - Provide a log (Xcode console or adb logcat) ### Troubleshooting contents -- [**General**](#general) - - [How do I Build without Eclipse?](#how-do-i-build-without-eclipse) +- [**General**](#general) + - [How do I Build without Eclipse?](#how-do-i-build-without-eclipse) - [How do I Add a Like Button?](#how-do-i-add-a-like-button) - [Where is the init API?](#where-is-the-init-api) - [How to install with NPM PhoneGap?](#how-to-install-with-npm-phonegap) @@ -25,6 +25,7 @@ When creating a Github issue **remember to**: - [**iOS**](#ios) - [Missing FacebookConnectPlugin](#missing-facebookconnectplugin) - [Login Always Opens Web Dialog Even Though Native App is Installed](#login-always-opens-web-dialog-even-though-native-app-is-installed) + - [Login fails with devices running iOS 10+](#login-fails-with-devices-running-ios-10) ## General ### How do I Build without Eclipse? @@ -37,7 +38,7 @@ When creating a Github issue **remember to**: - Problem - I am trying to implement like button functionality in my app. Whenever user likes my facebook page, user will get rewards. So i have tried to implement this like button functionality as described here: [https://developers.facebook.com/docs/plugins/like-button](https://developers.facebook.com/docs/plugins/like-button) - + - Solution - It won't work for native apps because: [https://developers.facebook.com/docs/ios/like-button/](https://developers.facebook.com/docs/ios/like-button/) is not supported yet. **HOWEVER**; it can be done with the graph API [http://stackoverflow.com/questions/11915269/built-in-like-button-facebook-sdk-3-0](http://stackoverflow.com/questions/11915269/built-in-like-button-facebook-sdk-3-0) Things you have to take care of are : @@ -64,7 +65,7 @@ The better way to understand this little "workflow" is to manipulate the Graph A - Problem - I'm trying to install via https the Facebook plugin in iOS, but when I try the following line: - + `sudo phonegap local plugin add https://github.com/phonegap/phonegap-facebook-plugin.git --variable APP_ID="12345678910" --variable APP_NAME="MyAPP"` Im getting the message "[error] Variable(s) missing: APP_ID, APP_NAME" @@ -80,7 +81,7 @@ Im getting the message "[error] Variable(s) missing: APP_ID, APP_NAME" ## Android ### No Reply From Login? - + - Problem - **facebookConnectPlugin.login doesn't call neither success nor faillure methods.** - When I'm disconnected from Facebook and don't have the native app, the iframe fallback is blank. Checking on chrome inspector, the elements are set to display: none. @@ -89,9 +90,9 @@ Im getting the message "[error] Variable(s) missing: APP_ID, APP_NAME" ``` try { - PackageInfo info = + PackageInfo info = cordova.getActivity().getPackageManager().getPackageInfo("com.goapes.golearn", PackageManager.GET_SIGNATURES); - + for (Signature signature : info.signatures) { MessageDigest md = MessageDigest.getInstance("SHA"); md.update(signature.toByteArray()); @@ -192,7 +193,7 @@ requestsDialog.build().show(); 3. Ensure that the following file is added under "Compile Sources": - `FacebookConnectPlugin.m` 4. Ensure that the following is added under "Link Binary With Libraris": - - `FacebookSDK.framework` + - `FacebookSDK.framework` - `libsqlite3.dylib` - `Social.framework` - `Accounts.framework` @@ -205,4 +206,11 @@ Cordova and plugman seems to have some problems adding frameworks etc. when re-i - Problem: - Calling the login function always opens the web dialog even though the native app is installed on the device. - Solution: - - Switch "deep linking" to ON in the Facebook developer website settings for your application + - Switch "deep linking" to ON in the Facebook developer website settings for your application + +### Login fails with devices running iOS 10 +- problem: + - even if login works properly with older iOS versions, on iOS 10+, error callback is always called +- Solution: + - Give the iOS application access to the Keychain Sharing capability before building it as instructed in + [http://stackoverflow.com/questions/38689631/how-to-use-facebook-ios-sdk-on-ios-10](http://stackoverflow.com/questions/38689631/how-to-use-facebook-ios-sdk-on-ios-10). diff --git a/docs/android/README.md b/docs/android/README.md new file mode 100644 index 000000000..5e78bbfb3 --- /dev/null +++ b/docs/android/README.md @@ -0,0 +1,41 @@ +# Facebook Requirements and Set-Up [Android] + +To use this plugin you will need to make sure you've registered your Facebook app with Facebook and have an APP_ID (https://developers.facebook.com/apps). + +If you plan on rolling this out on Android, please note that you will need to [generate a hash of your Android key(s) and submit those to the Developers page on Facebook](https://developers.facebook.com/docs/android/getting-started) to get it working. Furthermore, if you are generating this hash on Windows (specifically 64 bit versions), please use version 0.9.8e or 0.9.8d of [OpenSSL for Windows](http://code.google.com/p/openssl-for-windows/downloads/list) and *not* 0.9.8k. Big ups to [fernandomatos](http://github.com/fernandomatos) for pointing this out! + +## Install + +This plugin requires [Cordova CLI](https://cordova.apache.org/docs/en/5.0.0/guide_cli_index.md.html) + +To install the plugin in your app, execute the following (replace variables where necessary): +```sh +# Create initial Cordova app +$ cordova create myApp +$ cd myApp/ +$ cordova platform add android + +# Remember to replace APP_ID and APP_NAME variables +$ cordova plugin add https://github.com/jeduan/cordova-plugin-facebook4 --save --variable APP_ID="123456789" --variable APP_NAME="myApplication" +``` + +You can now use the plugin right away + +### Older Cordova CLI + +In order to be compatible with older Cordova CLI version than v7, the property `FACEBOOK_ANDROID_SDK_VERSION` has to be specified in the config.xml (because otherwise it won't pick the default value, see [#650](https://github.com/jeduan/cordova-plugin-facebook4/issues/650)). + +You could add the property manually or specify it when you install the plugin, see the cmd below in section "In case of conflict" + +Of course if you could, you could update your Cordova CLI and avoid specifying explicitly this option + +### In case of conflict + +If you would face version conflicts regarding the Facebook SDK with other plugins used in your project while installing `cordova-plugin-facebook4` for your ANDROID platform, you would be able to specify a specific Facebook SDK version for ANDROID using the variable `FACEBOOK_ANDROID_SDK_VERSION` + +```sh +$ cordova plugin add https://github.com/jeduan/cordova-plugin-facebook4 --save --variable APP_ID="123456789" --variable APP_NAME="myApplication" --variable FACEBOOK_ANDROID_SDK_VERSION="X.YY.Z" +``` + +where `X.YY.Z` could be for example 4.25.0. More information about the Facebook SDK for Android and list of their versions in the [Facebook documentation](https://developers.facebook.com/docs/android) + diff --git a/docs/browser/LOCALHOST_GUIDE.md b/docs/browser/LOCALHOST_GUIDE.md new file mode 100644 index 000000000..d9e68b59a --- /dev/null +++ b/docs/browser/LOCALHOST_GUIDE.md @@ -0,0 +1,16 @@ +# Test Your Facebook Web App on localhost + +A quick guide to testing your Facebook app on localhost + +### Steps + +**You must complete the [steps to create a Cordova browser application](README.md) and add the plugin first.** + + - Install `http-server` with node by issuing this command: `npm install -g http-server` (you may have to use sudo for this to work depending on your system setup) + - Open terminal and cd to your cordova project + - Run this command to run your server + +```sh +$ cordova prepare && http-server platforms/browser/www +``` + - open your browser and go to `localhost:8080/` diff --git a/docs/browser/README.md b/docs/browser/README.md new file mode 100644 index 000000000..de0300ff5 --- /dev/null +++ b/docs/browser/README.md @@ -0,0 +1,33 @@ +# Facebook Requirements and Set-Up [Web App] + +To use this plugin you will need to make sure you've registered your Facebook app with Facebook and have an `APP_ID` [https://developers.facebook.com/apps](https://developers.facebook.com/apps). + +This guide is for browser only. View the other guides for native setup. + +`www/facebookConnectPlugin.js` contains the JavaScript SDK and API file. The API matches as close as possible to the native APIs. + +## Example App + +For localhost testing on OS X see the [Localhost guide](LOCALHOST_GUIDE.md) + +## Install + +This plugin requires [Cordova CLI](https://cordova.apache.org/docs/en/5.0.0/guide_cli_index.md.html). + +To install the plugin in your app, execute the following (replace variables where necessary): + +```sh +# Create initial Cordova app +$ cordova create myApp +$ cd myApp/ +$ cordova platform add browser + +# Remember to replace APP_ID and APP_NAME variables +$ cordova plugin add cordova-plugin-facebook4 --save --variable APP_ID="123456789" --variable APP_NAME="myApplication" +``` + +## Setup + +- In your facebook develop website settings page, add your server's domain to app domain (or localhost for testing). + +![image](app_domain_setup.png) diff --git a/platforms/browser/app_domain_setup.png b/docs/browser/app_domain_setup.png similarity index 100% rename from platforms/browser/app_domain_setup.png rename to docs/browser/app_domain_setup.png diff --git a/docs/ios/README.md b/docs/ios/README.md new file mode 100644 index 000000000..58224d754 --- /dev/null +++ b/docs/ios/README.md @@ -0,0 +1,57 @@ +# Facebook Requirements and Set-Up [iOS] + +To use this plugin you will need to make sure you've registered your Facebook app with Facebook and have an `APP_ID` [https://developers.facebook.com/apps](https://developers.facebook.com/apps). + +If you plan on rolling this out on iOS, please note that you will need to ensure that you have properly set up your Native iOS App settings on the [Facebook App Dashboard](http://developers.facebook.com/apps). Please see the [Getting Started with the Facebook SDK](https://developers.facebook.com/docs/ios/getting-started/): Create a Facebook App section, for more details on this. + +### Installation + +This plugin requires [Cordova CLI](http://cordova.apache.org/docs/en/3.5.0/guide_cli_index.md.html). + +To install the plugin in your app, execute the following (replace variables where necessary): + +```sh +# Create initial Cordova app +$ cordova create myApp +$ cd myApp/ +$ cordova platform add ios + +# Remember to replace APP_ID and APP_NAME variables +$ cordova plugin add cordova-plugin-facebook4 --save --variable APP_ID="123456789" --variable APP_NAME="myApplication" +``` + +### Cocoapods + +This plugin use the [CocoaPods dependency manager](https://cocoapods.org) in order to satisfy the iOS Facebook SDK library dependencies. + +Therefore please make sure you have Cocoapods installed in your iOS build environment - setup instructions can be found [here](https://cocoapods.org/). Also make sure your local Cocoapods repo is up-to-date by running `pod repo update`. + +If building your project in Xcode, you need to open `YourProject.xcworkspace` (not `YourProject.xcodeproj`) so both your Cordova app project and the Pods project will be loaded into Xcode. + +You can list the pod dependencies in your Cordova iOS project by installing [cocoapods-dependencies](https://github.com/segiddins/cocoapods-dependencies): + +```bash +sudo gem install cocoapods-dependencies +cd platforms/ios/ +pod dependencies + ``` + +#### Error: pod: Command failed with exit code 31 + +If you install the plugin and face the error `Failed to install 'cordova-plugin-facebook4': Error: pod: Command failed with exit code 31`, it probably means that your local Pod repo is not up-to-date. In order to solve the problem, prior the installation, run th following command in your platform to update your Pod repo: + +```bash +pod update +``` + +#### 'FBSDKCoreKit/FBSDKCoreKit.h' file not found + +If you are using Cordova iOS < v5, you might face the error `'FBSDKCoreKit/FBSDKCoreKit.h' file not found`. To overcome this problem, edit the `plugin.xml` of the plugin in order to fetch de Facebook iOS SDK by adding the following `framework` references: + +``` + + + +``` + +Replace `X.Y.Z` with the Facebook iOS SDK and remove and add your platform again. diff --git a/facebookConnectPlugin.js b/facebookConnectPlugin.js deleted file mode 100644 index c6afb167e..000000000 --- a/facebookConnectPlugin.js +++ /dev/null @@ -1,229 +0,0 @@ -"use strict"; - -/* - * @author Ally Ogilvie - * @copyright Wizcorp Inc. [ Incorporated Wizards ] 2014 - * @file - facebookConnectPlugin.js - * @about - JavaScript interface for PhoneGap bridge to Facebook Connect SDK - * - * - */ - -if (cordova.platformId == "browser") { - - var facebookConnectPlugin = { - - getLoginStatus: function (s, f) { - // Try will catch errors when SDK has not been init - try { - FB.getLoginStatus(function (response) { - s(response); - }); - } catch (error) { - if (!f) { - console.error(error.message); - } else { - f(error.message); - } - } - }, - - showDialog: function (options, s, f) { - - if (!options.name) { - options.name = ""; - } - if (!options.message) { - options.message = ""; - } - if (!options.caption) { - options.caption = ""; - } - if (!options.description) { - options.description = ""; - } - if (!options.href) { - options.href = ""; - } - if (!options.picture) { - options.picture = ""; - } - - // Try will catch errors when SDK has not been init - try { - FB.ui(options, - function (response) { - if (response && (response.request || !response.error_code)) { - s(response); - } else { - f(response); - } - }); - } catch (error) { - if (!f) { - console.error(error.message); - } else { - f(error.message); - } - } - }, - // Attach this to a UI element, this requires user interaction. - login: function (permissions, s, f) { - // JS SDK takes an object here but the native SDKs use array. - var permissionObj = {}; - if (permissions && permissions.length > 0) { - permissionObj.scope = permissions.toString(); - } - - FB.login(function (response) { - if (response.authResponse) { - s(response); - } else { - f(response.status); - } - }, permissionObj); - }, - - getAccessToken: function (s, f) { - var response = FB.getAccessToken(); - if (!response) { - if (!f) { - console.error("NO_TOKEN"); - } else { - f("NO_TOKEN"); - } - } else { - s(response); - } - }, - - logEvent: function (eventName, params, valueToSum, s, f) { - // AppEvents are not avaliable in JS. - s(); - }, - - logPurchase: function (value, currency, s, f) { - // AppEvents are not avaliable in JS. - s(); - }, - - logout: function (s, f) { - // Try will catch errors when SDK has not been init - try { - FB.logout( function (response) { - s(response); - }); - } catch (error) { - if (!f) { - console.error(error.message); - } else { - f(error.message); - } - } - }, - - api: function (graphPath, permissions, s, f) { - // JS API does not take additional permissions - - // Try will catch errors when SDK has not been init - try { - FB.api(graphPath, function (response) { - if (response.error) { - f(response); - } else { - s(response); - } - }); - } catch (error) { - if (!f) { - console.error(error.message); - } else { - f(error.message); - } - } - }, - - // Browser wrapper API ONLY - browserInit: function (appId, version) { - if (!version) { - version = "v2.0"; - } - FB.init({ - appId : appId, - cookie : true, - xfbml : true, - version : version - }); - } - }; - - // Bake in the JS SDK - (function () { - if (!window.FB) { - console.log("launching FB SDK"); - var e = document.createElement('script'); - e.src = document.location.protocol + '//connect.facebook.net/en_US/sdk.js'; - e.async = true; - document.getElementById('fb-root').appendChild(e); - if (!window.FB) { - // Probably not on server, use the sample sdk - e.src = 'phonegap/plugin/facebookConnectPlugin/fbsdk.js'; - document.getElementById('fb-root').appendChild(e); - console.log("Attempt local load: ", e); - } - } - }()); - - module.exports = facebookConnectPlugin; - -} else { - - var exec = require("cordova/exec"); - - var facebookConnectPlugin = { - - getLoginStatus: function (s, f) { - exec(s, f, "FacebookConnectPlugin", "getLoginStatus", []); - }, - - showDialog: function (options, s, f) { - exec(s, f, "FacebookConnectPlugin", "showDialog", [options]); - }, - - login: function (permissions, s, f) { - exec(s, f, "FacebookConnectPlugin", "login", permissions); - }, - - logEvent: function(name, params, valueToSum, s, f) { - // Prevent NSNulls getting into iOS, messes up our [command.argument count] - if (!params && !valueToSum) { - exec(s, f, "FacebookConnectPlugin", "logEvent", [name]); - } else if (params && !valueToSum) { - exec(s, f, "FacebookConnectPlugin", "logEvent", [name, params]); - } else if (params && valueToSum) { - exec(s, f, "FacebookConnectPlugin", "logEvent", [name, params, valueToSum]); - } else { - f("Invalid arguments"); - } - }, - - logPurchase: function(value, currency, s, f) { - exec(s, f, "FacebookConnectPlugin", "logPurchase", [value, currency]); - }, - - getAccessToken: function(s, f) { - exec(s, f, "FacebookConnectPlugin", "getAccessToken", []); - }, - - logout: function (s, f) { - exec(s, f, "FacebookConnectPlugin", "logout", []); - }, - - api: function (graphPath, permissions, s, f) { - if (!permissions) { permissions = []; } - exec(s, f, "FacebookConnectPlugin", "graphApi", [graphPath, permissions]); - } - }; - - module.exports = facebookConnectPlugin; -} diff --git a/package.json b/package.json new file mode 100644 index 000000000..f6c11f777 --- /dev/null +++ b/package.json @@ -0,0 +1,29 @@ +{ + "name": "cordova-plugin-facebook4", + "version": "6.4.0", + "description": "Cordova Facebook SDK 4 Plugin", + "cordova": { + "id": "cordova-plugin-facebook4", + "platforms": [ + "android", + "ios", + "browser" + ] + }, + "repository": { + "type": "git", + "url": "https://github.com/jeduan/cordova-plugin-facebook4" + }, + "keywords": [ + "cordova", + "facebook", + "ecosystem:cordova", + "cordova-android", + "cordova-ios", + "cordova-browser", + "facebook-sdk", + "social", + "sharing" + ], + "license": "Apache-2.0" +} diff --git a/platforms/android/.classpath b/platforms/android/.classpath deleted file mode 100644 index 51769745b..000000000 --- a/platforms/android/.classpath +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/platforms/android/.project b/platforms/android/.project deleted file mode 100644 index 602b9b103..000000000 --- a/platforms/android/.project +++ /dev/null @@ -1,33 +0,0 @@ - - - HelloCordova - - - - - - com.android.ide.eclipse.adt.ResourceManagerBuilder - - - - - com.android.ide.eclipse.adt.PreCompilerBuilder - - - - - org.eclipse.jdt.core.javabuilder - - - - - com.android.ide.eclipse.adt.ApkBuilder - - - - - - com.android.ide.eclipse.adt.AndroidNature - org.eclipse.jdt.core.javanature - - diff --git a/platforms/android/AndroidManifest.xml b/platforms/android/AndroidManifest.xml deleted file mode 100644 index 6ca5ebad6..000000000 --- a/platforms/android/AndroidManifest.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/platforms/android/CordovaLib/.classpath b/platforms/android/CordovaLib/.classpath deleted file mode 100644 index 51769745b..000000000 --- a/platforms/android/CordovaLib/.classpath +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/platforms/android/CordovaLib/.project b/platforms/android/CordovaLib/.project deleted file mode 100644 index 4a53c2f66..000000000 --- a/platforms/android/CordovaLib/.project +++ /dev/null @@ -1,33 +0,0 @@ - - - HelloCordova-CordovaLib - - - - - - com.android.ide.eclipse.adt.ResourceManagerBuilder - - - - - com.android.ide.eclipse.adt.PreCompilerBuilder - - - - - org.eclipse.jdt.core.javabuilder - - - - - com.android.ide.eclipse.adt.ApkBuilder - - - - - - com.android.ide.eclipse.adt.AndroidNature - org.eclipse.jdt.core.javanature - - diff --git a/platforms/android/CordovaLib/AndroidManifest.xml b/platforms/android/CordovaLib/AndroidManifest.xml deleted file mode 100755 index 15a970228..000000000 --- a/platforms/android/CordovaLib/AndroidManifest.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - diff --git a/platforms/android/CordovaLib/build.xml b/platforms/android/CordovaLib/build.xml deleted file mode 100644 index 18829c42e..000000000 --- a/platforms/android/CordovaLib/build.xml +++ /dev/null @@ -1,92 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/platforms/android/CordovaLib/proguard-project.txt b/platforms/android/CordovaLib/proguard-project.txt deleted file mode 100644 index f2fe1559a..000000000 --- a/platforms/android/CordovaLib/proguard-project.txt +++ /dev/null @@ -1,20 +0,0 @@ -# To enable ProGuard in your project, edit project.properties -# to define the proguard.config property as described in that file. -# -# Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in ${sdk.dir}/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the ProGuard -# include property in project.properties. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# Add any project specific keep options here: - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} diff --git a/platforms/android/CordovaLib/project.properties b/platforms/android/CordovaLib/project.properties deleted file mode 100644 index 9fe04f3b8..000000000 --- a/platforms/android/CordovaLib/project.properties +++ /dev/null @@ -1,16 +0,0 @@ -# This file is automatically generated by Android Tools. -# Do not modify this file -- YOUR CHANGES WILL BE ERASED! -# -# This file must be checked in Version Control Systems. -# -# To customize properties used by the Ant build system use, -# "ant.properties", and override values to adapt the script to your -# project structure. - -# Indicates whether an apk should be generated for each density. -split.density=false -# Project target. -target=android-19 -apk-configurations= -renderscript.opt.level=O0 -android.library=true diff --git a/platforms/android/CordovaLib/src/com/squareup/okhttp/Address.java b/platforms/android/CordovaLib/src/com/squareup/okhttp/Address.java deleted file mode 100644 index cd41ac99c..000000000 --- a/platforms/android/CordovaLib/src/com/squareup/okhttp/Address.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed 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. - */ -package com.squareup.okhttp; - -import java.net.Proxy; -import java.net.UnknownHostException; -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.SSLSocketFactory; - -import static com.squareup.okhttp.internal.Util.equal; - -/** - * A specification for a connection to an origin server. For simple connections, - * this is the server's hostname and port. If an explicit proxy is requested (or - * {@link Proxy#NO_PROXY no proxy} is explicitly requested), this also includes - * that proxy information. For secure connections the address also includes the - * SSL socket factory and hostname verifier. - * - *

HTTP requests that share the same {@code Address} may also share the same - * {@link Connection}. - */ -public final class Address { - final Proxy proxy; - final String uriHost; - final int uriPort; - final SSLSocketFactory sslSocketFactory; - final HostnameVerifier hostnameVerifier; - - public Address(String uriHost, int uriPort, SSLSocketFactory sslSocketFactory, - HostnameVerifier hostnameVerifier, Proxy proxy) throws UnknownHostException { - if (uriHost == null) throw new NullPointerException("uriHost == null"); - if (uriPort <= 0) throw new IllegalArgumentException("uriPort <= 0: " + uriPort); - this.proxy = proxy; - this.uriHost = uriHost; - this.uriPort = uriPort; - this.sslSocketFactory = sslSocketFactory; - this.hostnameVerifier = hostnameVerifier; - } - - /** Returns the hostname of the origin server. */ - public String getUriHost() { - return uriHost; - } - - /** - * Returns the port of the origin server; typically 80 or 443. Unlike - * may {@code getPort()} accessors, this method never returns -1. - */ - public int getUriPort() { - return uriPort; - } - - /** - * Returns the SSL socket factory, or null if this is not an HTTPS - * address. - */ - public SSLSocketFactory getSslSocketFactory() { - return sslSocketFactory; - } - - /** - * Returns the hostname verifier, or null if this is not an HTTPS - * address. - */ - public HostnameVerifier getHostnameVerifier() { - return hostnameVerifier; - } - - /** - * Returns this address's explicitly-specified HTTP proxy, or null to - * delegate to the HTTP client's proxy selector. - */ - public Proxy getProxy() { - return proxy; - } - - @Override public boolean equals(Object other) { - if (other instanceof Address) { - Address that = (Address) other; - return equal(this.proxy, that.proxy) - && this.uriHost.equals(that.uriHost) - && this.uriPort == that.uriPort - && equal(this.sslSocketFactory, that.sslSocketFactory) - && equal(this.hostnameVerifier, that.hostnameVerifier); - } - return false; - } - - @Override public int hashCode() { - int result = 17; - result = 31 * result + uriHost.hashCode(); - result = 31 * result + uriPort; - result = 31 * result + (sslSocketFactory != null ? sslSocketFactory.hashCode() : 0); - result = 31 * result + (hostnameVerifier != null ? hostnameVerifier.hashCode() : 0); - result = 31 * result + (proxy != null ? proxy.hashCode() : 0); - return result; - } -} diff --git a/platforms/android/CordovaLib/src/com/squareup/okhttp/Connection.java b/platforms/android/CordovaLib/src/com/squareup/okhttp/Connection.java deleted file mode 100644 index 6a6c84dc7..000000000 --- a/platforms/android/CordovaLib/src/com/squareup/okhttp/Connection.java +++ /dev/null @@ -1,291 +0,0 @@ -/* - * 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. - */ -package com.squareup.okhttp; - -import com.squareup.okhttp.internal.Platform; -import com.squareup.okhttp.internal.http.HttpAuthenticator; -import com.squareup.okhttp.internal.http.HttpEngine; -import com.squareup.okhttp.internal.http.HttpTransport; -import com.squareup.okhttp.internal.http.RawHeaders; -import com.squareup.okhttp.internal.http.SpdyTransport; -import com.squareup.okhttp.internal.spdy.SpdyConnection; -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.Closeable; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.Proxy; -import java.net.Socket; -import java.net.URL; -import java.util.Arrays; -import javax.net.ssl.SSLSocket; - -import static java.net.HttpURLConnection.HTTP_OK; -import static java.net.HttpURLConnection.HTTP_PROXY_AUTH; - -/** - * Holds the sockets and streams of an HTTP, HTTPS, or HTTPS+SPDY connection, - * which may be used for multiple HTTP request/response exchanges. Connections - * may be direct to the origin server or via a proxy. - * - *

Typically instances of this class are created, connected and exercised - * automatically by the HTTP client. Applications may use this class to monitor - * HTTP connections as members of a {@link ConnectionPool connection pool}. - * - *

Do not confuse this class with the misnamed {@code HttpURLConnection}, - * which isn't so much a connection as a single request/response exchange. - * - *

Modern TLS

- * There are tradeoffs when selecting which options to include when negotiating - * a secure connection to a remote host. Newer TLS options are quite useful: - *
    - *
  • Server Name Indication (SNI) enables one IP address to negotiate secure - * connections for multiple domain names. - *
  • Next Protocol Negotiation (NPN) enables the HTTPS port (443) to be used - * for both HTTP and SPDY transports. - *
- * Unfortunately, older HTTPS servers refuse to connect when such options are - * presented. Rather than avoiding these options entirely, this class allows a - * connection to be attempted with modern options and then retried without them - * should the attempt fail. - */ -public final class Connection implements Closeable { - private static final byte[] NPN_PROTOCOLS = new byte[] { - 6, 's', 'p', 'd', 'y', '/', '3', - 8, 'h', 't', 't', 'p', '/', '1', '.', '1' - }; - private static final byte[] SPDY3 = new byte[] { - 's', 'p', 'd', 'y', '/', '3' - }; - private static final byte[] HTTP_11 = new byte[] { - 'h', 't', 't', 'p', '/', '1', '.', '1' - }; - - private final Route route; - - private Socket socket; - private InputStream in; - private OutputStream out; - private boolean connected = false; - private SpdyConnection spdyConnection; - private int httpMinorVersion = 1; // Assume HTTP/1.1 - private long idleStartTimeNs; - - public Connection(Route route) { - this.route = route; - } - - public void connect(int connectTimeout, int readTimeout, TunnelRequest tunnelRequest) - throws IOException { - if (connected) { - throw new IllegalStateException("already connected"); - } - connected = true; - socket = (route.proxy.type() != Proxy.Type.HTTP) ? new Socket(route.proxy) : new Socket(); - socket.connect(route.inetSocketAddress, connectTimeout); - socket.setSoTimeout(readTimeout); - in = socket.getInputStream(); - out = socket.getOutputStream(); - - if (route.address.sslSocketFactory != null) { - upgradeToTls(tunnelRequest); - } - - // Use MTU-sized buffers to send fewer packets. - int mtu = Platform.get().getMtu(socket); - in = new BufferedInputStream(in, mtu); - out = new BufferedOutputStream(out, mtu); - } - - /** - * Create an {@code SSLSocket} and perform the TLS handshake and certificate - * validation. - */ - private void upgradeToTls(TunnelRequest tunnelRequest) throws IOException { - Platform platform = Platform.get(); - - // Make an SSL Tunnel on the first message pair of each SSL + proxy connection. - if (requiresTunnel()) { - makeTunnel(tunnelRequest); - } - - // Create the wrapper over connected socket. - socket = route.address.sslSocketFactory - .createSocket(socket, route.address.uriHost, route.address.uriPort, true /* autoClose */); - SSLSocket sslSocket = (SSLSocket) socket; - if (route.modernTls) { - platform.enableTlsExtensions(sslSocket, route.address.uriHost); - } else { - platform.supportTlsIntolerantServer(sslSocket); - } - - if (route.modernTls) { - platform.setNpnProtocols(sslSocket, NPN_PROTOCOLS); - } - - // Force handshake. This can throw! - sslSocket.startHandshake(); - - // Verify that the socket's certificates are acceptable for the target host. - if (!route.address.hostnameVerifier.verify(route.address.uriHost, sslSocket.getSession())) { - throw new IOException("Hostname '" + route.address.uriHost + "' was not verified"); - } - - out = sslSocket.getOutputStream(); - in = sslSocket.getInputStream(); - - byte[] selectedProtocol; - if (route.modernTls - && (selectedProtocol = platform.getNpnSelectedProtocol(sslSocket)) != null) { - if (Arrays.equals(selectedProtocol, SPDY3)) { - sslSocket.setSoTimeout(0); // SPDY timeouts are set per-stream. - spdyConnection = new SpdyConnection.Builder(route.address.getUriHost(), true, in, out) - .build(); - } else if (!Arrays.equals(selectedProtocol, HTTP_11)) { - throw new IOException( - "Unexpected NPN transport " + new String(selectedProtocol, "ISO-8859-1")); - } - } - } - - /** Returns true if {@link #connect} has been attempted on this connection. */ - public boolean isConnected() { - return connected; - } - - @Override public void close() throws IOException { - socket.close(); - } - - /** Returns the route used by this connection. */ - public Route getRoute() { - return route; - } - - /** - * Returns the socket that this connection uses, or null if the connection - * is not currently connected. - */ - public Socket getSocket() { - return socket; - } - - /** Returns true if this connection is alive. */ - public boolean isAlive() { - return !socket.isClosed() && !socket.isInputShutdown() && !socket.isOutputShutdown(); - } - - public void resetIdleStartTime() { - if (spdyConnection != null) { - throw new IllegalStateException("spdyConnection != null"); - } - this.idleStartTimeNs = System.nanoTime(); - } - - /** Returns true if this connection is idle. */ - public boolean isIdle() { - return spdyConnection == null || spdyConnection.isIdle(); - } - - /** - * Returns true if this connection has been idle for longer than - * {@code keepAliveDurationNs}. - */ - public boolean isExpired(long keepAliveDurationNs) { - return isIdle() && System.nanoTime() - getIdleStartTimeNs() > keepAliveDurationNs; - } - - /** - * Returns the time in ns when this connection became idle. Undefined if - * this connection is not idle. - */ - public long getIdleStartTimeNs() { - return spdyConnection == null ? idleStartTimeNs : spdyConnection.getIdleStartTimeNs(); - } - - /** Returns the transport appropriate for this connection. */ - public Object newTransport(HttpEngine httpEngine) throws IOException { - return (spdyConnection != null) ? new SpdyTransport(httpEngine, spdyConnection) - : new HttpTransport(httpEngine, out, in); - } - - /** - * Returns true if this is a SPDY connection. Such connections can be used - * in multiple HTTP requests simultaneously. - */ - public boolean isSpdy() { - return spdyConnection != null; - } - - public SpdyConnection getSpdyConnection() { - return spdyConnection; - } - - /** - * Returns the minor HTTP version that should be used for future requests on - * this connection. Either 0 for HTTP/1.0, or 1 for HTTP/1.1. The default - * value is 1 for new connections. - */ - public int getHttpMinorVersion() { - return httpMinorVersion; - } - - public void setHttpMinorVersion(int httpMinorVersion) { - this.httpMinorVersion = httpMinorVersion; - } - - /** - * Returns true if the HTTP connection needs to tunnel one protocol over - * another, such as when using HTTPS through an HTTP proxy. When doing so, - * we must avoid buffering bytes intended for the higher-level protocol. - */ - public boolean requiresTunnel() { - return route.address.sslSocketFactory != null && route.proxy.type() == Proxy.Type.HTTP; - } - - /** - * To make an HTTPS connection over an HTTP proxy, send an unencrypted - * CONNECT request to create the proxy connection. This may need to be - * retried if the proxy requires authorization. - */ - private void makeTunnel(TunnelRequest tunnelRequest) throws IOException { - RawHeaders requestHeaders = tunnelRequest.getRequestHeaders(); - while (true) { - out.write(requestHeaders.toBytes()); - RawHeaders responseHeaders = RawHeaders.fromBytes(in); - - switch (responseHeaders.getResponseCode()) { - case HTTP_OK: - return; - case HTTP_PROXY_AUTH: - requestHeaders = new RawHeaders(requestHeaders); - URL url = new URL("https", tunnelRequest.host, tunnelRequest.port, "/"); - boolean credentialsFound = HttpAuthenticator.processAuthHeader(HTTP_PROXY_AUTH, - responseHeaders, requestHeaders, route.proxy, url); - if (credentialsFound) { - continue; - } else { - throw new IOException("Failed to authenticate with proxy"); - } - default: - throw new IOException( - "Unexpected response code for CONNECT: " + responseHeaders.getResponseCode()); - } - } - } -} diff --git a/platforms/android/CordovaLib/src/com/squareup/okhttp/ConnectionPool.java b/platforms/android/CordovaLib/src/com/squareup/okhttp/ConnectionPool.java deleted file mode 100644 index 933bd7379..000000000 --- a/platforms/android/CordovaLib/src/com/squareup/okhttp/ConnectionPool.java +++ /dev/null @@ -1,273 +0,0 @@ -/* - * 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. - */ -package com.squareup.okhttp; - -import com.squareup.okhttp.internal.Platform; -import com.squareup.okhttp.internal.Util; -import java.net.SocketException; -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; -import java.util.ListIterator; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; - -/** - * Manages reuse of HTTP and SPDY connections for reduced network latency. HTTP - * requests that share the same {@link com.squareup.okhttp.Address} may share a - * {@link com.squareup.okhttp.Connection}. This class implements the policy of - * which connections to keep open for future use. - * - *

The {@link #getDefault() system-wide default} uses system properties for - * tuning parameters: - *

    - *
  • {@code http.keepAlive} true if HTTP and SPDY connections should be - * pooled at all. Default is true. - *
  • {@code http.maxConnections} maximum number of idle connections to - * each to keep in the pool. Default is 5. - *
  • {@code http.keepAliveDuration} Time in milliseconds to keep the - * connection alive in the pool before closing it. Default is 5 minutes. - * This property isn't used by {@code HttpURLConnection}. - *
- * - *

The default instance doesn't adjust its configuration as system - * properties are changed. This assumes that the applications that set these - * parameters do so before making HTTP connections, and that this class is - * initialized lazily. - */ -public class ConnectionPool { - private static final int MAX_CONNECTIONS_TO_CLEANUP = 2; - private static final long DEFAULT_KEEP_ALIVE_DURATION_MS = 5 * 60 * 1000; // 5 min - - private static final ConnectionPool systemDefault; - - static { - String keepAlive = System.getProperty("http.keepAlive"); - String keepAliveDuration = System.getProperty("http.keepAliveDuration"); - String maxIdleConnections = System.getProperty("http.maxConnections"); - long keepAliveDurationMs = keepAliveDuration != null ? Long.parseLong(keepAliveDuration) - : DEFAULT_KEEP_ALIVE_DURATION_MS; - if (keepAlive != null && !Boolean.parseBoolean(keepAlive)) { - systemDefault = new ConnectionPool(0, keepAliveDurationMs); - } else if (maxIdleConnections != null) { - systemDefault = new ConnectionPool(Integer.parseInt(maxIdleConnections), keepAliveDurationMs); - } else { - systemDefault = new ConnectionPool(5, keepAliveDurationMs); - } - } - - /** The maximum number of idle connections for each address. */ - private final int maxIdleConnections; - private final long keepAliveDurationNs; - - private final LinkedList connections = new LinkedList(); - - /** We use a single background thread to cleanup expired connections. */ - private final ExecutorService executorService = - new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue()); - private final Callable connectionsCleanupCallable = new Callable() { - @Override public Void call() throws Exception { - List expiredConnections = new ArrayList(MAX_CONNECTIONS_TO_CLEANUP); - int idleConnectionCount = 0; - synchronized (ConnectionPool.this) { - for (ListIterator i = connections.listIterator(connections.size()); - i.hasPrevious(); ) { - Connection connection = i.previous(); - if (!connection.isAlive() || connection.isExpired(keepAliveDurationNs)) { - i.remove(); - expiredConnections.add(connection); - if (expiredConnections.size() == MAX_CONNECTIONS_TO_CLEANUP) break; - } else if (connection.isIdle()) { - idleConnectionCount++; - } - } - - for (ListIterator i = connections.listIterator(connections.size()); - i.hasPrevious() && idleConnectionCount > maxIdleConnections; ) { - Connection connection = i.previous(); - if (connection.isIdle()) { - expiredConnections.add(connection); - i.remove(); - --idleConnectionCount; - } - } - } - for (Connection expiredConnection : expiredConnections) { - Util.closeQuietly(expiredConnection); - } - return null; - } - }; - - public ConnectionPool(int maxIdleConnections, long keepAliveDurationMs) { - this.maxIdleConnections = maxIdleConnections; - this.keepAliveDurationNs = keepAliveDurationMs * 1000 * 1000; - } - - /** - * Returns a snapshot of the connections in this pool, ordered from newest to - * oldest. Waits for the cleanup callable to run if it is currently scheduled. - */ - List getConnections() { - waitForCleanupCallableToRun(); - synchronized (this) { - return new ArrayList(connections); - } - } - - /** - * Blocks until the executor service has processed all currently enqueued - * jobs. - */ - private void waitForCleanupCallableToRun() { - try { - executorService.submit(new Runnable() { - @Override public void run() { - } - }).get(); - } catch (Exception e) { - throw new AssertionError(); - } - } - - public static ConnectionPool getDefault() { - return systemDefault; - } - - /** Returns total number of connections in the pool. */ - public synchronized int getConnectionCount() { - return connections.size(); - } - - /** Returns total number of spdy connections in the pool. */ - public synchronized int getSpdyConnectionCount() { - int total = 0; - for (Connection connection : connections) { - if (connection.isSpdy()) total++; - } - return total; - } - - /** Returns total number of http connections in the pool. */ - public synchronized int getHttpConnectionCount() { - int total = 0; - for (Connection connection : connections) { - if (!connection.isSpdy()) total++; - } - return total; - } - - /** Returns a recycled connection to {@code address}, or null if no such connection exists. */ - public synchronized Connection get(Address address) { - Connection foundConnection = null; - for (ListIterator i = connections.listIterator(connections.size()); - i.hasPrevious(); ) { - Connection connection = i.previous(); - if (!connection.getRoute().getAddress().equals(address) - || !connection.isAlive() - || System.nanoTime() - connection.getIdleStartTimeNs() >= keepAliveDurationNs) { - continue; - } - i.remove(); - if (!connection.isSpdy()) { - try { - Platform.get().tagSocket(connection.getSocket()); - } catch (SocketException e) { - Util.closeQuietly(connection); - // When unable to tag, skip recycling and close - Platform.get().logW("Unable to tagSocket(): " + e); - continue; - } - } - foundConnection = connection; - break; - } - - if (foundConnection != null && foundConnection.isSpdy()) { - connections.addFirst(foundConnection); // Add it back after iteration. - } - - executorService.submit(connectionsCleanupCallable); - return foundConnection; - } - - /** - * Gives {@code connection} to the pool. The pool may store the connection, - * or close it, as its policy describes. - * - *

It is an error to use {@code connection} after calling this method. - */ - public void recycle(Connection connection) { - executorService.submit(connectionsCleanupCallable); - - if (connection.isSpdy()) { - return; - } - - if (!connection.isAlive()) { - Util.closeQuietly(connection); - return; - } - - try { - Platform.get().untagSocket(connection.getSocket()); - } catch (SocketException e) { - // When unable to remove tagging, skip recycling and close. - Platform.get().logW("Unable to untagSocket(): " + e); - Util.closeQuietly(connection); - return; - } - - synchronized (this) { - connections.addFirst(connection); - connection.resetIdleStartTime(); - } - } - - /** - * Shares the SPDY connection with the pool. Callers to this method may - * continue to use {@code connection}. - */ - public void maybeShare(Connection connection) { - executorService.submit(connectionsCleanupCallable); - if (!connection.isSpdy()) { - // Only SPDY connections are sharable. - return; - } - if (connection.isAlive()) { - synchronized (this) { - connections.addFirst(connection); - } - } - } - - /** Close and remove all connections in the pool. */ - public void evictAll() { - List connections; - synchronized (this) { - connections = new ArrayList(this.connections); - this.connections.clear(); - } - - for (Connection connection : connections) { - Util.closeQuietly(connection); - } - } -} diff --git a/platforms/android/CordovaLib/src/com/squareup/okhttp/HttpResponseCache.java b/platforms/android/CordovaLib/src/com/squareup/okhttp/HttpResponseCache.java deleted file mode 100644 index a6d380abf..000000000 --- a/platforms/android/CordovaLib/src/com/squareup/okhttp/HttpResponseCache.java +++ /dev/null @@ -1,693 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed 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. - */ - -package com.squareup.okhttp; - -import com.squareup.okhttp.internal.Base64; -import com.squareup.okhttp.internal.DiskLruCache; -import com.squareup.okhttp.internal.StrictLineReader; -import com.squareup.okhttp.internal.Util; -import com.squareup.okhttp.internal.http.HttpEngine; -import com.squareup.okhttp.internal.http.HttpURLConnectionImpl; -import com.squareup.okhttp.internal.http.HttpsURLConnectionImpl; -import com.squareup.okhttp.internal.http.OkResponseCache; -import com.squareup.okhttp.internal.http.RawHeaders; -import com.squareup.okhttp.internal.http.ResponseHeaders; -import java.io.BufferedWriter; -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.FilterInputStream; -import java.io.FilterOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.UnsupportedEncodingException; -import java.io.Writer; -import java.net.CacheRequest; -import java.net.CacheResponse; -import java.net.HttpURLConnection; -import java.net.ResponseCache; -import java.net.SecureCacheResponse; -import java.net.URI; -import java.net.URLConnection; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.Principal; -import java.security.cert.Certificate; -import java.security.cert.CertificateEncodingException; -import java.security.cert.CertificateException; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLPeerUnverifiedException; - -import static com.squareup.okhttp.internal.Util.US_ASCII; -import static com.squareup.okhttp.internal.Util.UTF_8; - -/** - * Caches HTTP and HTTPS responses to the filesystem so they may be reused, - * saving time and bandwidth. - * - *

Cache Optimization

- * To measure cache effectiveness, this class tracks three statistics: - *
    - *
  • {@link #getRequestCount() Request Count:} the number - * of HTTP requests issued since this cache was created. - *
  • {@link #getNetworkCount() Network Count:} the - * number of those requests that required network use. - *
  • {@link #getHitCount() Hit Count:} the number of - * those requests whose responses were served by the cache. - *
- * Sometimes a request will result in a conditional cache hit. If the cache - * contains a stale copy of the response, the client will issue a conditional - * {@code GET}. The server will then send either the updated response if it has - * changed, or a short 'not modified' response if the client's copy is still - * valid. Such responses increment both the network count and hit count. - * - *

The best way to improve the cache hit rate is by configuring the web - * server to return cacheable responses. Although this client honors all HTTP/1.1 (RFC 2068) cache - * headers, it doesn't cache partial responses. - * - *

Force a Network Response

- * In some situations, such as after a user clicks a 'refresh' button, it may be - * necessary to skip the cache, and fetch data directly from the server. To force - * a full refresh, add the {@code no-cache} directive:
   {@code
- *         connection.addRequestProperty("Cache-Control", "no-cache");
- * }
- * If it is only necessary to force a cached response to be validated by the - * server, use the more efficient {@code max-age=0} instead:
   {@code
- *         connection.addRequestProperty("Cache-Control", "max-age=0");
- * }
- * - *

Force a Cache Response

- * Sometimes you'll want to show resources if they are available immediately, - * but not otherwise. This can be used so your application can show - * something while waiting for the latest data to be downloaded. To - * restrict a request to locally-cached resources, add the {@code - * only-if-cached} directive:
   {@code
- *     try {
- *         connection.addRequestProperty("Cache-Control", "only-if-cached");
- *         InputStream cached = connection.getInputStream();
- *         // the resource was cached! show it
- *     } catch (FileNotFoundException e) {
- *         // the resource was not cached
- *     }
- * }
- * This technique works even better in situations where a stale response is - * better than no response. To permit stale cached responses, use the {@code - * max-stale} directive with the maximum staleness in seconds:
   {@code
- *         int maxStale = 60 * 60 * 24 * 28; // tolerate 4-weeks stale
- *         connection.addRequestProperty("Cache-Control", "max-stale=" + maxStale);
- * }
- */ -public final class HttpResponseCache extends ResponseCache { - private static final char[] DIGITS = - { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; - - // TODO: add APIs to iterate the cache? - private static final int VERSION = 201105; - private static final int ENTRY_METADATA = 0; - private static final int ENTRY_BODY = 1; - private static final int ENTRY_COUNT = 2; - - private final DiskLruCache cache; - - /* read and write statistics, all guarded by 'this' */ - private int writeSuccessCount; - private int writeAbortCount; - private int networkCount; - private int hitCount; - private int requestCount; - - /** - * Although this class only exposes the limited ResponseCache API, it - * implements the full OkResponseCache interface. This field is used as a - * package private handle to the complete implementation. It delegates to - * public and private members of this type. - */ - final OkResponseCache okResponseCache = new OkResponseCache() { - @Override public CacheResponse get(URI uri, String requestMethod, - Map> requestHeaders) throws IOException { - return HttpResponseCache.this.get(uri, requestMethod, requestHeaders); - } - - @Override public CacheRequest put(URI uri, URLConnection connection) throws IOException { - return HttpResponseCache.this.put(uri, connection); - } - - @Override public void update( - CacheResponse conditionalCacheHit, HttpURLConnection connection) throws IOException { - HttpResponseCache.this.update(conditionalCacheHit, connection); - } - - @Override public void trackConditionalCacheHit() { - HttpResponseCache.this.trackConditionalCacheHit(); - } - - @Override public void trackResponse(ResponseSource source) { - HttpResponseCache.this.trackResponse(source); - } - }; - - public HttpResponseCache(File directory, long maxSize) throws IOException { - cache = DiskLruCache.open(directory, VERSION, ENTRY_COUNT, maxSize); - } - - private String uriToKey(URI uri) { - try { - MessageDigest messageDigest = MessageDigest.getInstance("MD5"); - byte[] md5bytes = messageDigest.digest(uri.toString().getBytes("UTF-8")); - return bytesToHexString(md5bytes); - } catch (NoSuchAlgorithmException e) { - throw new AssertionError(e); - } catch (UnsupportedEncodingException e) { - throw new AssertionError(e); - } - } - - private static String bytesToHexString(byte[] bytes) { - char[] digits = DIGITS; - char[] buf = new char[bytes.length * 2]; - int c = 0; - for (byte b : bytes) { - buf[c++] = digits[(b >> 4) & 0xf]; - buf[c++] = digits[b & 0xf]; - } - return new String(buf); - } - - @Override public CacheResponse get(URI uri, String requestMethod, - Map> requestHeaders) { - String key = uriToKey(uri); - DiskLruCache.Snapshot snapshot; - Entry entry; - try { - snapshot = cache.get(key); - if (snapshot == null) { - return null; - } - entry = new Entry(snapshot.getInputStream(ENTRY_METADATA)); - } catch (IOException e) { - // Give up because the cache cannot be read. - return null; - } - - if (!entry.matches(uri, requestMethod, requestHeaders)) { - snapshot.close(); - return null; - } - - return entry.isHttps() ? new EntrySecureCacheResponse(entry, snapshot) - : new EntryCacheResponse(entry, snapshot); - } - - @Override public CacheRequest put(URI uri, URLConnection urlConnection) throws IOException { - if (!(urlConnection instanceof HttpURLConnection)) { - return null; - } - - HttpURLConnection httpConnection = (HttpURLConnection) urlConnection; - String requestMethod = httpConnection.getRequestMethod(); - String key = uriToKey(uri); - - if (requestMethod.equals("POST") || requestMethod.equals("PUT") || requestMethod.equals( - "DELETE")) { - try { - cache.remove(key); - } catch (IOException ignored) { - // The cache cannot be written. - } - return null; - } else if (!requestMethod.equals("GET")) { - // Don't cache non-GET responses. We're technically allowed to cache - // HEAD requests and some POST requests, but the complexity of doing - // so is high and the benefit is low. - return null; - } - - HttpEngine httpEngine = getHttpEngine(httpConnection); - if (httpEngine == null) { - // Don't cache unless the HTTP implementation is ours. - return null; - } - - ResponseHeaders response = httpEngine.getResponseHeaders(); - if (response.hasVaryAll()) { - return null; - } - - RawHeaders varyHeaders = - httpEngine.getRequestHeaders().getHeaders().getAll(response.getVaryFields()); - Entry entry = new Entry(uri, varyHeaders, httpConnection); - DiskLruCache.Editor editor = null; - try { - editor = cache.edit(key); - if (editor == null) { - return null; - } - entry.writeTo(editor); - return new CacheRequestImpl(editor); - } catch (IOException e) { - abortQuietly(editor); - return null; - } - } - - private void update(CacheResponse conditionalCacheHit, HttpURLConnection httpConnection) - throws IOException { - HttpEngine httpEngine = getHttpEngine(httpConnection); - URI uri = httpEngine.getUri(); - ResponseHeaders response = httpEngine.getResponseHeaders(); - RawHeaders varyHeaders = - httpEngine.getRequestHeaders().getHeaders().getAll(response.getVaryFields()); - Entry entry = new Entry(uri, varyHeaders, httpConnection); - DiskLruCache.Snapshot snapshot = (conditionalCacheHit instanceof EntryCacheResponse) - ? ((EntryCacheResponse) conditionalCacheHit).snapshot - : ((EntrySecureCacheResponse) conditionalCacheHit).snapshot; - DiskLruCache.Editor editor = null; - try { - editor = snapshot.edit(); // returns null if snapshot is not current - if (editor != null) { - entry.writeTo(editor); - editor.commit(); - } - } catch (IOException e) { - abortQuietly(editor); - } - } - - private void abortQuietly(DiskLruCache.Editor editor) { - // Give up because the cache cannot be written. - try { - if (editor != null) { - editor.abort(); - } - } catch (IOException ignored) { - } - } - - private HttpEngine getHttpEngine(URLConnection httpConnection) { - if (httpConnection instanceof HttpURLConnectionImpl) { - return ((HttpURLConnectionImpl) httpConnection).getHttpEngine(); - } else if (httpConnection instanceof HttpsURLConnectionImpl) { - return ((HttpsURLConnectionImpl) httpConnection).getHttpEngine(); - } else { - return null; - } - } - - /** - * Closes the cache and deletes all of its stored values. This will delete - * all files in the cache directory including files that weren't created by - * the cache. - */ - public void delete() throws IOException { - cache.delete(); - } - - public synchronized int getWriteAbortCount() { - return writeAbortCount; - } - - public synchronized int getWriteSuccessCount() { - return writeSuccessCount; - } - - private synchronized void trackResponse(ResponseSource source) { - requestCount++; - - switch (source) { - case CACHE: - hitCount++; - break; - case CONDITIONAL_CACHE: - case NETWORK: - networkCount++; - break; - } - } - - private synchronized void trackConditionalCacheHit() { - hitCount++; - } - - public synchronized int getNetworkCount() { - return networkCount; - } - - public synchronized int getHitCount() { - return hitCount; - } - - public synchronized int getRequestCount() { - return requestCount; - } - - private final class CacheRequestImpl extends CacheRequest { - private final DiskLruCache.Editor editor; - private OutputStream cacheOut; - private boolean done; - private OutputStream body; - - public CacheRequestImpl(final DiskLruCache.Editor editor) throws IOException { - this.editor = editor; - this.cacheOut = editor.newOutputStream(ENTRY_BODY); - this.body = new FilterOutputStream(cacheOut) { - @Override public void close() throws IOException { - synchronized (HttpResponseCache.this) { - if (done) { - return; - } - done = true; - writeSuccessCount++; - } - super.close(); - editor.commit(); - } - - @Override - public void write(byte[] buffer, int offset, int length) throws IOException { - // Since we don't override "write(int oneByte)", we can write directly to "out" - // and avoid the inefficient implementation from the FilterOutputStream. - out.write(buffer, offset, length); - } - }; - } - - @Override public void abort() { - synchronized (HttpResponseCache.this) { - if (done) { - return; - } - done = true; - writeAbortCount++; - } - Util.closeQuietly(cacheOut); - try { - editor.abort(); - } catch (IOException ignored) { - } - } - - @Override public OutputStream getBody() throws IOException { - return body; - } - } - - private static final class Entry { - private final String uri; - private final RawHeaders varyHeaders; - private final String requestMethod; - private final RawHeaders responseHeaders; - private final String cipherSuite; - private final Certificate[] peerCertificates; - private final Certificate[] localCertificates; - - /** - * Reads an entry from an input stream. A typical entry looks like this: - *
{@code
-     *   http://google.com/foo
-     *   GET
-     *   2
-     *   Accept-Language: fr-CA
-     *   Accept-Charset: UTF-8
-     *   HTTP/1.1 200 OK
-     *   3
-     *   Content-Type: image/png
-     *   Content-Length: 100
-     *   Cache-Control: max-age=600
-     * }
- * - *

A typical HTTPS file looks like this: - *

{@code
-     *   https://google.com/foo
-     *   GET
-     *   2
-     *   Accept-Language: fr-CA
-     *   Accept-Charset: UTF-8
-     *   HTTP/1.1 200 OK
-     *   3
-     *   Content-Type: image/png
-     *   Content-Length: 100
-     *   Cache-Control: max-age=600
-     *
-     *   AES_256_WITH_MD5
-     *   2
-     *   base64-encoded peerCertificate[0]
-     *   base64-encoded peerCertificate[1]
-     *   -1
-     * }
- * The file is newline separated. The first two lines are the URL and - * the request method. Next is the number of HTTP Vary request header - * lines, followed by those lines. - * - *

Next is the response status line, followed by the number of HTTP - * response header lines, followed by those lines. - * - *

HTTPS responses also contain SSL session information. This begins - * with a blank line, and then a line containing the cipher suite. Next - * is the length of the peer certificate chain. These certificates are - * base64-encoded and appear each on their own line. The next line - * contains the length of the local certificate chain. These - * certificates are also base64-encoded and appear each on their own - * line. A length of -1 is used to encode a null array. - */ - public Entry(InputStream in) throws IOException { - try { - StrictLineReader reader = new StrictLineReader(in, US_ASCII); - uri = reader.readLine(); - requestMethod = reader.readLine(); - varyHeaders = new RawHeaders(); - int varyRequestHeaderLineCount = reader.readInt(); - for (int i = 0; i < varyRequestHeaderLineCount; i++) { - varyHeaders.addLine(reader.readLine()); - } - - responseHeaders = new RawHeaders(); - responseHeaders.setStatusLine(reader.readLine()); - int responseHeaderLineCount = reader.readInt(); - for (int i = 0; i < responseHeaderLineCount; i++) { - responseHeaders.addLine(reader.readLine()); - } - - if (isHttps()) { - String blank = reader.readLine(); - if (blank.length() > 0) { - throw new IOException("expected \"\" but was \"" + blank + "\""); - } - cipherSuite = reader.readLine(); - peerCertificates = readCertArray(reader); - localCertificates = readCertArray(reader); - } else { - cipherSuite = null; - peerCertificates = null; - localCertificates = null; - } - } finally { - in.close(); - } - } - - public Entry(URI uri, RawHeaders varyHeaders, HttpURLConnection httpConnection) - throws IOException { - this.uri = uri.toString(); - this.varyHeaders = varyHeaders; - this.requestMethod = httpConnection.getRequestMethod(); - this.responseHeaders = RawHeaders.fromMultimap(httpConnection.getHeaderFields(), true); - - if (isHttps()) { - HttpsURLConnection httpsConnection = (HttpsURLConnection) httpConnection; - cipherSuite = httpsConnection.getCipherSuite(); - Certificate[] peerCertificatesNonFinal = null; - try { - peerCertificatesNonFinal = httpsConnection.getServerCertificates(); - } catch (SSLPeerUnverifiedException ignored) { - } - peerCertificates = peerCertificatesNonFinal; - localCertificates = httpsConnection.getLocalCertificates(); - } else { - cipherSuite = null; - peerCertificates = null; - localCertificates = null; - } - } - - public void writeTo(DiskLruCache.Editor editor) throws IOException { - OutputStream out = editor.newOutputStream(ENTRY_METADATA); - Writer writer = new BufferedWriter(new OutputStreamWriter(out, UTF_8)); - - writer.write(uri + '\n'); - writer.write(requestMethod + '\n'); - writer.write(Integer.toString(varyHeaders.length()) + '\n'); - for (int i = 0; i < varyHeaders.length(); i++) { - writer.write(varyHeaders.getFieldName(i) + ": " + varyHeaders.getValue(i) + '\n'); - } - - writer.write(responseHeaders.getStatusLine() + '\n'); - writer.write(Integer.toString(responseHeaders.length()) + '\n'); - for (int i = 0; i < responseHeaders.length(); i++) { - writer.write(responseHeaders.getFieldName(i) + ": " + responseHeaders.getValue(i) + '\n'); - } - - if (isHttps()) { - writer.write('\n'); - writer.write(cipherSuite + '\n'); - writeCertArray(writer, peerCertificates); - writeCertArray(writer, localCertificates); - } - writer.close(); - } - - private boolean isHttps() { - return uri.startsWith("https://"); - } - - private Certificate[] readCertArray(StrictLineReader reader) throws IOException { - int length = reader.readInt(); - if (length == -1) { - return null; - } - try { - CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); - Certificate[] result = new Certificate[length]; - for (int i = 0; i < result.length; i++) { - String line = reader.readLine(); - byte[] bytes = Base64.decode(line.getBytes("US-ASCII")); - result[i] = certificateFactory.generateCertificate(new ByteArrayInputStream(bytes)); - } - return result; - } catch (CertificateException e) { - throw new IOException(e.getMessage()); - } - } - - private void writeCertArray(Writer writer, Certificate[] certificates) throws IOException { - if (certificates == null) { - writer.write("-1\n"); - return; - } - try { - writer.write(Integer.toString(certificates.length) + '\n'); - for (Certificate certificate : certificates) { - byte[] bytes = certificate.getEncoded(); - String line = Base64.encode(bytes); - writer.write(line + '\n'); - } - } catch (CertificateEncodingException e) { - throw new IOException(e.getMessage()); - } - } - - public boolean matches(URI uri, String requestMethod, - Map> requestHeaders) { - return this.uri.equals(uri.toString()) - && this.requestMethod.equals(requestMethod) - && new ResponseHeaders(uri, responseHeaders).varyMatches(varyHeaders.toMultimap(false), - requestHeaders); - } - } - - /** - * Returns an input stream that reads the body of a snapshot, closing the - * snapshot when the stream is closed. - */ - private static InputStream newBodyInputStream(final DiskLruCache.Snapshot snapshot) { - return new FilterInputStream(snapshot.getInputStream(ENTRY_BODY)) { - @Override public void close() throws IOException { - snapshot.close(); - super.close(); - } - }; - } - - static class EntryCacheResponse extends CacheResponse { - private final Entry entry; - private final DiskLruCache.Snapshot snapshot; - private final InputStream in; - - public EntryCacheResponse(Entry entry, DiskLruCache.Snapshot snapshot) { - this.entry = entry; - this.snapshot = snapshot; - this.in = newBodyInputStream(snapshot); - } - - @Override public Map> getHeaders() { - return entry.responseHeaders.toMultimap(true); - } - - @Override public InputStream getBody() { - return in; - } - } - - static class EntrySecureCacheResponse extends SecureCacheResponse { - private final Entry entry; - private final DiskLruCache.Snapshot snapshot; - private final InputStream in; - - public EntrySecureCacheResponse(Entry entry, DiskLruCache.Snapshot snapshot) { - this.entry = entry; - this.snapshot = snapshot; - this.in = newBodyInputStream(snapshot); - } - - @Override public Map> getHeaders() { - return entry.responseHeaders.toMultimap(true); - } - - @Override public InputStream getBody() { - return in; - } - - @Override public String getCipherSuite() { - return entry.cipherSuite; - } - - @Override public List getServerCertificateChain() - throws SSLPeerUnverifiedException { - if (entry.peerCertificates == null || entry.peerCertificates.length == 0) { - throw new SSLPeerUnverifiedException(null); - } - return Arrays.asList(entry.peerCertificates.clone()); - } - - @Override public Principal getPeerPrincipal() throws SSLPeerUnverifiedException { - if (entry.peerCertificates == null || entry.peerCertificates.length == 0) { - throw new SSLPeerUnverifiedException(null); - } - return ((X509Certificate) entry.peerCertificates[0]).getSubjectX500Principal(); - } - - @Override public List getLocalCertificateChain() { - if (entry.localCertificates == null || entry.localCertificates.length == 0) { - return null; - } - return Arrays.asList(entry.localCertificates.clone()); - } - - @Override public Principal getLocalPrincipal() { - if (entry.localCertificates == null || entry.localCertificates.length == 0) { - return null; - } - return ((X509Certificate) entry.localCertificates[0]).getSubjectX500Principal(); - } - } -} diff --git a/platforms/android/CordovaLib/src/com/squareup/okhttp/OkHttpClient.java b/platforms/android/CordovaLib/src/com/squareup/okhttp/OkHttpClient.java deleted file mode 100644 index 7834bd6b2..000000000 --- a/platforms/android/CordovaLib/src/com/squareup/okhttp/OkHttpClient.java +++ /dev/null @@ -1,216 +0,0 @@ -/* - * Copyright (C) 2012 Square, Inc. - * - * Licensed 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. - */ -package com.squareup.okhttp; - -import com.squareup.okhttp.internal.http.HttpURLConnectionImpl; -import com.squareup.okhttp.internal.http.HttpsURLConnectionImpl; -import com.squareup.okhttp.internal.http.OkResponseCache; -import com.squareup.okhttp.internal.http.OkResponseCacheAdapter; -import java.net.CookieHandler; -import java.net.HttpURLConnection; -import java.net.Proxy; -import java.net.ProxySelector; -import java.net.ResponseCache; -import java.net.URL; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.Set; -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLSocketFactory; - -/** Configures and creates HTTP connections. */ -public final class OkHttpClient { - private Proxy proxy; - private Set failedRoutes = Collections.synchronizedSet(new LinkedHashSet()); - private ProxySelector proxySelector; - private CookieHandler cookieHandler; - private ResponseCache responseCache; - private SSLSocketFactory sslSocketFactory; - private HostnameVerifier hostnameVerifier; - private ConnectionPool connectionPool; - private boolean followProtocolRedirects = true; - - /** - * Sets the HTTP proxy that will be used by connections created by this - * client. This takes precedence over {@link #setProxySelector}, which is - * only honored when this proxy is null (which it is by default). To disable - * proxy use completely, call {@code setProxy(Proxy.NO_PROXY)}. - */ - public OkHttpClient setProxy(Proxy proxy) { - this.proxy = proxy; - return this; - } - - public Proxy getProxy() { - return proxy; - } - - /** - * Sets the proxy selection policy to be used if no {@link #setProxy proxy} - * is specified explicitly. The proxy selector may return multiple proxies; - * in that case they will be tried in sequence until a successful connection - * is established. - * - *

If unset, the {@link ProxySelector#getDefault() system-wide default} - * proxy selector will be used. - */ - public OkHttpClient setProxySelector(ProxySelector proxySelector) { - this.proxySelector = proxySelector; - return this; - } - - public ProxySelector getProxySelector() { - return proxySelector; - } - - /** - * Sets the cookie handler to be used to read outgoing cookies and write - * incoming cookies. - * - *

If unset, the {@link CookieHandler#getDefault() system-wide default} - * cookie handler will be used. - */ - public OkHttpClient setCookieHandler(CookieHandler cookieHandler) { - this.cookieHandler = cookieHandler; - return this; - } - - public CookieHandler getCookieHandler() { - return cookieHandler; - } - - /** - * Sets the response cache to be used to read and write cached responses. - * - *

If unset, the {@link ResponseCache#getDefault() system-wide default} - * response cache will be used. - */ - public OkHttpClient setResponseCache(ResponseCache responseCache) { - this.responseCache = responseCache; - return this; - } - - public ResponseCache getResponseCache() { - return responseCache; - } - - private OkResponseCache okResponseCache() { - if (responseCache instanceof HttpResponseCache) { - return ((HttpResponseCache) responseCache).okResponseCache; - } else if (responseCache != null) { - return new OkResponseCacheAdapter(responseCache); - } else { - return null; - } - } - - /** - * Sets the socket factory used to secure HTTPS connections. - * - *

If unset, the {@link HttpsURLConnection#getDefaultSSLSocketFactory() - * system-wide default} SSL socket factory will be used. - */ - public OkHttpClient setSSLSocketFactory(SSLSocketFactory sslSocketFactory) { - this.sslSocketFactory = sslSocketFactory; - return this; - } - - public SSLSocketFactory getSslSocketFactory() { - return sslSocketFactory; - } - - /** - * Sets the verifier used to confirm that response certificates apply to - * requested hostnames for HTTPS connections. - * - *

If unset, the {@link HttpsURLConnection#getDefaultHostnameVerifier() - * system-wide default} hostname verifier will be used. - */ - public OkHttpClient setHostnameVerifier(HostnameVerifier hostnameVerifier) { - this.hostnameVerifier = hostnameVerifier; - return this; - } - - public HostnameVerifier getHostnameVerifier() { - return hostnameVerifier; - } - - /** - * Sets the connection pool used to recycle HTTP and HTTPS connections. - * - *

If unset, the {@link ConnectionPool#getDefault() system-wide - * default} connection pool will be used. - */ - public OkHttpClient setConnectionPool(ConnectionPool connectionPool) { - this.connectionPool = connectionPool; - return this; - } - - public ConnectionPool getConnectionPool() { - return connectionPool; - } - - /** - * Configure this client to follow redirects from HTTPS to HTTP and from HTTP - * to HTTPS. - * - *

If unset, protocol redirects will be followed. This is different than - * the built-in {@code HttpURLConnection}'s default. - */ - public OkHttpClient setFollowProtocolRedirects(boolean followProtocolRedirects) { - this.followProtocolRedirects = followProtocolRedirects; - return this; - } - - public boolean getFollowProtocolRedirects() { - return followProtocolRedirects; - } - - public HttpURLConnection open(URL url) { - String protocol = url.getProtocol(); - OkHttpClient copy = copyWithDefaults(); - if (protocol.equals("http")) { - return new HttpURLConnectionImpl(url, copy, copy.okResponseCache(), copy.failedRoutes); - } else if (protocol.equals("https")) { - return new HttpsURLConnectionImpl(url, copy, copy.okResponseCache(), copy.failedRoutes); - } else { - throw new IllegalArgumentException("Unexpected protocol: " + protocol); - } - } - - /** - * Returns a shallow copy of this OkHttpClient that uses the system-wide default for - * each field that hasn't been explicitly configured. - */ - private OkHttpClient copyWithDefaults() { - OkHttpClient result = new OkHttpClient(); - result.proxy = proxy; - result.failedRoutes = failedRoutes; - result.proxySelector = proxySelector != null ? proxySelector : ProxySelector.getDefault(); - result.cookieHandler = cookieHandler != null ? cookieHandler : CookieHandler.getDefault(); - result.responseCache = responseCache != null ? responseCache : ResponseCache.getDefault(); - result.sslSocketFactory = sslSocketFactory != null - ? sslSocketFactory - : HttpsURLConnection.getDefaultSSLSocketFactory(); - result.hostnameVerifier = hostnameVerifier != null - ? hostnameVerifier - : HttpsURLConnection.getDefaultHostnameVerifier(); - result.connectionPool = connectionPool != null ? connectionPool : ConnectionPool.getDefault(); - result.followProtocolRedirects = followProtocolRedirects; - return result; - } -} diff --git a/platforms/android/CordovaLib/src/com/squareup/okhttp/OkResponseCache.java b/platforms/android/CordovaLib/src/com/squareup/okhttp/OkResponseCache.java deleted file mode 100644 index b7e3801dc..000000000 --- a/platforms/android/CordovaLib/src/com/squareup/okhttp/OkResponseCache.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed 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. - */ -package com.squareup.okhttp; - -import java.io.IOException; -import java.net.CacheResponse; -import java.net.HttpURLConnection; - -/** - * A response cache that supports statistics tracking and updating stored - * responses. Implementations of {@link java.net.ResponseCache} should implement - * this interface to receive additional support from the HTTP engine. - */ -public interface OkResponseCache { - - /** Track an HTTP response being satisfied by {@code source}. */ - void trackResponse(ResponseSource source); - - /** Track an conditional GET that was satisfied by this cache. */ - void trackConditionalCacheHit(); - - /** Updates stored HTTP headers using a hit on a conditional GET. */ - void update(CacheResponse conditionalCacheHit, HttpURLConnection httpConnection) - throws IOException; -} diff --git a/platforms/android/CordovaLib/src/com/squareup/okhttp/ResponseSource.java b/platforms/android/CordovaLib/src/com/squareup/okhttp/ResponseSource.java deleted file mode 100644 index 4eca172d3..000000000 --- a/platforms/android/CordovaLib/src/com/squareup/okhttp/ResponseSource.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed 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. - */ -package com.squareup.okhttp; - -/** The source of an HTTP response. */ -public enum ResponseSource { - - /** The response was returned from the local cache. */ - CACHE, - - /** - * The response is available in the cache but must be validated with the - * network. The cache result will be used if it is still valid; otherwise - * the network's response will be used. - */ - CONDITIONAL_CACHE, - - /** The response was returned from the network. */ - NETWORK; - - public boolean requiresConnection() { - return this == CONDITIONAL_CACHE || this == NETWORK; - } -} diff --git a/platforms/android/CordovaLib/src/com/squareup/okhttp/Route.java b/platforms/android/CordovaLib/src/com/squareup/okhttp/Route.java deleted file mode 100644 index 6968c6046..000000000 --- a/platforms/android/CordovaLib/src/com/squareup/okhttp/Route.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (C) 2013 Square, Inc. - * - * Licensed 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. - */ -package com.squareup.okhttp; - -import java.net.InetSocketAddress; -import java.net.Proxy; - -/** Represents the route used by a connection to reach an endpoint. */ -public class Route { - final Address address; - final Proxy proxy; - final InetSocketAddress inetSocketAddress; - final boolean modernTls; - - public Route(Address address, Proxy proxy, InetSocketAddress inetSocketAddress, - boolean modernTls) { - if (address == null) throw new NullPointerException("address == null"); - if (proxy == null) throw new NullPointerException("proxy == null"); - if (inetSocketAddress == null) throw new NullPointerException("inetSocketAddress == null"); - this.address = address; - this.proxy = proxy; - this.inetSocketAddress = inetSocketAddress; - this.modernTls = modernTls; - } - - /** Returns the {@link Address} of this route. */ - public Address getAddress() { - return address; - } - - /** - * Returns the {@link Proxy} of this route. - * - * Warning: This may be different than the proxy returned - * by {@link #getAddress}! That is the proxy that the user asked to be - * connected to; this returns the proxy that they were actually connected - * to. The two may disagree when a proxy selector selects a different proxy - * for a connection. - */ - public Proxy getProxy() { - return proxy; - } - - /** Returns the {@link InetSocketAddress} of this route. */ - public InetSocketAddress getSocketAddress() { - return inetSocketAddress; - } - - /** Returns true if this route uses modern tls. */ - public boolean isModernTls() { - return modernTls; - } - - /** Returns a copy of this route with flipped tls mode. */ - public Route flipTlsMode() { - return new Route(address, proxy, inetSocketAddress, !modernTls); - } - - @Override public boolean equals(Object obj) { - if (obj instanceof Route) { - Route other = (Route) obj; - return (address.equals(other.address) - && proxy.equals(other.proxy) - && inetSocketAddress.equals(other.inetSocketAddress) - && modernTls == other.modernTls); - } - return false; - } - - @Override public int hashCode() { - int result = 17; - result = 31 * result + address.hashCode(); - result = 31 * result + proxy.hashCode(); - result = 31 * result + inetSocketAddress.hashCode(); - result = result + (modernTls ? (31 * result) : 0); - return result; - } -} diff --git a/platforms/android/CordovaLib/src/com/squareup/okhttp/TunnelRequest.java b/platforms/android/CordovaLib/src/com/squareup/okhttp/TunnelRequest.java deleted file mode 100644 index 5260b87c4..000000000 --- a/platforms/android/CordovaLib/src/com/squareup/okhttp/TunnelRequest.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed 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. - */ -package com.squareup.okhttp; - -import com.squareup.okhttp.internal.http.RawHeaders; - -import static com.squareup.okhttp.internal.Util.getDefaultPort; - -/** - * Routing and authentication information sent to an HTTP proxy to create a - * HTTPS to an origin server. Everything in the tunnel request is sent - * unencrypted to the proxy server. - * - *

See RFC 2817, Section - * 5.2. - */ -public final class TunnelRequest { - final String host; - final int port; - final String userAgent; - final String proxyAuthorization; - - /** - * @param host the origin server's hostname. Not null. - * @param port the origin server's port, like 80 or 443. - * @param userAgent the client's user-agent. Not null. - * @param proxyAuthorization proxy authorization, or null if the proxy is - * used without an authorization header. - */ - public TunnelRequest(String host, int port, String userAgent, String proxyAuthorization) { - if (host == null) throw new NullPointerException("host == null"); - if (userAgent == null) throw new NullPointerException("userAgent == null"); - this.host = host; - this.port = port; - this.userAgent = userAgent; - this.proxyAuthorization = proxyAuthorization; - } - - /** - * If we're creating a TLS tunnel, send only the minimum set of headers. - * This avoids sending potentially sensitive data like HTTP cookies to - * the proxy unencrypted. - */ - RawHeaders getRequestHeaders() { - RawHeaders result = new RawHeaders(); - result.setRequestLine("CONNECT " + host + ":" + port + " HTTP/1.1"); - - // Always set Host and User-Agent. - result.set("Host", port == getDefaultPort("https") ? host : (host + ":" + port)); - result.set("User-Agent", userAgent); - - // Copy over the Proxy-Authorization header if it exists. - if (proxyAuthorization != null) { - result.set("Proxy-Authorization", proxyAuthorization); - } - - // Always set the Proxy-Connection to Keep-Alive for the benefit of - // HTTP/1.0 proxies like Squid. - result.set("Proxy-Connection", "Keep-Alive"); - return result; - } -} diff --git a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/AbstractOutputStream.java b/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/AbstractOutputStream.java deleted file mode 100644 index 78c9691e6..000000000 --- a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/AbstractOutputStream.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed 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. - */ - -package com.squareup.okhttp.internal; - -import java.io.IOException; -import java.io.OutputStream; - -/** - * An output stream for an HTTP request body. - * - *

Since a single socket's output stream may be used to write multiple HTTP - * requests to the same server, subclasses should not close the socket stream. - */ -public abstract class AbstractOutputStream extends OutputStream { - protected boolean closed; - - @Override public final void write(int data) throws IOException { - write(new byte[] { (byte) data }); - } - - protected final void checkNotClosed() throws IOException { - if (closed) { - throw new IOException("stream closed"); - } - } - - /** Returns true if this stream was closed locally. */ - public boolean isClosed() { - return closed; - } -} diff --git a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/Base64.java b/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/Base64.java deleted file mode 100644 index 79cd0206f..000000000 --- a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/Base64.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * 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. - */ - -/** - * @author Alexander Y. Kleymenov - */ - -package com.squareup.okhttp.internal; - -import java.io.UnsupportedEncodingException; - -import static com.squareup.okhttp.internal.Util.EMPTY_BYTE_ARRAY; - -/** - * Base64 encoder/decoder. - * In violation of the RFC, this encoder doesn't wrap lines at 76 columns. - */ -public final class Base64 { - private Base64() { - } - - public static byte[] decode(byte[] in) { - return decode(in, in.length); - } - - public static byte[] decode(byte[] in, int len) { - // approximate output length - int length = len / 4 * 3; - // return an empty array on empty or short input without padding - if (length == 0) { - return EMPTY_BYTE_ARRAY; - } - // temporary array - byte[] out = new byte[length]; - // number of padding characters ('=') - int pad = 0; - byte chr; - // compute the number of the padding characters - // and adjust the length of the input - for (; ; len--) { - chr = in[len - 1]; - // skip the neutral characters - if ((chr == '\n') || (chr == '\r') || (chr == ' ') || (chr == '\t')) { - continue; - } - if (chr == '=') { - pad++; - } else { - break; - } - } - // index in the output array - int outIndex = 0; - // index in the input array - int inIndex = 0; - // holds the value of the input character - int bits = 0; - // holds the value of the input quantum - int quantum = 0; - for (int i = 0; i < len; i++) { - chr = in[i]; - // skip the neutral characters - if ((chr == '\n') || (chr == '\r') || (chr == ' ') || (chr == '\t')) { - continue; - } - if ((chr >= 'A') && (chr <= 'Z')) { - // char ASCII value - // A 65 0 - // Z 90 25 (ASCII - 65) - bits = chr - 65; - } else if ((chr >= 'a') && (chr <= 'z')) { - // char ASCII value - // a 97 26 - // z 122 51 (ASCII - 71) - bits = chr - 71; - } else if ((chr >= '0') && (chr <= '9')) { - // char ASCII value - // 0 48 52 - // 9 57 61 (ASCII + 4) - bits = chr + 4; - } else if (chr == '+') { - bits = 62; - } else if (chr == '/') { - bits = 63; - } else { - return null; - } - // append the value to the quantum - quantum = (quantum << 6) | (byte) bits; - if (inIndex % 4 == 3) { - // 4 characters were read, so make the output: - out[outIndex++] = (byte) (quantum >> 16); - out[outIndex++] = (byte) (quantum >> 8); - out[outIndex++] = (byte) quantum; - } - inIndex++; - } - if (pad > 0) { - // adjust the quantum value according to the padding - quantum = quantum << (6 * pad); - // make output - out[outIndex++] = (byte) (quantum >> 16); - if (pad == 1) { - out[outIndex++] = (byte) (quantum >> 8); - } - } - // create the resulting array - byte[] result = new byte[outIndex]; - System.arraycopy(out, 0, result, 0, outIndex); - return result; - } - - private static final byte[] MAP = new byte[] { - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', - 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', - 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', - '5', '6', '7', '8', '9', '+', '/' - }; - - public static String encode(byte[] in) { - int length = (in.length + 2) * 4 / 3; - byte[] out = new byte[length]; - int index = 0, end = in.length - in.length % 3; - for (int i = 0; i < end; i += 3) { - out[index++] = MAP[(in[i] & 0xff) >> 2]; - out[index++] = MAP[((in[i] & 0x03) << 4) | ((in[i + 1] & 0xff) >> 4)]; - out[index++] = MAP[((in[i + 1] & 0x0f) << 2) | ((in[i + 2] & 0xff) >> 6)]; - out[index++] = MAP[(in[i + 2] & 0x3f)]; - } - switch (in.length % 3) { - case 1: - out[index++] = MAP[(in[end] & 0xff) >> 2]; - out[index++] = MAP[(in[end] & 0x03) << 4]; - out[index++] = '='; - out[index++] = '='; - break; - case 2: - out[index++] = MAP[(in[end] & 0xff) >> 2]; - out[index++] = MAP[((in[end] & 0x03) << 4) | ((in[end + 1] & 0xff) >> 4)]; - out[index++] = MAP[((in[end + 1] & 0x0f) << 2)]; - out[index++] = '='; - break; - } - try { - return new String(out, 0, index, "US-ASCII"); - } catch (UnsupportedEncodingException e) { - throw new AssertionError(e); - } - } -} diff --git a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/DiskLruCache.java b/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/DiskLruCache.java deleted file mode 100644 index f7fcb1ed2..000000000 --- a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/DiskLruCache.java +++ /dev/null @@ -1,926 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed 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. - */ - -package com.squareup.okhttp.internal; - -import java.io.BufferedWriter; -import java.io.Closeable; -import java.io.EOFException; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.FilterOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.Writer; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.concurrent.Callable; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * A cache that uses a bounded amount of space on a filesystem. Each cache - * entry has a string key and a fixed number of values. Each key must match - * the regex [a-z0-9_-]{1,64}. Values are byte sequences, - * accessible as streams or files. Each value must be between {@code 0} and - * {@code Integer.MAX_VALUE} bytes in length. - * - *

The cache stores its data in a directory on the filesystem. This - * directory must be exclusive to the cache; the cache may delete or overwrite - * files from its directory. It is an error for multiple processes to use the - * same cache directory at the same time. - * - *

This cache limits the number of bytes that it will store on the - * filesystem. When the number of stored bytes exceeds the limit, the cache will - * remove entries in the background until the limit is satisfied. The limit is - * not strict: the cache may temporarily exceed it while waiting for files to be - * deleted. The limit does not include filesystem overhead or the cache - * journal so space-sensitive applications should set a conservative limit. - * - *

Clients call {@link #edit} to create or update the values of an entry. An - * entry may have only one editor at one time; if a value is not available to be - * edited then {@link #edit} will return null. - *

    - *
  • When an entry is being created it is necessary to - * supply a full set of values; the empty value should be used as a - * placeholder if necessary. - *
  • When an entry is being edited, it is not necessary - * to supply data for every value; values default to their previous - * value. - *
- * Every {@link #edit} call must be matched by a call to {@link Editor#commit} - * or {@link Editor#abort}. Committing is atomic: a read observes the full set - * of values as they were before or after the commit, but never a mix of values. - * - *

Clients call {@link #get} to read a snapshot of an entry. The read will - * observe the value at the time that {@link #get} was called. Updates and - * removals after the call do not impact ongoing reads. - * - *

This class is tolerant of some I/O errors. If files are missing from the - * filesystem, the corresponding entries will be dropped from the cache. If - * an error occurs while writing a cache value, the edit will fail silently. - * Callers should handle other problems by catching {@code IOException} and - * responding appropriately. - */ -public final class DiskLruCache implements Closeable { - static final String JOURNAL_FILE = "journal"; - static final String JOURNAL_FILE_TEMP = "journal.tmp"; - static final String JOURNAL_FILE_BACKUP = "journal.bkp"; - static final String MAGIC = "libcore.io.DiskLruCache"; - static final String VERSION_1 = "1"; - static final long ANY_SEQUENCE_NUMBER = -1; - static final Pattern LEGAL_KEY_PATTERN = Pattern.compile("[a-z0-9_-]{1,64}"); - private static final String CLEAN = "CLEAN"; - private static final String DIRTY = "DIRTY"; - private static final String REMOVE = "REMOVE"; - private static final String READ = "READ"; - - /* - * This cache uses a journal file named "journal". A typical journal file - * looks like this: - * libcore.io.DiskLruCache - * 1 - * 100 - * 2 - * - * CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054 - * DIRTY 335c4c6028171cfddfbaae1a9c313c52 - * CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342 - * REMOVE 335c4c6028171cfddfbaae1a9c313c52 - * DIRTY 1ab96a171faeeee38496d8b330771a7a - * CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234 - * READ 335c4c6028171cfddfbaae1a9c313c52 - * READ 3400330d1dfc7f3f7f4b8d4d803dfcf6 - * - * The first five lines of the journal form its header. They are the - * constant string "libcore.io.DiskLruCache", the disk cache's version, - * the application's version, the value count, and a blank line. - * - * Each of the subsequent lines in the file is a record of the state of a - * cache entry. Each line contains space-separated values: a state, a key, - * and optional state-specific values. - * o DIRTY lines track that an entry is actively being created or updated. - * Every successful DIRTY action should be followed by a CLEAN or REMOVE - * action. DIRTY lines without a matching CLEAN or REMOVE indicate that - * temporary files may need to be deleted. - * o CLEAN lines track a cache entry that has been successfully published - * and may be read. A publish line is followed by the lengths of each of - * its values. - * o READ lines track accesses for LRU. - * o REMOVE lines track entries that have been deleted. - * - * The journal file is appended to as cache operations occur. The journal may - * occasionally be compacted by dropping redundant lines. A temporary file named - * "journal.tmp" will be used during compaction; that file should be deleted if - * it exists when the cache is opened. - */ - - private final File directory; - private final File journalFile; - private final File journalFileTmp; - private final File journalFileBackup; - private final int appVersion; - private long maxSize; - private final int valueCount; - private long size = 0; - private Writer journalWriter; - private final LinkedHashMap lruEntries = - new LinkedHashMap(0, 0.75f, true); - private int redundantOpCount; - - /** - * To differentiate between old and current snapshots, each entry is given - * a sequence number each time an edit is committed. A snapshot is stale if - * its sequence number is not equal to its entry's sequence number. - */ - private long nextSequenceNumber = 0; - - /** This cache uses a single background thread to evict entries. */ - final ThreadPoolExecutor executorService = - new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue()); - private final Callable cleanupCallable = new Callable() { - public Void call() throws Exception { - synchronized (DiskLruCache.this) { - if (journalWriter == null) { - return null; // Closed. - } - trimToSize(); - if (journalRebuildRequired()) { - rebuildJournal(); - redundantOpCount = 0; - } - } - return null; - } - }; - - private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize) { - this.directory = directory; - this.appVersion = appVersion; - this.journalFile = new File(directory, JOURNAL_FILE); - this.journalFileTmp = new File(directory, JOURNAL_FILE_TEMP); - this.journalFileBackup = new File(directory, JOURNAL_FILE_BACKUP); - this.valueCount = valueCount; - this.maxSize = maxSize; - } - - /** - * Opens the cache in {@code directory}, creating a cache if none exists - * there. - * - * @param directory a writable directory - * @param valueCount the number of values per cache entry. Must be positive. - * @param maxSize the maximum number of bytes this cache should use to store - * @throws IOException if reading or writing the cache directory fails - */ - public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize) - throws IOException { - if (maxSize <= 0) { - throw new IllegalArgumentException("maxSize <= 0"); - } - if (valueCount <= 0) { - throw new IllegalArgumentException("valueCount <= 0"); - } - - // If a bkp file exists, use it instead. - File backupFile = new File(directory, JOURNAL_FILE_BACKUP); - if (backupFile.exists()) { - File journalFile = new File(directory, JOURNAL_FILE); - // If journal file also exists just delete backup file. - if (journalFile.exists()) { - backupFile.delete(); - } else { - renameTo(backupFile, journalFile, false); - } - } - - // Prefer to pick up where we left off. - DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize); - if (cache.journalFile.exists()) { - try { - cache.readJournal(); - cache.processJournal(); - cache.journalWriter = new BufferedWriter( - new OutputStreamWriter(new FileOutputStream(cache.journalFile, true), Util.US_ASCII)); - return cache; - } catch (IOException journalIsCorrupt) { - Platform.get().logW("DiskLruCache " + directory + " is corrupt: " - + journalIsCorrupt.getMessage() + ", removing"); - cache.delete(); - } - } - - // Create a new empty cache. - directory.mkdirs(); - cache = new DiskLruCache(directory, appVersion, valueCount, maxSize); - cache.rebuildJournal(); - return cache; - } - - private void readJournal() throws IOException { - StrictLineReader reader = new StrictLineReader(new FileInputStream(journalFile), Util.US_ASCII); - try { - String magic = reader.readLine(); - String version = reader.readLine(); - String appVersionString = reader.readLine(); - String valueCountString = reader.readLine(); - String blank = reader.readLine(); - if (!MAGIC.equals(magic) - || !VERSION_1.equals(version) - || !Integer.toString(appVersion).equals(appVersionString) - || !Integer.toString(valueCount).equals(valueCountString) - || !"".equals(blank)) { - throw new IOException("unexpected journal header: [" + magic + ", " + version + ", " - + valueCountString + ", " + blank + "]"); - } - - int lineCount = 0; - while (true) { - try { - readJournalLine(reader.readLine()); - lineCount++; - } catch (EOFException endOfJournal) { - break; - } - } - redundantOpCount = lineCount - lruEntries.size(); - } finally { - Util.closeQuietly(reader); - } - } - - private void readJournalLine(String line) throws IOException { - int firstSpace = line.indexOf(' '); - if (firstSpace == -1) { - throw new IOException("unexpected journal line: " + line); - } - - int keyBegin = firstSpace + 1; - int secondSpace = line.indexOf(' ', keyBegin); - final String key; - if (secondSpace == -1) { - key = line.substring(keyBegin); - if (firstSpace == REMOVE.length() && line.startsWith(REMOVE)) { - lruEntries.remove(key); - return; - } - } else { - key = line.substring(keyBegin, secondSpace); - } - - Entry entry = lruEntries.get(key); - if (entry == null) { - entry = new Entry(key); - lruEntries.put(key, entry); - } - - if (secondSpace != -1 && firstSpace == CLEAN.length() && line.startsWith(CLEAN)) { - String[] parts = line.substring(secondSpace + 1).split(" "); - entry.readable = true; - entry.currentEditor = null; - entry.setLengths(parts); - } else if (secondSpace == -1 && firstSpace == DIRTY.length() && line.startsWith(DIRTY)) { - entry.currentEditor = new Editor(entry); - } else if (secondSpace == -1 && firstSpace == READ.length() && line.startsWith(READ)) { - // This work was already done by calling lruEntries.get(). - } else { - throw new IOException("unexpected journal line: " + line); - } - } - - /** - * Computes the initial size and collects garbage as a part of opening the - * cache. Dirty entries are assumed to be inconsistent and will be deleted. - */ - private void processJournal() throws IOException { - deleteIfExists(journalFileTmp); - for (Iterator i = lruEntries.values().iterator(); i.hasNext(); ) { - Entry entry = i.next(); - if (entry.currentEditor == null) { - for (int t = 0; t < valueCount; t++) { - size += entry.lengths[t]; - } - } else { - entry.currentEditor = null; - for (int t = 0; t < valueCount; t++) { - deleteIfExists(entry.getCleanFile(t)); - deleteIfExists(entry.getDirtyFile(t)); - } - i.remove(); - } - } - } - - /** - * Creates a new journal that omits redundant information. This replaces the - * current journal if it exists. - */ - private synchronized void rebuildJournal() throws IOException { - if (journalWriter != null) { - journalWriter.close(); - } - - Writer writer = new BufferedWriter( - new OutputStreamWriter(new FileOutputStream(journalFileTmp), Util.US_ASCII)); - try { - writer.write(MAGIC); - writer.write("\n"); - writer.write(VERSION_1); - writer.write("\n"); - writer.write(Integer.toString(appVersion)); - writer.write("\n"); - writer.write(Integer.toString(valueCount)); - writer.write("\n"); - writer.write("\n"); - - for (Entry entry : lruEntries.values()) { - if (entry.currentEditor != null) { - writer.write(DIRTY + ' ' + entry.key + '\n'); - } else { - writer.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n'); - } - } - } finally { - writer.close(); - } - - if (journalFile.exists()) { - renameTo(journalFile, journalFileBackup, true); - } - renameTo(journalFileTmp, journalFile, false); - journalFileBackup.delete(); - - journalWriter = new BufferedWriter( - new OutputStreamWriter(new FileOutputStream(journalFile, true), Util.US_ASCII)); - } - - private static void deleteIfExists(File file) throws IOException { - if (file.exists() && !file.delete()) { - throw new IOException(); - } - } - - private static void renameTo(File from, File to, boolean deleteDestination) throws IOException { - if (deleteDestination) { - deleteIfExists(to); - } - if (!from.renameTo(to)) { - throw new IOException(); - } - } - - /** - * Returns a snapshot of the entry named {@code key}, or null if it doesn't - * exist is not currently readable. If a value is returned, it is moved to - * the head of the LRU queue. - */ - public synchronized Snapshot get(String key) throws IOException { - checkNotClosed(); - validateKey(key); - Entry entry = lruEntries.get(key); - if (entry == null) { - return null; - } - - if (!entry.readable) { - return null; - } - - // Open all streams eagerly to guarantee that we see a single published - // snapshot. If we opened streams lazily then the streams could come - // from different edits. - InputStream[] ins = new InputStream[valueCount]; - try { - for (int i = 0; i < valueCount; i++) { - ins[i] = new FileInputStream(entry.getCleanFile(i)); - } - } catch (FileNotFoundException e) { - // A file must have been deleted manually! - for (int i = 0; i < valueCount; i++) { - if (ins[i] != null) { - Util.closeQuietly(ins[i]); - } else { - break; - } - } - return null; - } - - redundantOpCount++; - journalWriter.append(READ + ' ' + key + '\n'); - if (journalRebuildRequired()) { - executorService.submit(cleanupCallable); - } - - return new Snapshot(key, entry.sequenceNumber, ins, entry.lengths); - } - - /** - * Returns an editor for the entry named {@code key}, or null if another - * edit is in progress. - */ - public Editor edit(String key) throws IOException { - return edit(key, ANY_SEQUENCE_NUMBER); - } - - private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException { - checkNotClosed(); - validateKey(key); - Entry entry = lruEntries.get(key); - if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER && (entry == null - || entry.sequenceNumber != expectedSequenceNumber)) { - return null; // Snapshot is stale. - } - if (entry == null) { - entry = new Entry(key); - lruEntries.put(key, entry); - } else if (entry.currentEditor != null) { - return null; // Another edit is in progress. - } - - Editor editor = new Editor(entry); - entry.currentEditor = editor; - - // Flush the journal before creating files to prevent file leaks. - journalWriter.write(DIRTY + ' ' + key + '\n'); - journalWriter.flush(); - return editor; - } - - /** Returns the directory where this cache stores its data. */ - public File getDirectory() { - return directory; - } - - /** - * Returns the maximum number of bytes that this cache should use to store - * its data. - */ - public long getMaxSize() { - return maxSize; - } - - /** - * Changes the maximum number of bytes the cache can store and queues a job - * to trim the existing store, if necessary. - */ - public synchronized void setMaxSize(long maxSize) { - this.maxSize = maxSize; - executorService.submit(cleanupCallable); - } - - /** - * Returns the number of bytes currently being used to store the values in - * this cache. This may be greater than the max size if a background - * deletion is pending. - */ - public synchronized long size() { - return size; - } - - private synchronized void completeEdit(Editor editor, boolean success) throws IOException { - Entry entry = editor.entry; - if (entry.currentEditor != editor) { - throw new IllegalStateException(); - } - - // If this edit is creating the entry for the first time, every index must have a value. - if (success && !entry.readable) { - for (int i = 0; i < valueCount; i++) { - if (!editor.written[i]) { - editor.abort(); - throw new IllegalStateException("Newly created entry didn't create value for index " + i); - } - if (!entry.getDirtyFile(i).exists()) { - editor.abort(); - return; - } - } - } - - for (int i = 0; i < valueCount; i++) { - File dirty = entry.getDirtyFile(i); - if (success) { - if (dirty.exists()) { - File clean = entry.getCleanFile(i); - dirty.renameTo(clean); - long oldLength = entry.lengths[i]; - long newLength = clean.length(); - entry.lengths[i] = newLength; - size = size - oldLength + newLength; - } - } else { - deleteIfExists(dirty); - } - } - - redundantOpCount++; - entry.currentEditor = null; - if (entry.readable | success) { - entry.readable = true; - journalWriter.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n'); - if (success) { - entry.sequenceNumber = nextSequenceNumber++; - } - } else { - lruEntries.remove(entry.key); - journalWriter.write(REMOVE + ' ' + entry.key + '\n'); - } - journalWriter.flush(); - - if (size > maxSize || journalRebuildRequired()) { - executorService.submit(cleanupCallable); - } - } - - /** - * We only rebuild the journal when it will halve the size of the journal - * and eliminate at least 2000 ops. - */ - private boolean journalRebuildRequired() { - final int redundantOpCompactThreshold = 2000; - return redundantOpCount >= redundantOpCompactThreshold // - && redundantOpCount >= lruEntries.size(); - } - - /** - * Drops the entry for {@code key} if it exists and can be removed. Entries - * actively being edited cannot be removed. - * - * @return true if an entry was removed. - */ - public synchronized boolean remove(String key) throws IOException { - checkNotClosed(); - validateKey(key); - Entry entry = lruEntries.get(key); - if (entry == null || entry.currentEditor != null) { - return false; - } - - for (int i = 0; i < valueCount; i++) { - File file = entry.getCleanFile(i); - if (!file.delete()) { - throw new IOException("failed to delete " + file); - } - size -= entry.lengths[i]; - entry.lengths[i] = 0; - } - - redundantOpCount++; - journalWriter.append(REMOVE + ' ' + key + '\n'); - lruEntries.remove(key); - - if (journalRebuildRequired()) { - executorService.submit(cleanupCallable); - } - - return true; - } - - /** Returns true if this cache has been closed. */ - public boolean isClosed() { - return journalWriter == null; - } - - private void checkNotClosed() { - if (journalWriter == null) { - throw new IllegalStateException("cache is closed"); - } - } - - /** Force buffered operations to the filesystem. */ - public synchronized void flush() throws IOException { - checkNotClosed(); - trimToSize(); - journalWriter.flush(); - } - - /** Closes this cache. Stored values will remain on the filesystem. */ - public synchronized void close() throws IOException { - if (journalWriter == null) { - return; // Already closed. - } - for (Entry entry : new ArrayList(lruEntries.values())) { - if (entry.currentEditor != null) { - entry.currentEditor.abort(); - } - } - trimToSize(); - journalWriter.close(); - journalWriter = null; - } - - private void trimToSize() throws IOException { - while (size > maxSize) { - Map.Entry toEvict = lruEntries.entrySet().iterator().next(); - remove(toEvict.getKey()); - } - } - - /** - * Closes the cache and deletes all of its stored values. This will delete - * all files in the cache directory including files that weren't created by - * the cache. - */ - public void delete() throws IOException { - close(); - Util.deleteContents(directory); - } - - private void validateKey(String key) { - Matcher matcher = LEGAL_KEY_PATTERN.matcher(key); - if (!matcher.matches()) { - throw new IllegalArgumentException("keys must match regex [a-z0-9_-]{1,64}: \"" + key + "\""); - } - } - - private static String inputStreamToString(InputStream in) throws IOException { - return Util.readFully(new InputStreamReader(in, Util.UTF_8)); - } - - /** A snapshot of the values for an entry. */ - public final class Snapshot implements Closeable { - private final String key; - private final long sequenceNumber; - private final InputStream[] ins; - private final long[] lengths; - - private Snapshot(String key, long sequenceNumber, InputStream[] ins, long[] lengths) { - this.key = key; - this.sequenceNumber = sequenceNumber; - this.ins = ins; - this.lengths = lengths; - } - - /** - * Returns an editor for this snapshot's entry, or null if either the - * entry has changed since this snapshot was created or if another edit - * is in progress. - */ - public Editor edit() throws IOException { - return DiskLruCache.this.edit(key, sequenceNumber); - } - - /** Returns the unbuffered stream with the value for {@code index}. */ - public InputStream getInputStream(int index) { - return ins[index]; - } - - /** Returns the string value for {@code index}. */ - public String getString(int index) throws IOException { - return inputStreamToString(getInputStream(index)); - } - - /** Returns the byte length of the value for {@code index}. */ - public long getLength(int index) { - return lengths[index]; - } - - public void close() { - for (InputStream in : ins) { - Util.closeQuietly(in); - } - } - } - - private static final OutputStream NULL_OUTPUT_STREAM = new OutputStream() { - @Override - public void write(int b) throws IOException { - // Eat all writes silently. Nom nom. - } - }; - - /** Edits the values for an entry. */ - public final class Editor { - private final Entry entry; - private final boolean[] written; - private boolean hasErrors; - private boolean committed; - - private Editor(Entry entry) { - this.entry = entry; - this.written = (entry.readable) ? null : new boolean[valueCount]; - } - - /** - * Returns an unbuffered input stream to read the last committed value, - * or null if no value has been committed. - */ - public InputStream newInputStream(int index) throws IOException { - synchronized (DiskLruCache.this) { - if (entry.currentEditor != this) { - throw new IllegalStateException(); - } - if (!entry.readable) { - return null; - } - try { - return new FileInputStream(entry.getCleanFile(index)); - } catch (FileNotFoundException e) { - return null; - } - } - } - - /** - * Returns the last committed value as a string, or null if no value - * has been committed. - */ - public String getString(int index) throws IOException { - InputStream in = newInputStream(index); - return in != null ? inputStreamToString(in) : null; - } - - /** - * Returns a new unbuffered output stream to write the value at - * {@code index}. If the underlying output stream encounters errors - * when writing to the filesystem, this edit will be aborted when - * {@link #commit} is called. The returned output stream does not throw - * IOExceptions. - */ - public OutputStream newOutputStream(int index) throws IOException { - synchronized (DiskLruCache.this) { - if (entry.currentEditor != this) { - throw new IllegalStateException(); - } - if (!entry.readable) { - written[index] = true; - } - File dirtyFile = entry.getDirtyFile(index); - FileOutputStream outputStream; - try { - outputStream = new FileOutputStream(dirtyFile); - } catch (FileNotFoundException e) { - // Attempt to recreate the cache directory. - directory.mkdirs(); - try { - outputStream = new FileOutputStream(dirtyFile); - } catch (FileNotFoundException e2) { - // We are unable to recover. Silently eat the writes. - return NULL_OUTPUT_STREAM; - } - } - return new FaultHidingOutputStream(outputStream); - } - } - - /** Sets the value at {@code index} to {@code value}. */ - public void set(int index, String value) throws IOException { - Writer writer = null; - try { - writer = new OutputStreamWriter(newOutputStream(index), Util.UTF_8); - writer.write(value); - } finally { - Util.closeQuietly(writer); - } - } - - /** - * Commits this edit so it is visible to readers. This releases the - * edit lock so another edit may be started on the same key. - */ - public void commit() throws IOException { - if (hasErrors) { - completeEdit(this, false); - remove(entry.key); // The previous entry is stale. - } else { - completeEdit(this, true); - } - committed = true; - } - - /** - * Aborts this edit. This releases the edit lock so another edit may be - * started on the same key. - */ - public void abort() throws IOException { - completeEdit(this, false); - } - - public void abortUnlessCommitted() { - if (!committed) { - try { - abort(); - } catch (IOException ignored) { - } - } - } - - private class FaultHidingOutputStream extends FilterOutputStream { - private FaultHidingOutputStream(OutputStream out) { - super(out); - } - - @Override public void write(int oneByte) { - try { - out.write(oneByte); - } catch (IOException e) { - hasErrors = true; - } - } - - @Override public void write(byte[] buffer, int offset, int length) { - try { - out.write(buffer, offset, length); - } catch (IOException e) { - hasErrors = true; - } - } - - @Override public void close() { - try { - out.close(); - } catch (IOException e) { - hasErrors = true; - } - } - - @Override public void flush() { - try { - out.flush(); - } catch (IOException e) { - hasErrors = true; - } - } - } - } - - private final class Entry { - private final String key; - - /** Lengths of this entry's files. */ - private final long[] lengths; - - /** True if this entry has ever been published. */ - private boolean readable; - - /** The ongoing edit or null if this entry is not being edited. */ - private Editor currentEditor; - - /** The sequence number of the most recently committed edit to this entry. */ - private long sequenceNumber; - - private Entry(String key) { - this.key = key; - this.lengths = new long[valueCount]; - } - - public String getLengths() throws IOException { - StringBuilder result = new StringBuilder(); - for (long size : lengths) { - result.append(' ').append(size); - } - return result.toString(); - } - - /** Set lengths using decimal numbers like "10123". */ - private void setLengths(String[] strings) throws IOException { - if (strings.length != valueCount) { - throw invalidLengths(strings); - } - - try { - for (int i = 0; i < strings.length; i++) { - lengths[i] = Long.parseLong(strings[i]); - } - } catch (NumberFormatException e) { - throw invalidLengths(strings); - } - } - - private IOException invalidLengths(String[] strings) throws IOException { - throw new IOException("unexpected journal line: " + java.util.Arrays.toString(strings)); - } - - public File getCleanFile(int i) { - return new File(directory, key + "." + i); - } - - public File getDirtyFile(int i) { - return new File(directory, key + "." + i + ".tmp"); - } - } -} diff --git a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/Dns.java b/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/Dns.java deleted file mode 100644 index 69b2d37e9..000000000 --- a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/Dns.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2012 Square, Inc. - * - * Licensed 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. - */ -package com.squareup.okhttp.internal; - -import java.net.InetAddress; -import java.net.UnknownHostException; - -/** - * Domain name service. Prefer this over {@link InetAddress#getAllByName} to - * make code more testable. - */ -public interface Dns { - Dns DEFAULT = new Dns() { - @Override public InetAddress[] getAllByName(String host) throws UnknownHostException { - return InetAddress.getAllByName(host); - } - }; - - InetAddress[] getAllByName(String host) throws UnknownHostException; -} diff --git a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/FaultRecoveringOutputStream.java b/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/FaultRecoveringOutputStream.java deleted file mode 100644 index c32b27aeb..000000000 --- a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/FaultRecoveringOutputStream.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright (C) 2013 Square, Inc. - * - * Licensed 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. - */ -package com.squareup.okhttp.internal; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; - -import static com.squareup.okhttp.internal.Util.checkOffsetAndCount; - -/** - * An output stream wrapper that recovers from failures in the underlying stream - * by replacing it with another stream. This class buffers a fixed amount of - * data under the assumption that failures occur early in a stream's life. - * If a failure occurs after the buffer has been exhausted, no recovery is - * attempted. - * - *

Subclasses must override {@link #replacementStream} which will request a - * replacement stream each time an {@link IOException} is encountered on the - * current stream. - */ -public abstract class FaultRecoveringOutputStream extends AbstractOutputStream { - private final int maxReplayBufferLength; - - /** Bytes to transmit on the replacement stream, or null if no recovery is possible. */ - private ByteArrayOutputStream replayBuffer; - private OutputStream out; - - /** - * @param maxReplayBufferLength the maximum number of successfully written - * bytes to buffer so they can be replayed in the event of an error. - * Failure recoveries are not possible once this limit has been exceeded. - */ - public FaultRecoveringOutputStream(int maxReplayBufferLength, OutputStream out) { - if (maxReplayBufferLength < 0) throw new IllegalArgumentException(); - this.maxReplayBufferLength = maxReplayBufferLength; - this.replayBuffer = new ByteArrayOutputStream(maxReplayBufferLength); - this.out = out; - } - - @Override public final void write(byte[] buffer, int offset, int count) throws IOException { - if (closed) throw new IOException("stream closed"); - checkOffsetAndCount(buffer.length, offset, count); - - while (true) { - try { - out.write(buffer, offset, count); - - if (replayBuffer != null) { - if (count + replayBuffer.size() > maxReplayBufferLength) { - // Failure recovery is no longer possible once we overflow the replay buffer. - replayBuffer = null; - } else { - // Remember the written bytes to the replay buffer. - replayBuffer.write(buffer, offset, count); - } - } - return; - } catch (IOException e) { - if (!recover(e)) throw e; - } - } - } - - @Override public final void flush() throws IOException { - if (closed) { - return; // don't throw; this stream might have been closed on the caller's behalf - } - while (true) { - try { - out.flush(); - return; - } catch (IOException e) { - if (!recover(e)) throw e; - } - } - } - - @Override public final void close() throws IOException { - if (closed) { - return; - } - while (true) { - try { - out.close(); - closed = true; - return; - } catch (IOException e) { - if (!recover(e)) throw e; - } - } - } - - /** - * Attempt to replace {@code out} with another equivalent stream. Returns true - * if a suitable replacement stream was found. - */ - private boolean recover(IOException e) { - if (replayBuffer == null) { - return false; // Can't recover because we've dropped data that we would need to replay. - } - - while (true) { - OutputStream replacementStream = null; - try { - replacementStream = replacementStream(e); - if (replacementStream == null) { - return false; - } - replaceStream(replacementStream); - return true; - } catch (IOException replacementStreamFailure) { - // The replacement was also broken. Loop to ask for another replacement. - Util.closeQuietly(replacementStream); - e = replacementStreamFailure; - } - } - } - - /** - * Returns true if errors in the underlying stream can currently be recovered. - */ - public boolean isRecoverable() { - return replayBuffer != null; - } - - /** - * Replaces the current output stream with {@code replacementStream}, writing - * any replay bytes to it if they exist. The current output stream is closed. - */ - public final void replaceStream(OutputStream replacementStream) throws IOException { - if (!isRecoverable()) { - throw new IllegalStateException(); - } - if (this.out == replacementStream) { - return; // Don't replace a stream with itself. - } - replayBuffer.writeTo(replacementStream); - Util.closeQuietly(out); - out = replacementStream; - } - - /** - * Returns a replacement output stream to recover from {@code e} thrown by the - * previous stream. Returns a new OutputStream if recovery was successful, in - * which case all previously-written data will be replayed. Returns null if - * the failure cannot be recovered. - */ - protected abstract OutputStream replacementStream(IOException e) throws IOException; -} diff --git a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/NamedRunnable.java b/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/NamedRunnable.java deleted file mode 100644 index ce430b27b..000000000 --- a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/NamedRunnable.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2013 Square, Inc. - * - * Licensed 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. - */ - -package com.squareup.okhttp.internal; - -/** - * Runnable implementation which always sets its thread name. - */ -public abstract class NamedRunnable implements Runnable { - private String name; - - public NamedRunnable(String name) { - this.name = name; - } - - @Override public final void run() { - String oldName = Thread.currentThread().getName(); - Thread.currentThread().setName(name); - try { - execute(); - } finally { - Thread.currentThread().setName(oldName); - } - } - - protected abstract void execute(); -} diff --git a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/Platform.java b/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/Platform.java deleted file mode 100644 index 6b4ac343b..000000000 --- a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/Platform.java +++ /dev/null @@ -1,389 +0,0 @@ -/* - * Copyright (C) 2012 Square, Inc. - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed 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. - */ -package com.squareup.okhttp.internal; - -import com.squareup.okhttp.OkHttpClient; -import java.io.IOException; -import java.io.OutputStream; -import java.io.UnsupportedEncodingException; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; -import java.net.NetworkInterface; -import java.net.Socket; -import java.net.SocketException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.zip.Deflater; -import java.util.zip.DeflaterOutputStream; -import javax.net.ssl.SSLSocket; - -/** - * Access to Platform-specific features necessary for SPDY and advanced TLS. - * - *

SPDY

- * SPDY requires a TLS extension called NPN (Next Protocol Negotiation) that's - * available in Android 4.1+ and OpenJDK 7+ (with the npn-boot extension). It - * also requires a recent version of {@code DeflaterOutputStream} that is - * public API in Java 7 and callable via reflection in Android 4.1+. - */ -public class Platform { - private static final Platform PLATFORM = findPlatform(); - - private Constructor deflaterConstructor; - - public static Platform get() { - return PLATFORM; - } - - public void logW(String warning) { - System.out.println(warning); - } - - public void tagSocket(Socket socket) throws SocketException { - } - - public void untagSocket(Socket socket) throws SocketException { - } - - public URI toUriLenient(URL url) throws URISyntaxException { - return url.toURI(); // this isn't as good as the built-in toUriLenient - } - - /** - * Attempt a TLS connection with useful extensions enabled. This mode - * supports more features, but is less likely to be compatible with older - * HTTPS servers. - */ - public void enableTlsExtensions(SSLSocket socket, String uriHost) { - } - - /** - * Attempt a secure connection with basic functionality to maximize - * compatibility. Currently this uses SSL 3.0. - */ - public void supportTlsIntolerantServer(SSLSocket socket) { - socket.setEnabledProtocols(new String[] {"SSLv3"}); - } - - /** Returns the negotiated protocol, or null if no protocol was negotiated. */ - public byte[] getNpnSelectedProtocol(SSLSocket socket) { - return null; - } - - /** - * Sets client-supported protocols on a socket to send to a server. The - * protocols are only sent if the socket implementation supports NPN. - */ - public void setNpnProtocols(SSLSocket socket, byte[] npnProtocols) { - } - - /** - * Returns a deflater output stream that supports SYNC_FLUSH for SPDY name - * value blocks. This throws an {@link UnsupportedOperationException} on - * Java 6 and earlier where there is no built-in API to do SYNC_FLUSH. - */ - public OutputStream newDeflaterOutputStream(OutputStream out, Deflater deflater, - boolean syncFlush) { - try { - Constructor constructor = deflaterConstructor; - if (constructor == null) { - constructor = deflaterConstructor = DeflaterOutputStream.class.getConstructor( - OutputStream.class, Deflater.class, boolean.class); - } - return constructor.newInstance(out, deflater, syncFlush); - } catch (NoSuchMethodException e) { - throw new UnsupportedOperationException("Cannot SPDY; no SYNC_FLUSH available"); - } catch (InvocationTargetException e) { - throw e.getCause() instanceof RuntimeException ? (RuntimeException) e.getCause() - : new RuntimeException(e.getCause()); - } catch (InstantiationException e) { - throw new RuntimeException(e); - } catch (IllegalAccessException e) { - throw new AssertionError(); - } - } - - /** - * Returns the maximum transmission unit of the network interface used by - * {@code socket}, or a reasonable default if this platform doesn't expose the - * MTU to the application layer. - * - *

The returned value should only be used as an optimization; such as to - * size buffers efficiently. - */ - public int getMtu(Socket socket) throws IOException { - return 1400; // Smaller than 1500 to leave room for headers on interfaces like PPPoE. - } - - /** Attempt to match the host runtime to a capable Platform implementation. */ - private static Platform findPlatform() { - Method getMtu; - try { - getMtu = NetworkInterface.class.getMethod("getMTU"); - } catch (NoSuchMethodException e) { - return new Platform(); // No Java 1.6 APIs. It's either Java 1.5, Android 2.2 or earlier. - } - - // Attempt to find Android 2.3+ APIs. - Class openSslSocketClass; - Method setUseSessionTickets; - Method setHostname; - try { - openSslSocketClass = Class.forName("org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl"); - setUseSessionTickets = openSslSocketClass.getMethod("setUseSessionTickets", boolean.class); - setHostname = openSslSocketClass.getMethod("setHostname", String.class); - - // Attempt to find Android 4.1+ APIs. - try { - Method setNpnProtocols = openSslSocketClass.getMethod("setNpnProtocols", byte[].class); - Method getNpnSelectedProtocol = openSslSocketClass.getMethod("getNpnSelectedProtocol"); - return new Android41(getMtu, openSslSocketClass, setUseSessionTickets, setHostname, - setNpnProtocols, getNpnSelectedProtocol); - } catch (NoSuchMethodException ignored) { - return new Android23(getMtu, openSslSocketClass, setUseSessionTickets, setHostname); - } - } catch (ClassNotFoundException ignored) { - // This isn't an Android runtime. - } catch (NoSuchMethodException ignored) { - // This isn't Android 2.3 or better. - } - - // Attempt to find the Jetty's NPN extension for OpenJDK. - try { - String npnClassName = "org.eclipse.jetty.npn.NextProtoNego"; - Class nextProtoNegoClass = Class.forName(npnClassName); - Class providerClass = Class.forName(npnClassName + "$Provider"); - Class clientProviderClass = Class.forName(npnClassName + "$ClientProvider"); - Class serverProviderClass = Class.forName(npnClassName + "$ServerProvider"); - Method putMethod = nextProtoNegoClass.getMethod("put", SSLSocket.class, providerClass); - Method getMethod = nextProtoNegoClass.getMethod("get", SSLSocket.class); - return new JdkWithJettyNpnPlatform(getMtu, putMethod, getMethod, clientProviderClass, - serverProviderClass); - } catch (ClassNotFoundException ignored) { - // NPN isn't on the classpath. - } catch (NoSuchMethodException ignored) { - // The NPN version isn't what we expect. - } - - return getMtu != null ? new Java5(getMtu) : new Platform(); - } - - private static class Java5 extends Platform { - private final Method getMtu; - - private Java5(Method getMtu) { - this.getMtu = getMtu; - } - - @Override public int getMtu(Socket socket) throws IOException { - try { - NetworkInterface networkInterface = NetworkInterface.getByInetAddress( - socket.getLocalAddress()); - return (Integer) getMtu.invoke(networkInterface); - } catch (IllegalAccessException e) { - throw new AssertionError(e); - } catch (InvocationTargetException e) { - if (e.getCause() instanceof IOException) throw (IOException) e.getCause(); - throw new RuntimeException(e.getCause()); - } - } - } - - /** - * Android version 2.3 and newer support TLS session tickets and server name - * indication (SNI). - */ - private static class Android23 extends Java5 { - protected final Class openSslSocketClass; - private final Method setUseSessionTickets; - private final Method setHostname; - - private Android23(Method getMtu, Class openSslSocketClass, Method setUseSessionTickets, - Method setHostname) { - super(getMtu); - this.openSslSocketClass = openSslSocketClass; - this.setUseSessionTickets = setUseSessionTickets; - this.setHostname = setHostname; - } - - @Override public void enableTlsExtensions(SSLSocket socket, String uriHost) { - super.enableTlsExtensions(socket, uriHost); - if (openSslSocketClass.isInstance(socket)) { - // This is Android: use reflection on OpenSslSocketImpl. - try { - setUseSessionTickets.invoke(socket, true); - setHostname.invoke(socket, uriHost); - } catch (InvocationTargetException e) { - throw new RuntimeException(e); - } catch (IllegalAccessException e) { - throw new AssertionError(e); - } - } - } - } - - /** Android version 4.1 and newer support NPN. */ - private static class Android41 extends Android23 { - private final Method setNpnProtocols; - private final Method getNpnSelectedProtocol; - - private Android41(Method getMtu, Class openSslSocketClass, Method setUseSessionTickets, - Method setHostname, Method setNpnProtocols, Method getNpnSelectedProtocol) { - super(getMtu, openSslSocketClass, setUseSessionTickets, setHostname); - this.setNpnProtocols = setNpnProtocols; - this.getNpnSelectedProtocol = getNpnSelectedProtocol; - } - - @Override public void setNpnProtocols(SSLSocket socket, byte[] npnProtocols) { - if (!openSslSocketClass.isInstance(socket)) { - return; - } - try { - setNpnProtocols.invoke(socket, new Object[] {npnProtocols}); - } catch (IllegalAccessException e) { - throw new AssertionError(e); - } catch (InvocationTargetException e) { - throw new RuntimeException(e); - } - } - - @Override public byte[] getNpnSelectedProtocol(SSLSocket socket) { - if (!openSslSocketClass.isInstance(socket)) { - return null; - } - try { - return (byte[]) getNpnSelectedProtocol.invoke(socket); - } catch (InvocationTargetException e) { - throw new RuntimeException(e); - } catch (IllegalAccessException e) { - throw new AssertionError(e); - } - } - } - - /** - * OpenJDK 7 plus {@code org.mortbay.jetty.npn/npn-boot} on the boot class - * path. - */ - private static class JdkWithJettyNpnPlatform extends Java5 { - private final Method getMethod; - private final Method putMethod; - private final Class clientProviderClass; - private final Class serverProviderClass; - - public JdkWithJettyNpnPlatform(Method getMtu, Method putMethod, Method getMethod, - Class clientProviderClass, Class serverProviderClass) { - super(getMtu); - this.putMethod = putMethod; - this.getMethod = getMethod; - this.clientProviderClass = clientProviderClass; - this.serverProviderClass = serverProviderClass; - } - - @Override public void setNpnProtocols(SSLSocket socket, byte[] npnProtocols) { - try { - List strings = new ArrayList(); - for (int i = 0; i < npnProtocols.length; ) { - int length = npnProtocols[i++]; - strings.add(new String(npnProtocols, i, length, "US-ASCII")); - i += length; - } - Object provider = Proxy.newProxyInstance(Platform.class.getClassLoader(), - new Class[] {clientProviderClass, serverProviderClass}, - new JettyNpnProvider(strings)); - putMethod.invoke(null, socket, provider); - } catch (UnsupportedEncodingException e) { - throw new AssertionError(e); - } catch (InvocationTargetException e) { - throw new AssertionError(e); - } catch (IllegalAccessException e) { - throw new AssertionError(e); - } - } - - @Override public byte[] getNpnSelectedProtocol(SSLSocket socket) { - try { - JettyNpnProvider provider = - (JettyNpnProvider) Proxy.getInvocationHandler(getMethod.invoke(null, socket)); - if (!provider.unsupported && provider.selected == null) { - Logger logger = Logger.getLogger(OkHttpClient.class.getName()); - logger.log(Level.INFO, - "NPN callback dropped so SPDY is disabled. " + "Is npn-boot on the boot class path?"); - return null; - } - return provider.unsupported ? null : provider.selected.getBytes("US-ASCII"); - } catch (UnsupportedEncodingException e) { - throw new AssertionError(); - } catch (InvocationTargetException e) { - throw new AssertionError(); - } catch (IllegalAccessException e) { - throw new AssertionError(); - } - } - } - - /** - * Handle the methods of NextProtoNego's ClientProvider and ServerProvider - * without a compile-time dependency on those interfaces. - */ - private static class JettyNpnProvider implements InvocationHandler { - private final List protocols; - private boolean unsupported; - private String selected; - - public JettyNpnProvider(List protocols) { - this.protocols = protocols; - } - - @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - String methodName = method.getName(); - Class returnType = method.getReturnType(); - if (args == null) { - args = Util.EMPTY_STRING_ARRAY; - } - if (methodName.equals("supports") && boolean.class == returnType) { - return true; - } else if (methodName.equals("unsupported") && void.class == returnType) { - this.unsupported = true; - return null; - } else if (methodName.equals("protocols") && args.length == 0) { - return protocols; - } else if (methodName.equals("selectProtocol") - && String.class == returnType - && args.length == 1 - && (args[0] == null || args[0] instanceof List)) { - // TODO: use OpenSSL's algorithm which uses both lists - List serverProtocols = (List) args[0]; - this.selected = protocols.get(0); - return selected; - } else if (methodName.equals("protocolSelected") && args.length == 1) { - this.selected = (String) args[0]; - return null; - } else { - return method.invoke(this, args); - } - } - } -} diff --git a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/StrictLineReader.java b/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/StrictLineReader.java deleted file mode 100644 index 3ddc693c4..000000000 --- a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/StrictLineReader.java +++ /dev/null @@ -1,208 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed 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. - */ - -package com.squareup.okhttp.internal; - -import java.io.ByteArrayOutputStream; -import java.io.Closeable; -import java.io.EOFException; -import java.io.IOException; -import java.io.InputStream; -import java.io.UnsupportedEncodingException; -import java.nio.charset.Charset; - -/** - * Buffers input from an {@link InputStream} for reading lines. - * - *

This class is used for buffered reading of lines. For purposes of this class, a line ends with - * "\n" or "\r\n". End of input is reported by throwing {@code EOFException}. Unterminated line at - * end of input is invalid and will be ignored, the caller may use {@code hasUnterminatedLine()} - * to detect it after catching the {@code EOFException}. - * - *

This class is intended for reading input that strictly consists of lines, such as line-based - * cache entries or cache journal. Unlike the {@link java.io.BufferedReader} which in conjunction - * with {@link java.io.InputStreamReader} provides similar functionality, this class uses different - * end-of-input reporting and a more restrictive definition of a line. - * - *

This class supports only charsets that encode '\r' and '\n' as a single byte with value 13 - * and 10, respectively, and the representation of no other character contains these values. - * We currently check in constructor that the charset is one of US-ASCII, UTF-8 and ISO-8859-1. - * The default charset is US_ASCII. - */ -public class StrictLineReader implements Closeable { - private static final byte CR = (byte) '\r'; - private static final byte LF = (byte) '\n'; - - private final InputStream in; - private final Charset charset; - - /* - * Buffered data is stored in {@code buf}. As long as no exception occurs, 0 <= pos <= end - * and the data in the range [pos, end) is buffered for reading. At end of input, if there is - * an unterminated line, we set end == -1, otherwise end == pos. If the underlying - * {@code InputStream} throws an {@code IOException}, end may remain as either pos or -1. - */ - private byte[] buf; - private int pos; - private int end; - - /** - * Constructs a new {@code LineReader} with the specified charset and the default capacity. - * - * @param in the {@code InputStream} to read data from. - * @param charset the charset used to decode data. Only US-ASCII, UTF-8 and ISO-8859-1 are - * supported. - * @throws NullPointerException if {@code in} or {@code charset} is null. - * @throws IllegalArgumentException if the specified charset is not supported. - */ - public StrictLineReader(InputStream in, Charset charset) { - this(in, 8192, charset); - } - - /** - * Constructs a new {@code LineReader} with the specified capacity and charset. - * - * @param in the {@code InputStream} to read data from. - * @param capacity the capacity of the buffer. - * @param charset the charset used to decode data. Only US-ASCII, UTF-8 and ISO-8859-1 are - * supported. - * @throws NullPointerException if {@code in} or {@code charset} is null. - * @throws IllegalArgumentException if {@code capacity} is negative or zero - * or the specified charset is not supported. - */ - public StrictLineReader(InputStream in, int capacity, Charset charset) { - if (in == null || charset == null) { - throw new NullPointerException(); - } - if (capacity < 0) { - throw new IllegalArgumentException("capacity <= 0"); - } - if (!(charset.equals(Util.US_ASCII))) { - throw new IllegalArgumentException("Unsupported encoding"); - } - - this.in = in; - this.charset = charset; - buf = new byte[capacity]; - } - - /** - * Closes the reader by closing the underlying {@code InputStream} and - * marking this reader as closed. - * - * @throws IOException for errors when closing the underlying {@code InputStream}. - */ - public void close() throws IOException { - synchronized (in) { - if (buf != null) { - buf = null; - in.close(); - } - } - } - - /** - * Reads the next line. A line ends with {@code "\n"} or {@code "\r\n"}, - * this end of line marker is not included in the result. - * - * @return the next line from the input. - * @throws IOException for underlying {@code InputStream} errors. - * @throws EOFException for the end of source stream. - */ - public String readLine() throws IOException { - synchronized (in) { - if (buf == null) { - throw new IOException("LineReader is closed"); - } - - // Read more data if we are at the end of the buffered data. - // Though it's an error to read after an exception, we will let {@code fillBuf()} - // throw again if that happens; thus we need to handle end == -1 as well as end == pos. - if (pos >= end) { - fillBuf(); - } - // Try to find LF in the buffered data and return the line if successful. - for (int i = pos; i != end; ++i) { - if (buf[i] == LF) { - int lineEnd = (i != pos && buf[i - 1] == CR) ? i - 1 : i; - String res = new String(buf, pos, lineEnd - pos, charset.name()); - pos = i + 1; - return res; - } - } - - // Let's anticipate up to 80 characters on top of those already read. - ByteArrayOutputStream out = new ByteArrayOutputStream(end - pos + 80) { - @Override - public String toString() { - int length = (count > 0 && buf[count - 1] == CR) ? count - 1 : count; - try { - return new String(buf, 0, length, charset.name()); - } catch (UnsupportedEncodingException e) { - throw new AssertionError(e); // Since we control the charset this will never happen. - } - } - }; - - while (true) { - out.write(buf, pos, end - pos); - // Mark unterminated line in case fillBuf throws EOFException or IOException. - end = -1; - fillBuf(); - // Try to find LF in the buffered data and return the line if successful. - for (int i = pos; i != end; ++i) { - if (buf[i] == LF) { - if (i != pos) { - out.write(buf, pos, i - pos); - } - pos = i + 1; - return out.toString(); - } - } - } - } - } - - /** - * Read an {@code int} from a line containing its decimal representation. - * - * @return the value of the {@code int} from the next line. - * @throws IOException for underlying {@code InputStream} errors or conversion error. - * @throws EOFException for the end of source stream. - */ - public int readInt() throws IOException { - String intString = readLine(); - try { - return Integer.parseInt(intString); - } catch (NumberFormatException e) { - throw new IOException("expected an int but was \"" + intString + "\""); - } - } - - /** - * Reads new input data into the buffer. Call only with pos == end or end == -1, - * depending on the desired outcome if the function throws. - */ - private void fillBuf() throws IOException { - int result = in.read(buf, 0, buf.length); - if (result == -1) { - throw new EOFException(); - } - pos = 0; - end = result; - } -} - diff --git a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/Util.java b/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/Util.java deleted file mode 100644 index 290e5ea9f..000000000 --- a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/Util.java +++ /dev/null @@ -1,327 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed 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. - */ - -package com.squareup.okhttp.internal; - -import java.io.Closeable; -import java.io.EOFException; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.Reader; -import java.io.StringWriter; -import java.net.Socket; -import java.net.URI; -import java.net.URL; -import java.nio.ByteOrder; -import java.nio.charset.Charset; -import java.util.concurrent.atomic.AtomicReference; - -/** Junk drawer of utility methods. */ -public final class Util { - public static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; - public static final String[] EMPTY_STRING_ARRAY = new String[0]; - - /** A cheap and type-safe constant for the ISO-8859-1 Charset. */ - public static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1"); - - /** A cheap and type-safe constant for the US-ASCII Charset. */ - public static final Charset US_ASCII = Charset.forName("US-ASCII"); - - /** A cheap and type-safe constant for the UTF-8 Charset. */ - public static final Charset UTF_8 = Charset.forName("UTF-8"); - private static AtomicReference skipBuffer = new AtomicReference(); - - private Util() { - } - - public static int getEffectivePort(URI uri) { - return getEffectivePort(uri.getScheme(), uri.getPort()); - } - - public static int getEffectivePort(URL url) { - return getEffectivePort(url.getProtocol(), url.getPort()); - } - - private static int getEffectivePort(String scheme, int specifiedPort) { - return specifiedPort != -1 ? specifiedPort : getDefaultPort(scheme); - } - - public static int getDefaultPort(String scheme) { - if ("http".equalsIgnoreCase(scheme)) { - return 80; - } else if ("https".equalsIgnoreCase(scheme)) { - return 443; - } else { - return -1; - } - } - - public static void checkOffsetAndCount(int arrayLength, int offset, int count) { - if ((offset | count) < 0 || offset > arrayLength || arrayLength - offset < count) { - throw new ArrayIndexOutOfBoundsException(); - } - } - - public static void pokeInt(byte[] dst, int offset, int value, ByteOrder order) { - if (order == ByteOrder.BIG_ENDIAN) { - dst[offset++] = (byte) ((value >> 24) & 0xff); - dst[offset++] = (byte) ((value >> 16) & 0xff); - dst[offset++] = (byte) ((value >> 8) & 0xff); - dst[offset] = (byte) ((value >> 0) & 0xff); - } else { - dst[offset++] = (byte) ((value >> 0) & 0xff); - dst[offset++] = (byte) ((value >> 8) & 0xff); - dst[offset++] = (byte) ((value >> 16) & 0xff); - dst[offset] = (byte) ((value >> 24) & 0xff); - } - } - - /** Returns true if two possibly-null objects are equal. */ - public static boolean equal(Object a, Object b) { - return a == b || (a != null && a.equals(b)); - } - - /** - * Closes {@code closeable}, ignoring any checked exceptions. Does nothing - * if {@code closeable} is null. - */ - public static void closeQuietly(Closeable closeable) { - if (closeable != null) { - try { - closeable.close(); - } catch (RuntimeException rethrown) { - throw rethrown; - } catch (Exception ignored) { - } - } - } - - /** - * Closes {@code socket}, ignoring any checked exceptions. Does nothing if - * {@code socket} is null. - */ - public static void closeQuietly(Socket socket) { - if (socket != null) { - try { - socket.close(); - } catch (RuntimeException rethrown) { - throw rethrown; - } catch (Exception ignored) { - } - } - } - - /** - * Closes {@code a} and {@code b}. If either close fails, this completes - * the other close and rethrows the first encountered exception. - */ - public static void closeAll(Closeable a, Closeable b) throws IOException { - Throwable thrown = null; - try { - a.close(); - } catch (Throwable e) { - thrown = e; - } - try { - b.close(); - } catch (Throwable e) { - if (thrown == null) thrown = e; - } - if (thrown == null) return; - if (thrown instanceof IOException) throw (IOException) thrown; - if (thrown instanceof RuntimeException) throw (RuntimeException) thrown; - if (thrown instanceof Error) throw (Error) thrown; - throw new AssertionError(thrown); - } - - /** - * Deletes the contents of {@code dir}. Throws an IOException if any file - * could not be deleted, or if {@code dir} is not a readable directory. - */ - public static void deleteContents(File dir) throws IOException { - File[] files = dir.listFiles(); - if (files == null) { - throw new IOException("not a readable directory: " + dir); - } - for (File file : files) { - if (file.isDirectory()) { - deleteContents(file); - } - if (!file.delete()) { - throw new IOException("failed to delete file: " + file); - } - } - } - - /** - * Implements InputStream.read(int) in terms of InputStream.read(byte[], int, int). - * InputStream assumes that you implement InputStream.read(int) and provides default - * implementations of the others, but often the opposite is more efficient. - */ - public static int readSingleByte(InputStream in) throws IOException { - byte[] buffer = new byte[1]; - int result = in.read(buffer, 0, 1); - return (result != -1) ? buffer[0] & 0xff : -1; - } - - /** - * Implements OutputStream.write(int) in terms of OutputStream.write(byte[], int, int). - * OutputStream assumes that you implement OutputStream.write(int) and provides default - * implementations of the others, but often the opposite is more efficient. - */ - public static void writeSingleByte(OutputStream out, int b) throws IOException { - byte[] buffer = new byte[1]; - buffer[0] = (byte) (b & 0xff); - out.write(buffer); - } - - /** - * Fills 'dst' with bytes from 'in', throwing EOFException if insufficient bytes are available. - */ - public static void readFully(InputStream in, byte[] dst) throws IOException { - readFully(in, dst, 0, dst.length); - } - - /** - * Reads exactly 'byteCount' bytes from 'in' (into 'dst' at offset 'offset'), and throws - * EOFException if insufficient bytes are available. - * - * Used to implement {@link java.io.DataInputStream#readFully(byte[], int, int)}. - */ - public static void readFully(InputStream in, byte[] dst, int offset, int byteCount) - throws IOException { - if (byteCount == 0) { - return; - } - if (in == null) { - throw new NullPointerException("in == null"); - } - if (dst == null) { - throw new NullPointerException("dst == null"); - } - checkOffsetAndCount(dst.length, offset, byteCount); - while (byteCount > 0) { - int bytesRead = in.read(dst, offset, byteCount); - if (bytesRead < 0) { - throw new EOFException(); - } - offset += bytesRead; - byteCount -= bytesRead; - } - } - - /** Returns the remainder of 'reader' as a string, closing it when done. */ - public static String readFully(Reader reader) throws IOException { - try { - StringWriter writer = new StringWriter(); - char[] buffer = new char[1024]; - int count; - while ((count = reader.read(buffer)) != -1) { - writer.write(buffer, 0, count); - } - return writer.toString(); - } finally { - reader.close(); - } - } - - public static void skipAll(InputStream in) throws IOException { - do { - in.skip(Long.MAX_VALUE); - } while (in.read() != -1); - } - - /** - * Call {@code in.read()} repeatedly until either the stream is exhausted or - * {@code byteCount} bytes have been read. - * - *

This method reuses the skip buffer but is careful to never use it at - * the same time that another stream is using it. Otherwise streams that use - * the caller's buffer for consistency checks like CRC could be clobbered by - * other threads. A thread-local buffer is also insufficient because some - * streams may call other streams in their skip() method, also clobbering the - * buffer. - */ - public static long skipByReading(InputStream in, long byteCount) throws IOException { - // acquire the shared skip buffer. - byte[] buffer = skipBuffer.getAndSet(null); - if (buffer == null) { - buffer = new byte[4096]; - } - - long skipped = 0; - while (skipped < byteCount) { - int toRead = (int) Math.min(byteCount - skipped, buffer.length); - int read = in.read(buffer, 0, toRead); - if (read == -1) { - break; - } - skipped += read; - if (read < toRead) { - break; - } - } - - // release the shared skip buffer. - skipBuffer.set(buffer); - - return skipped; - } - - /** - * Copies all of the bytes from {@code in} to {@code out}. Neither stream is closed. - * Returns the total number of bytes transferred. - */ - public static int copy(InputStream in, OutputStream out) throws IOException { - int total = 0; - byte[] buffer = new byte[8192]; - int c; - while ((c = in.read(buffer)) != -1) { - total += c; - out.write(buffer, 0, c); - } - return total; - } - - /** - * Returns the ASCII characters up to but not including the next "\r\n", or - * "\n". - * - * @throws java.io.EOFException if the stream is exhausted before the next newline - * character. - */ - public static String readAsciiLine(InputStream in) throws IOException { - // TODO: support UTF-8 here instead - StringBuilder result = new StringBuilder(80); - while (true) { - int c = in.read(); - if (c == -1) { - throw new EOFException(); - } else if (c == '\n') { - break; - } - - result.append((char) c); - } - int length = result.length(); - if (length > 0 && result.charAt(length - 1) == '\r') { - result.setLength(length - 1); - } - return result.toString(); - } -} diff --git a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/AbstractHttpInputStream.java b/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/AbstractHttpInputStream.java deleted file mode 100644 index 187f3b6e1..000000000 --- a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/AbstractHttpInputStream.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed 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. - */ - -package com.squareup.okhttp.internal.http; - -import com.squareup.okhttp.internal.Util; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.CacheRequest; - -/** - * An input stream for the body of an HTTP response. - * - *

Since a single socket's input stream may be used to read multiple HTTP - * responses from the same server, subclasses shouldn't close the socket stream. - * - *

A side effect of reading an HTTP response is that the response cache - * is populated. If the stream is closed early, that cache entry will be - * invalidated. - */ -abstract class AbstractHttpInputStream extends InputStream { - protected final InputStream in; - protected final HttpEngine httpEngine; - private final CacheRequest cacheRequest; - private final OutputStream cacheBody; - protected boolean closed; - - AbstractHttpInputStream(InputStream in, HttpEngine httpEngine, CacheRequest cacheRequest) - throws IOException { - this.in = in; - this.httpEngine = httpEngine; - - OutputStream cacheBody = cacheRequest != null ? cacheRequest.getBody() : null; - - // some apps return a null body; for compatibility we treat that like a null cache request - if (cacheBody == null) { - cacheRequest = null; - } - - this.cacheBody = cacheBody; - this.cacheRequest = cacheRequest; - } - - /** - * read() is implemented using read(byte[], int, int) so subclasses only - * need to override the latter. - */ - @Override public final int read() throws IOException { - return Util.readSingleByte(this); - } - - protected final void checkNotClosed() throws IOException { - if (closed) { - throw new IOException("stream closed"); - } - } - - protected final void cacheWrite(byte[] buffer, int offset, int count) throws IOException { - if (cacheBody != null) { - cacheBody.write(buffer, offset, count); - } - } - - /** - * Closes the cache entry and makes the socket available for reuse. This - * should be invoked when the end of the body has been reached. - */ - protected final void endOfInput(boolean streamCancelled) throws IOException { - if (cacheRequest != null) { - cacheBody.close(); - } - httpEngine.release(streamCancelled); - } - - /** - * Calls abort on the cache entry and disconnects the socket. This - * should be invoked when the connection is closed unexpectedly to - * invalidate the cache entry and to prevent the HTTP connection from - * being reused. HTTP messages are sent in serial so whenever a message - * cannot be read to completion, subsequent messages cannot be read - * either and the connection must be discarded. - * - *

An earlier implementation skipped the remaining bytes, but this - * requires that the entire transfer be completed. If the intention was - * to cancel the transfer, closing the connection is the only solution. - */ - protected final void unexpectedEndOfInput() { - if (cacheRequest != null) { - cacheRequest.abort(); - } - httpEngine.release(true); - } -} diff --git a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/AbstractHttpOutputStream.java b/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/AbstractHttpOutputStream.java deleted file mode 100644 index 90675b06b..000000000 --- a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/AbstractHttpOutputStream.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed 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. - */ - -package com.squareup.okhttp.internal.http; - -import java.io.IOException; -import java.io.OutputStream; - -/** - * An output stream for the body of an HTTP request. - * - *

Since a single socket's output stream may be used to write multiple HTTP - * requests to the same server, subclasses should not close the socket stream. - */ -abstract class AbstractHttpOutputStream extends OutputStream { - protected boolean closed; - - @Override public final void write(int data) throws IOException { - write(new byte[] { (byte) data }); - } - - protected final void checkNotClosed() throws IOException { - if (closed) { - throw new IOException("stream closed"); - } - } -} diff --git a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/HeaderParser.java b/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/HeaderParser.java deleted file mode 100644 index 12e640973..000000000 --- a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/HeaderParser.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed 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. - */ - -package com.squareup.okhttp.internal.http; - -final class HeaderParser { - - public interface CacheControlHandler { - void handle(String directive, String parameter); - } - - /** Parse a comma-separated list of cache control header values. */ - public static void parseCacheControl(String value, CacheControlHandler handler) { - int pos = 0; - while (pos < value.length()) { - int tokenStart = pos; - pos = skipUntil(value, pos, "=,"); - String directive = value.substring(tokenStart, pos).trim(); - - if (pos == value.length() || value.charAt(pos) == ',') { - pos++; // consume ',' (if necessary) - handler.handle(directive, null); - continue; - } - - pos++; // consume '=' - pos = skipWhitespace(value, pos); - - String parameter; - - // quoted string - if (pos < value.length() && value.charAt(pos) == '\"') { - pos++; // consume '"' open quote - int parameterStart = pos; - pos = skipUntil(value, pos, "\""); - parameter = value.substring(parameterStart, pos); - pos++; // consume '"' close quote (if necessary) - - // unquoted string - } else { - int parameterStart = pos; - pos = skipUntil(value, pos, ","); - parameter = value.substring(parameterStart, pos).trim(); - } - - handler.handle(directive, parameter); - } - } - - /** - * Returns the next index in {@code input} at or after {@code pos} that - * contains a character from {@code characters}. Returns the input length if - * none of the requested characters can be found. - */ - public static int skipUntil(String input, int pos, String characters) { - for (; pos < input.length(); pos++) { - if (characters.indexOf(input.charAt(pos)) != -1) { - break; - } - } - return pos; - } - - /** - * Returns the next non-whitespace character in {@code input} that is white - * space. Result is undefined if input contains newline characters. - */ - public static int skipWhitespace(String input, int pos) { - for (; pos < input.length(); pos++) { - char c = input.charAt(pos); - if (c != ' ' && c != '\t') { - break; - } - } - return pos; - } - - /** - * Returns {@code value} as a positive integer, or 0 if it is negative, or - * -1 if it cannot be parsed. - */ - public static int parseSeconds(String value) { - try { - long seconds = Long.parseLong(value); - if (seconds > Integer.MAX_VALUE) { - return Integer.MAX_VALUE; - } else if (seconds < 0) { - return 0; - } else { - return (int) seconds; - } - } catch (NumberFormatException e) { - return -1; - } - } - - private HeaderParser() { - } -} diff --git a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/HttpAuthenticator.java b/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/HttpAuthenticator.java deleted file mode 100644 index 4ccd12aa9..000000000 --- a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/HttpAuthenticator.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright (C) 2012 Square, Inc. - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed 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. - */ -package com.squareup.okhttp.internal.http; - -import com.squareup.okhttp.internal.Base64; -import java.io.IOException; -import java.net.Authenticator; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.PasswordAuthentication; -import java.net.Proxy; -import java.net.URL; -import java.util.ArrayList; -import java.util.List; - -import static java.net.HttpURLConnection.HTTP_PROXY_AUTH; -import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED; - -/** Handles HTTP authentication headers from origin and proxy servers. */ -public final class HttpAuthenticator { - private HttpAuthenticator() { - } - - /** - * React to a failed authorization response by looking up new credentials. - * - * @return true if credentials have been added to successorRequestHeaders - * and another request should be attempted. - */ - public static boolean processAuthHeader(int responseCode, RawHeaders responseHeaders, - RawHeaders successorRequestHeaders, Proxy proxy, URL url) throws IOException { - if (responseCode != HTTP_PROXY_AUTH && responseCode != HTTP_UNAUTHORIZED) { - throw new IllegalArgumentException(); - } - - // Keep asking for username/password until authorized. - String challengeHeader = - responseCode == HTTP_PROXY_AUTH ? "Proxy-Authenticate" : "WWW-Authenticate"; - String credentials = getCredentials(responseHeaders, challengeHeader, proxy, url); - if (credentials == null) { - return false; // Could not find credentials so end the request cycle. - } - - // Add authorization credentials, bypassing the already-connected check. - String fieldName = responseCode == HTTP_PROXY_AUTH ? "Proxy-Authorization" : "Authorization"; - successorRequestHeaders.set(fieldName, credentials); - return true; - } - - /** - * Returns the authorization credentials that may satisfy the challenge. - * Returns null if a challenge header was not provided or if credentials - * were not available. - */ - private static String getCredentials(RawHeaders responseHeaders, String challengeHeader, - Proxy proxy, URL url) throws IOException { - List challenges = parseChallenges(responseHeaders, challengeHeader); - if (challenges.isEmpty()) { - return null; - } - - for (Challenge challenge : challenges) { - // Use the global authenticator to get the password. - PasswordAuthentication auth; - if (responseHeaders.getResponseCode() == HTTP_PROXY_AUTH) { - InetSocketAddress proxyAddress = (InetSocketAddress) proxy.address(); - auth = Authenticator.requestPasswordAuthentication(proxyAddress.getHostName(), - getConnectToInetAddress(proxy, url), proxyAddress.getPort(), url.getProtocol(), - challenge.realm, challenge.scheme, url, Authenticator.RequestorType.PROXY); - } else { - auth = Authenticator.requestPasswordAuthentication(url.getHost(), - getConnectToInetAddress(proxy, url), url.getPort(), url.getProtocol(), challenge.realm, - challenge.scheme, url, Authenticator.RequestorType.SERVER); - } - if (auth == null) { - continue; - } - - // Use base64 to encode the username and password. - String usernameAndPassword = auth.getUserName() + ":" + new String(auth.getPassword()); - byte[] bytes = usernameAndPassword.getBytes("ISO-8859-1"); - String encoded = Base64.encode(bytes); - return challenge.scheme + " " + encoded; - } - - return null; - } - - private static InetAddress getConnectToInetAddress(Proxy proxy, URL url) throws IOException { - return (proxy != null && proxy.type() != Proxy.Type.DIRECT) - ? ((InetSocketAddress) proxy.address()).getAddress() : InetAddress.getByName(url.getHost()); - } - - /** - * Parse RFC 2617 challenges. This API is only interested in the scheme - * name and realm. - */ - private static List parseChallenges(RawHeaders responseHeaders, - String challengeHeader) { - // auth-scheme = token - // auth-param = token "=" ( token | quoted-string ) - // challenge = auth-scheme 1*SP 1#auth-param - // realm = "realm" "=" realm-value - // realm-value = quoted-string - List result = new ArrayList(); - for (int h = 0; h < responseHeaders.length(); h++) { - if (!challengeHeader.equalsIgnoreCase(responseHeaders.getFieldName(h))) { - continue; - } - String value = responseHeaders.getValue(h); - int pos = 0; - while (pos < value.length()) { - int tokenStart = pos; - pos = HeaderParser.skipUntil(value, pos, " "); - - String scheme = value.substring(tokenStart, pos).trim(); - pos = HeaderParser.skipWhitespace(value, pos); - - // TODO: This currently only handles schemes with a 'realm' parameter; - // It needs to be fixed to handle any scheme and any parameters - // http://code.google.com/p/android/issues/detail?id=11140 - - if (!value.regionMatches(pos, "realm=\"", 0, "realm=\"".length())) { - break; // Unexpected challenge parameter; give up! - } - - pos += "realm=\"".length(); - int realmStart = pos; - pos = HeaderParser.skipUntil(value, pos, "\""); - String realm = value.substring(realmStart, pos); - pos++; // Consume '"' close quote. - pos = HeaderParser.skipUntil(value, pos, ","); - pos++; // Consume ',' comma. - pos = HeaderParser.skipWhitespace(value, pos); - result.add(new Challenge(scheme, realm)); - } - } - return result; - } - - /** An RFC 2617 challenge. */ - private static final class Challenge { - final String scheme; - final String realm; - - Challenge(String scheme, String realm) { - this.scheme = scheme; - this.realm = realm; - } - - @Override public boolean equals(Object o) { - return o instanceof Challenge - && ((Challenge) o).scheme.equals(scheme) - && ((Challenge) o).realm.equals(realm); - } - - @Override public int hashCode() { - return scheme.hashCode() + 31 * realm.hashCode(); - } - } -} diff --git a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/HttpDate.java b/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/HttpDate.java deleted file mode 100644 index acb5fda9b..000000000 --- a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/HttpDate.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed 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. - */ - -package com.squareup.okhttp.internal.http; - -import java.text.DateFormat; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Locale; -import java.util.TimeZone; - -/** - * Best-effort parser for HTTP dates. - */ -final class HttpDate { - - /** - * Most websites serve cookies in the blessed format. Eagerly create the parser to ensure such - * cookies are on the fast path. - */ - private static final ThreadLocal STANDARD_DATE_FORMAT = - new ThreadLocal() { - @Override protected DateFormat initialValue() { - DateFormat rfc1123 = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US); - rfc1123.setTimeZone(TimeZone.getTimeZone("UTC")); - return rfc1123; - } - }; - - /** If we fail to parse a date in a non-standard format, try each of these formats in sequence. */ - private static final String[] BROWSER_COMPATIBLE_DATE_FORMATS = new String[] { - /* This list comes from {@code org.apache.http.impl.cookie.BrowserCompatSpec}. */ - "EEEE, dd-MMM-yy HH:mm:ss zzz", // RFC 1036 - "EEE MMM d HH:mm:ss yyyy", // ANSI C asctime() - "EEE, dd-MMM-yyyy HH:mm:ss z", "EEE, dd-MMM-yyyy HH-mm-ss z", "EEE, dd MMM yy HH:mm:ss z", - "EEE dd-MMM-yyyy HH:mm:ss z", "EEE dd MMM yyyy HH:mm:ss z", "EEE dd-MMM-yyyy HH-mm-ss z", - "EEE dd-MMM-yy HH:mm:ss z", "EEE dd MMM yy HH:mm:ss z", "EEE,dd-MMM-yy HH:mm:ss z", - "EEE,dd-MMM-yyyy HH:mm:ss z", "EEE, dd-MM-yyyy HH:mm:ss z", - - /* RI bug 6641315 claims a cookie of this format was once served by www.yahoo.com */ - "EEE MMM d yyyy HH:mm:ss z", }; - - /** - * Returns the date for {@code value}. Returns null if the value couldn't be - * parsed. - */ - public static Date parse(String value) { - try { - return STANDARD_DATE_FORMAT.get().parse(value); - } catch (ParseException ignore) { - } - for (String formatString : BROWSER_COMPATIBLE_DATE_FORMATS) { - try { - return new SimpleDateFormat(formatString, Locale.US).parse(value); - } catch (ParseException ignore) { - } - } - return null; - } - - /** Returns the string for {@code value}. */ - public static String format(Date value) { - return STANDARD_DATE_FORMAT.get().format(value); - } - - private HttpDate() { - } -} diff --git a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/HttpEngine.java b/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/HttpEngine.java deleted file mode 100644 index 7a06dca53..000000000 --- a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/HttpEngine.java +++ /dev/null @@ -1,664 +0,0 @@ -/* - * 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. - */ - -package com.squareup.okhttp.internal.http; - -import com.squareup.okhttp.Address; -import com.squareup.okhttp.Connection; -import com.squareup.okhttp.ResponseSource; -import com.squareup.okhttp.TunnelRequest; -import com.squareup.okhttp.internal.Dns; -import com.squareup.okhttp.internal.Platform; -import com.squareup.okhttp.internal.Util; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.CacheRequest; -import java.net.CacheResponse; -import java.net.CookieHandler; -import java.net.Proxy; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.net.UnknownHostException; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.zip.GZIPInputStream; -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.SSLSocketFactory; - -import static com.squareup.okhttp.internal.Util.EMPTY_BYTE_ARRAY; -import static com.squareup.okhttp.internal.Util.getDefaultPort; -import static com.squareup.okhttp.internal.Util.getEffectivePort; - -/** - * Handles a single HTTP request/response pair. Each HTTP engine follows this - * lifecycle: - *

    - *
  1. It is created. - *
  2. The HTTP request message is sent with sendRequest(). Once the request - * is sent it is an error to modify the request headers. After - * sendRequest() has been called the request body can be written to if - * it exists. - *
  3. The HTTP response message is read with readResponse(). After the - * response has been read the response headers and body can be read. - * All responses have a response body input stream, though in some - * instances this stream is empty. - *
- * - *

The request and response may be served by the HTTP response cache, by the - * network, or by both in the event of a conditional GET. - * - *

This class may hold a socket connection that needs to be released or - * recycled. By default, this socket connection is held when the last byte of - * the response is consumed. To release the connection when it is no longer - * required, use {@link #automaticallyReleaseConnectionToPool()}. - */ -public class HttpEngine { - private static final CacheResponse GATEWAY_TIMEOUT_RESPONSE = new CacheResponse() { - @Override public Map> getHeaders() throws IOException { - Map> result = new HashMap>(); - result.put(null, Collections.singletonList("HTTP/1.1 504 Gateway Timeout")); - return result; - } - @Override public InputStream getBody() throws IOException { - return new ByteArrayInputStream(EMPTY_BYTE_ARRAY); - } - }; - public static final int HTTP_CONTINUE = 100; - - protected final HttpURLConnectionImpl policy; - - protected final String method; - - private ResponseSource responseSource; - - protected Connection connection; - protected RouteSelector routeSelector; - private OutputStream requestBodyOut; - - private Transport transport; - - private InputStream responseTransferIn; - private InputStream responseBodyIn; - - private CacheResponse cacheResponse; - private CacheRequest cacheRequest; - - /** The time when the request headers were written, or -1 if they haven't been written yet. */ - long sentRequestMillis = -1; - - /** - * True if this client added an "Accept-Encoding: gzip" header field and is - * therefore responsible for also decompressing the transfer stream. - */ - private boolean transparentGzip; - - final URI uri; - - final RequestHeaders requestHeaders; - - /** Null until a response is received from the network or the cache. */ - ResponseHeaders responseHeaders; - - // The cache response currently being validated on a conditional get. Null - // if the cached response doesn't exist or doesn't need validation. If the - // conditional get succeeds, these will be used for the response headers and - // body. If it fails, these be closed and set to null. - private ResponseHeaders cachedResponseHeaders; - private InputStream cachedResponseBody; - - /** - * True if the socket connection should be released to the connection pool - * when the response has been fully read. - */ - private boolean automaticallyReleaseConnectionToPool; - - /** True if the socket connection is no longer needed by this engine. */ - private boolean connectionReleased; - - /** - * @param requestHeaders the client's supplied request headers. This class - * creates a private copy that it can mutate. - * @param connection the connection used for an intermediate response - * immediately prior to this request/response pair, such as a same-host - * redirect. This engine assumes ownership of the connection and must - * release it when it is unneeded. - */ - public HttpEngine(HttpURLConnectionImpl policy, String method, RawHeaders requestHeaders, - Connection connection, RetryableOutputStream requestBodyOut) throws IOException { - this.policy = policy; - this.method = method; - this.connection = connection; - this.requestBodyOut = requestBodyOut; - - try { - uri = Platform.get().toUriLenient(policy.getURL()); - } catch (URISyntaxException e) { - throw new IOException(e.getMessage()); - } - - this.requestHeaders = new RequestHeaders(uri, new RawHeaders(requestHeaders)); - } - - public URI getUri() { - return uri; - } - - /** - * Figures out what the response source will be, and opens a socket to that - * source if necessary. Prepares the request headers and gets ready to start - * writing the request body if it exists. - */ - public final void sendRequest() throws IOException { - if (responseSource != null) { - return; - } - - prepareRawRequestHeaders(); - initResponseSource(); - if (policy.responseCache != null) { - policy.responseCache.trackResponse(responseSource); - } - - // The raw response source may require the network, but the request - // headers may forbid network use. In that case, dispose of the network - // response and use a GATEWAY_TIMEOUT response instead, as specified - // by http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.4. - if (requestHeaders.isOnlyIfCached() && responseSource.requiresConnection()) { - if (responseSource == ResponseSource.CONDITIONAL_CACHE) { - Util.closeQuietly(cachedResponseBody); - } - this.responseSource = ResponseSource.CACHE; - this.cacheResponse = GATEWAY_TIMEOUT_RESPONSE; - RawHeaders rawResponseHeaders = RawHeaders.fromMultimap(cacheResponse.getHeaders(), true); - setResponse(new ResponseHeaders(uri, rawResponseHeaders), cacheResponse.getBody()); - } - - if (responseSource.requiresConnection()) { - sendSocketRequest(); - } else if (connection != null) { - policy.connectionPool.recycle(connection); - policy.getFailedRoutes().remove(connection.getRoute()); - connection = null; - } - } - - /** - * Initialize the source for this response. It may be corrected later if the - * request headers forbids network use. - */ - private void initResponseSource() throws IOException { - responseSource = ResponseSource.NETWORK; - if (!policy.getUseCaches() || policy.responseCache == null) { - return; - } - - CacheResponse candidate = - policy.responseCache.get(uri, method, requestHeaders.getHeaders().toMultimap(false)); - if (candidate == null) { - return; - } - - Map> responseHeadersMap = candidate.getHeaders(); - cachedResponseBody = candidate.getBody(); - if (!acceptCacheResponseType(candidate) - || responseHeadersMap == null - || cachedResponseBody == null) { - Util.closeQuietly(cachedResponseBody); - return; - } - - RawHeaders rawResponseHeaders = RawHeaders.fromMultimap(responseHeadersMap, true); - cachedResponseHeaders = new ResponseHeaders(uri, rawResponseHeaders); - long now = System.currentTimeMillis(); - this.responseSource = cachedResponseHeaders.chooseResponseSource(now, requestHeaders); - if (responseSource == ResponseSource.CACHE) { - this.cacheResponse = candidate; - setResponse(cachedResponseHeaders, cachedResponseBody); - } else if (responseSource == ResponseSource.CONDITIONAL_CACHE) { - this.cacheResponse = candidate; - } else if (responseSource == ResponseSource.NETWORK) { - Util.closeQuietly(cachedResponseBody); - } else { - throw new AssertionError(); - } - } - - private void sendSocketRequest() throws IOException { - if (connection == null) { - connect(); - } - - if (transport != null) { - throw new IllegalStateException(); - } - - transport = (Transport) connection.newTransport(this); - - if (hasRequestBody() && requestBodyOut == null) { - // Create a request body if we don't have one already. We'll already - // have one if we're retrying a failed POST. - requestBodyOut = transport.createRequestBody(); - } - } - - /** Connect to the origin server either directly or via a proxy. */ - protected final void connect() throws IOException { - if (connection != null) { - return; - } - if (routeSelector == null) { - String uriHost = uri.getHost(); - if (uriHost == null) { - throw new UnknownHostException(uri.toString()); - } - SSLSocketFactory sslSocketFactory = null; - HostnameVerifier hostnameVerifier = null; - if (uri.getScheme().equalsIgnoreCase("https")) { - sslSocketFactory = policy.sslSocketFactory; - hostnameVerifier = policy.hostnameVerifier; - } - Address address = new Address(uriHost, getEffectivePort(uri), sslSocketFactory, - hostnameVerifier, policy.requestedProxy); - routeSelector = new RouteSelector(address, uri, policy.proxySelector, policy.connectionPool, - Dns.DEFAULT, policy.getFailedRoutes()); - } - connection = routeSelector.next(); - if (!connection.isConnected()) { - connection.connect(policy.getConnectTimeout(), policy.getReadTimeout(), getTunnelConfig()); - policy.connectionPool.maybeShare(connection); - policy.getFailedRoutes().remove(connection.getRoute()); - } - connected(connection); - if (connection.getRoute().getProxy() != policy.requestedProxy) { - // Update the request line if the proxy changed; it may need a host name. - requestHeaders.getHeaders().setRequestLine(getRequestLine()); - } - } - - /** - * Called after a socket connection has been created or retrieved from the - * pool. Subclasses use this hook to get a reference to the TLS data. - */ - protected void connected(Connection connection) { - } - - /** - * Called immediately before the transport transmits HTTP request headers. - * This is used to observe the sent time should the request be cached. - */ - public void writingRequestHeaders() { - if (sentRequestMillis != -1) { - throw new IllegalStateException(); - } - sentRequestMillis = System.currentTimeMillis(); - } - - /** - * @param body the response body, or null if it doesn't exist or isn't - * available. - */ - private void setResponse(ResponseHeaders headers, InputStream body) throws IOException { - if (this.responseBodyIn != null) { - throw new IllegalStateException(); - } - this.responseHeaders = headers; - if (body != null) { - initContentStream(body); - } - } - - boolean hasRequestBody() { - return method.equals("POST") || method.equals("PUT"); - } - - /** Returns the request body or null if this request doesn't have a body. */ - public final OutputStream getRequestBody() { - if (responseSource == null) { - throw new IllegalStateException(); - } - return requestBodyOut; - } - - public final boolean hasResponse() { - return responseHeaders != null; - } - - public final RequestHeaders getRequestHeaders() { - return requestHeaders; - } - - public final ResponseHeaders getResponseHeaders() { - if (responseHeaders == null) { - throw new IllegalStateException(); - } - return responseHeaders; - } - - public final int getResponseCode() { - if (responseHeaders == null) { - throw new IllegalStateException(); - } - return responseHeaders.getHeaders().getResponseCode(); - } - - public final InputStream getResponseBody() { - if (responseHeaders == null) { - throw new IllegalStateException(); - } - return responseBodyIn; - } - - public final CacheResponse getCacheResponse() { - return cacheResponse; - } - - public final Connection getConnection() { - return connection; - } - - /** - * Returns true if {@code cacheResponse} is of the right type. This - * condition is necessary but not sufficient for the cached response to - * be used. - */ - protected boolean acceptCacheResponseType(CacheResponse cacheResponse) { - return true; - } - - private void maybeCache() throws IOException { - // Are we caching at all? - if (!policy.getUseCaches() || policy.responseCache == null) { - return; - } - - // Should we cache this response for this request? - if (!responseHeaders.isCacheable(requestHeaders)) { - return; - } - - // Offer this request to the cache. - cacheRequest = policy.responseCache.put(uri, policy.getHttpConnectionToCache()); - } - - /** - * Cause the socket connection to be released to the connection pool when - * it is no longer needed. If it is already unneeded, it will be pooled - * immediately. Otherwise the connection is held so that redirects can be - * handled by the same connection. - */ - public final void automaticallyReleaseConnectionToPool() { - automaticallyReleaseConnectionToPool = true; - if (connection != null && connectionReleased) { - policy.connectionPool.recycle(connection); - connection = null; - } - } - - /** - * Releases this engine so that its resources may be either reused or - * closed. Also call {@link #automaticallyReleaseConnectionToPool} unless - * the connection will be used to follow a redirect. - */ - public final void release(boolean streamCancelled) { - // If the response body comes from the cache, close it. - if (responseBodyIn == cachedResponseBody) { - Util.closeQuietly(responseBodyIn); - } - - if (!connectionReleased && connection != null) { - connectionReleased = true; - - if (transport == null || !transport.makeReusable(streamCancelled, requestBodyOut, - responseTransferIn)) { - Util.closeQuietly(connection); - connection = null; - } else if (automaticallyReleaseConnectionToPool) { - policy.connectionPool.recycle(connection); - connection = null; - } - } - } - - private void initContentStream(InputStream transferStream) throws IOException { - responseTransferIn = transferStream; - if (transparentGzip && responseHeaders.isContentEncodingGzip()) { - // If the response was transparently gzipped, remove the gzip header field - // so clients don't double decompress. http://b/3009828 - // - // Also remove the Content-Length in this case because it contains the - // length 528 of the gzipped response. This isn't terribly useful and is - // dangerous because 529 clients can query the content length, but not - // the content encoding. - responseHeaders.stripContentEncoding(); - responseHeaders.stripContentLength(); - responseBodyIn = new GZIPInputStream(transferStream); - } else { - responseBodyIn = transferStream; - } - } - - /** - * Returns true if the response must have a (possibly 0-length) body. - * See RFC 2616 section 4.3. - */ - public final boolean hasResponseBody() { - int responseCode = responseHeaders.getHeaders().getResponseCode(); - - // HEAD requests never yield a body regardless of the response headers. - if (method.equals("HEAD")) { - return false; - } - - if ((responseCode < HTTP_CONTINUE || responseCode >= 200) - && responseCode != HttpURLConnectionImpl.HTTP_NO_CONTENT - && responseCode != HttpURLConnectionImpl.HTTP_NOT_MODIFIED) { - return true; - } - - // If the Content-Length or Transfer-Encoding headers disagree with the - // response code, the response is malformed. For best compatibility, we - // honor the headers. - if (responseHeaders.getContentLength() != -1 || responseHeaders.isChunked()) { - return true; - } - - return false; - } - - /** - * Populates requestHeaders with defaults and cookies. - * - *

This client doesn't specify a default {@code Accept} header because it - * doesn't know what content types the application is interested in. - */ - private void prepareRawRequestHeaders() throws IOException { - requestHeaders.getHeaders().setRequestLine(getRequestLine()); - - if (requestHeaders.getUserAgent() == null) { - requestHeaders.setUserAgent(getDefaultUserAgent()); - } - - if (requestHeaders.getHost() == null) { - requestHeaders.setHost(getOriginAddress(policy.getURL())); - } - - if ((connection == null || connection.getHttpMinorVersion() != 0) - && requestHeaders.getConnection() == null) { - requestHeaders.setConnection("Keep-Alive"); - } - - if (requestHeaders.getAcceptEncoding() == null) { - transparentGzip = true; - requestHeaders.setAcceptEncoding("gzip"); - } - - if (hasRequestBody() && requestHeaders.getContentType() == null) { - requestHeaders.setContentType("application/x-www-form-urlencoded"); - } - - long ifModifiedSince = policy.getIfModifiedSince(); - if (ifModifiedSince != 0) { - requestHeaders.setIfModifiedSince(new Date(ifModifiedSince)); - } - - CookieHandler cookieHandler = policy.cookieHandler; - if (cookieHandler != null) { - requestHeaders.addCookies( - cookieHandler.get(uri, requestHeaders.getHeaders().toMultimap(false))); - } - } - - /** - * Returns the request status line, like "GET / HTTP/1.1". This is exposed - * to the application by {@link HttpURLConnectionImpl#getHeaderFields}, so - * it needs to be set even if the transport is SPDY. - */ - String getRequestLine() { - String protocol = - (connection == null || connection.getHttpMinorVersion() != 0) ? "HTTP/1.1" : "HTTP/1.0"; - return method + " " + requestString() + " " + protocol; - } - - private String requestString() { - URL url = policy.getURL(); - if (includeAuthorityInRequestLine()) { - return url.toString(); - } else { - return requestPath(url); - } - } - - /** - * Returns the path to request, like the '/' in 'GET / HTTP/1.1'. Never - * empty, even if the request URL is. Includes the query component if it - * exists. - */ - public static String requestPath(URL url) { - String fileOnly = url.getFile(); - if (fileOnly == null) { - return "/"; - } else if (!fileOnly.startsWith("/")) { - return "/" + fileOnly; - } else { - return fileOnly; - } - } - - /** - * Returns true if the request line should contain the full URL with host - * and port (like "GET http://android.com/foo HTTP/1.1") or only the path - * (like "GET /foo HTTP/1.1"). - * - *

This is non-final because for HTTPS it's never necessary to supply the - * full URL, even if a proxy is in use. - */ - protected boolean includeAuthorityInRequestLine() { - return connection == null - ? policy.usingProxy() // A proxy was requested. - : connection.getRoute().getProxy().type() == Proxy.Type.HTTP; // A proxy was selected. - } - - public static String getDefaultUserAgent() { - String agent = System.getProperty("http.agent"); - return agent != null ? agent : ("Java" + System.getProperty("java.version")); - } - - public static String getOriginAddress(URL url) { - int port = url.getPort(); - String result = url.getHost(); - if (port > 0 && port != getDefaultPort(url.getProtocol())) { - result = result + ":" + port; - } - return result; - } - - /** - * Flushes the remaining request header and body, parses the HTTP response - * headers and starts reading the HTTP response body if it exists. - */ - public final void readResponse() throws IOException { - if (hasResponse()) { - responseHeaders.setResponseSource(responseSource); - return; - } - - if (responseSource == null) { - throw new IllegalStateException("readResponse() without sendRequest()"); - } - - if (!responseSource.requiresConnection()) { - return; - } - - if (sentRequestMillis == -1) { - if (requestBodyOut instanceof RetryableOutputStream) { - int contentLength = ((RetryableOutputStream) requestBodyOut).contentLength(); - requestHeaders.setContentLength(contentLength); - } - transport.writeRequestHeaders(); - } - - if (requestBodyOut != null) { - requestBodyOut.close(); - if (requestBodyOut instanceof RetryableOutputStream) { - transport.writeRequestBody((RetryableOutputStream) requestBodyOut); - } - } - - transport.flushRequest(); - - responseHeaders = transport.readResponseHeaders(); - responseHeaders.setLocalTimestamps(sentRequestMillis, System.currentTimeMillis()); - responseHeaders.setResponseSource(responseSource); - - if (responseSource == ResponseSource.CONDITIONAL_CACHE) { - if (cachedResponseHeaders.validate(responseHeaders)) { - release(false); - ResponseHeaders combinedHeaders = cachedResponseHeaders.combine(responseHeaders); - setResponse(combinedHeaders, cachedResponseBody); - policy.responseCache.trackConditionalCacheHit(); - policy.responseCache.update(cacheResponse, policy.getHttpConnectionToCache()); - return; - } else { - Util.closeQuietly(cachedResponseBody); - } - } - - if (hasResponseBody()) { - maybeCache(); // reentrant. this calls into user code which may call back into this! - } - - initContentStream(transport.getTransferStream(cacheRequest)); - } - - protected TunnelRequest getTunnelConfig() { - return null; - } - - public void receiveHeaders(RawHeaders headers) throws IOException { - CookieHandler cookieHandler = policy.cookieHandler; - if (cookieHandler != null) { - cookieHandler.put(uri, headers.toMultimap(true)); - } - } -} diff --git a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/HttpResponseCache.java b/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/HttpResponseCache.java deleted file mode 100644 index 873516693..000000000 --- a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/HttpResponseCache.java +++ /dev/null @@ -1,608 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed 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. - */ - -package com.squareup.okhttp.internal.http; - -import com.squareup.okhttp.OkResponseCache; -import com.squareup.okhttp.ResponseSource; -import com.squareup.okhttp.internal.Base64; -import com.squareup.okhttp.internal.DiskLruCache; -import com.squareup.okhttp.internal.StrictLineReader; -import com.squareup.okhttp.internal.Util; -import java.io.BufferedWriter; -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.FilterInputStream; -import java.io.FilterOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.UnsupportedEncodingException; -import java.io.Writer; -import java.net.CacheRequest; -import java.net.CacheResponse; -import java.net.HttpURLConnection; -import java.net.ResponseCache; -import java.net.SecureCacheResponse; -import java.net.URI; -import java.net.URLConnection; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.Principal; -import java.security.cert.Certificate; -import java.security.cert.CertificateEncodingException; -import java.security.cert.CertificateException; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLPeerUnverifiedException; - -import static com.squareup.okhttp.internal.Util.US_ASCII; -import static com.squareup.okhttp.internal.Util.UTF_8; - -/** - * Cache responses in a directory on the file system. Most clients should use - * {@code android.net.HttpResponseCache}, the stable, documented front end for - * this. - */ -public final class HttpResponseCache extends ResponseCache implements OkResponseCache { - private static final char[] DIGITS = - { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; - - // TODO: add APIs to iterate the cache? - private static final int VERSION = 201105; - private static final int ENTRY_METADATA = 0; - private static final int ENTRY_BODY = 1; - private static final int ENTRY_COUNT = 2; - - private final DiskLruCache cache; - - /* read and write statistics, all guarded by 'this' */ - private int writeSuccessCount; - private int writeAbortCount; - private int networkCount; - private int hitCount; - private int requestCount; - - public HttpResponseCache(File directory, long maxSize) throws IOException { - cache = DiskLruCache.open(directory, VERSION, ENTRY_COUNT, maxSize); - } - - private String uriToKey(URI uri) { - try { - MessageDigest messageDigest = MessageDigest.getInstance("MD5"); - byte[] md5bytes = messageDigest.digest(uri.toString().getBytes("UTF-8")); - return bytesToHexString(md5bytes); - } catch (NoSuchAlgorithmException e) { - throw new AssertionError(e); - } catch (UnsupportedEncodingException e) { - throw new AssertionError(e); - } - } - - private static String bytesToHexString(byte[] bytes) { - char[] digits = DIGITS; - char[] buf = new char[bytes.length * 2]; - int c = 0; - for (byte b : bytes) { - buf[c++] = digits[(b >> 4) & 0xf]; - buf[c++] = digits[b & 0xf]; - } - return new String(buf); - } - - @Override public CacheResponse get(URI uri, String requestMethod, - Map> requestHeaders) { - String key = uriToKey(uri); - DiskLruCache.Snapshot snapshot; - Entry entry; - try { - snapshot = cache.get(key); - if (snapshot == null) { - return null; - } - entry = new Entry(snapshot.getInputStream(ENTRY_METADATA)); - } catch (IOException e) { - // Give up because the cache cannot be read. - return null; - } - - if (!entry.matches(uri, requestMethod, requestHeaders)) { - snapshot.close(); - return null; - } - - return entry.isHttps() ? new EntrySecureCacheResponse(entry, snapshot) - : new EntryCacheResponse(entry, snapshot); - } - - @Override public CacheRequest put(URI uri, URLConnection urlConnection) throws IOException { - if (!(urlConnection instanceof HttpURLConnection)) { - return null; - } - - HttpURLConnection httpConnection = (HttpURLConnection) urlConnection; - String requestMethod = httpConnection.getRequestMethod(); - String key = uriToKey(uri); - - if (requestMethod.equals("POST") || requestMethod.equals("PUT") || requestMethod.equals( - "DELETE")) { - try { - cache.remove(key); - } catch (IOException ignored) { - // The cache cannot be written. - } - return null; - } else if (!requestMethod.equals("GET")) { - // Don't cache non-GET responses. We're technically allowed to cache - // HEAD requests and some POST requests, but the complexity of doing - // so is high and the benefit is low. - return null; - } - - HttpEngine httpEngine = getHttpEngine(httpConnection); - if (httpEngine == null) { - // Don't cache unless the HTTP implementation is ours. - return null; - } - - ResponseHeaders response = httpEngine.getResponseHeaders(); - if (response.hasVaryAll()) { - return null; - } - - RawHeaders varyHeaders = - httpEngine.getRequestHeaders().getHeaders().getAll(response.getVaryFields()); - Entry entry = new Entry(uri, varyHeaders, httpConnection); - DiskLruCache.Editor editor = null; - try { - editor = cache.edit(key); - if (editor == null) { - return null; - } - entry.writeTo(editor); - return new CacheRequestImpl(editor); - } catch (IOException e) { - abortQuietly(editor); - return null; - } - } - - /** - * Handles a conditional request hit by updating the stored cache response - * with the headers from {@code httpConnection}. The cached response body is - * not updated. If the stored response has changed since {@code - * conditionalCacheHit} was returned, this does nothing. - */ - @Override public void update(CacheResponse conditionalCacheHit, HttpURLConnection httpConnection) - throws IOException { - HttpEngine httpEngine = getHttpEngine(httpConnection); - URI uri = httpEngine.getUri(); - ResponseHeaders response = httpEngine.getResponseHeaders(); - RawHeaders varyHeaders = - httpEngine.getRequestHeaders().getHeaders().getAll(response.getVaryFields()); - Entry entry = new Entry(uri, varyHeaders, httpConnection); - DiskLruCache.Snapshot snapshot = (conditionalCacheHit instanceof EntryCacheResponse) - ? ((EntryCacheResponse) conditionalCacheHit).snapshot - : ((EntrySecureCacheResponse) conditionalCacheHit).snapshot; - DiskLruCache.Editor editor = null; - try { - editor = snapshot.edit(); // returns null if snapshot is not current - if (editor != null) { - entry.writeTo(editor); - editor.commit(); - } - } catch (IOException e) { - abortQuietly(editor); - } - } - - private void abortQuietly(DiskLruCache.Editor editor) { - // Give up because the cache cannot be written. - try { - if (editor != null) { - editor.abort(); - } - } catch (IOException ignored) { - } - } - - private HttpEngine getHttpEngine(URLConnection httpConnection) { - if (httpConnection instanceof HttpURLConnectionImpl) { - return ((HttpURLConnectionImpl) httpConnection).getHttpEngine(); - } else if (httpConnection instanceof HttpsURLConnectionImpl) { - return ((HttpsURLConnectionImpl) httpConnection).getHttpEngine(); - } else { - return null; - } - } - - public DiskLruCache getCache() { - return cache; - } - - public synchronized int getWriteAbortCount() { - return writeAbortCount; - } - - public synchronized int getWriteSuccessCount() { - return writeSuccessCount; - } - - public synchronized void trackResponse(ResponseSource source) { - requestCount++; - - switch (source) { - case CACHE: - hitCount++; - break; - case CONDITIONAL_CACHE: - case NETWORK: - networkCount++; - break; - } - } - - public synchronized void trackConditionalCacheHit() { - hitCount++; - } - - public synchronized int getNetworkCount() { - return networkCount; - } - - public synchronized int getHitCount() { - return hitCount; - } - - public synchronized int getRequestCount() { - return requestCount; - } - - private final class CacheRequestImpl extends CacheRequest { - private final DiskLruCache.Editor editor; - private OutputStream cacheOut; - private boolean done; - private OutputStream body; - - public CacheRequestImpl(final DiskLruCache.Editor editor) throws IOException { - this.editor = editor; - this.cacheOut = editor.newOutputStream(ENTRY_BODY); - this.body = new FilterOutputStream(cacheOut) { - @Override public void close() throws IOException { - synchronized (HttpResponseCache.this) { - if (done) { - return; - } - done = true; - writeSuccessCount++; - } - super.close(); - editor.commit(); - } - - @Override - public void write(byte[] buffer, int offset, int length) throws IOException { - // Since we don't override "write(int oneByte)", we can write directly to "out" - // and avoid the inefficient implementation from the FilterOutputStream. - out.write(buffer, offset, length); - } - }; - } - - @Override public void abort() { - synchronized (HttpResponseCache.this) { - if (done) { - return; - } - done = true; - writeAbortCount++; - } - Util.closeQuietly(cacheOut); - try { - editor.abort(); - } catch (IOException ignored) { - } - } - - @Override public OutputStream getBody() throws IOException { - return body; - } - } - - private static final class Entry { - private final String uri; - private final RawHeaders varyHeaders; - private final String requestMethod; - private final RawHeaders responseHeaders; - private final String cipherSuite; - private final Certificate[] peerCertificates; - private final Certificate[] localCertificates; - - /** - * Reads an entry from an input stream. A typical entry looks like this: - *

{@code
-     *   http://google.com/foo
-     *   GET
-     *   2
-     *   Accept-Language: fr-CA
-     *   Accept-Charset: UTF-8
-     *   HTTP/1.1 200 OK
-     *   3
-     *   Content-Type: image/png
-     *   Content-Length: 100
-     *   Cache-Control: max-age=600
-     * }
- * - *

A typical HTTPS file looks like this: - *

{@code
-     *   https://google.com/foo
-     *   GET
-     *   2
-     *   Accept-Language: fr-CA
-     *   Accept-Charset: UTF-8
-     *   HTTP/1.1 200 OK
-     *   3
-     *   Content-Type: image/png
-     *   Content-Length: 100
-     *   Cache-Control: max-age=600
-     *
-     *   AES_256_WITH_MD5
-     *   2
-     *   base64-encoded peerCertificate[0]
-     *   base64-encoded peerCertificate[1]
-     *   -1
-     * }
- * The file is newline separated. The first two lines are the URL and - * the request method. Next is the number of HTTP Vary request header - * lines, followed by those lines. - * - *

Next is the response status line, followed by the number of HTTP - * response header lines, followed by those lines. - * - *

HTTPS responses also contain SSL session information. This begins - * with a blank line, and then a line containing the cipher suite. Next - * is the length of the peer certificate chain. These certificates are - * base64-encoded and appear each on their own line. The next line - * contains the length of the local certificate chain. These - * certificates are also base64-encoded and appear each on their own - * line. A length of -1 is used to encode a null array. - */ - public Entry(InputStream in) throws IOException { - try { - StrictLineReader reader = new StrictLineReader(in, US_ASCII); - uri = reader.readLine(); - requestMethod = reader.readLine(); - varyHeaders = new RawHeaders(); - int varyRequestHeaderLineCount = reader.readInt(); - for (int i = 0; i < varyRequestHeaderLineCount; i++) { - varyHeaders.addLine(reader.readLine()); - } - - responseHeaders = new RawHeaders(); - responseHeaders.setStatusLine(reader.readLine()); - int responseHeaderLineCount = reader.readInt(); - for (int i = 0; i < responseHeaderLineCount; i++) { - responseHeaders.addLine(reader.readLine()); - } - - if (isHttps()) { - String blank = reader.readLine(); - if (!blank.isEmpty()) { - throw new IOException("expected \"\" but was \"" + blank + "\""); - } - cipherSuite = reader.readLine(); - peerCertificates = readCertArray(reader); - localCertificates = readCertArray(reader); - } else { - cipherSuite = null; - peerCertificates = null; - localCertificates = null; - } - } finally { - in.close(); - } - } - - public Entry(URI uri, RawHeaders varyHeaders, HttpURLConnection httpConnection) - throws IOException { - this.uri = uri.toString(); - this.varyHeaders = varyHeaders; - this.requestMethod = httpConnection.getRequestMethod(); - this.responseHeaders = RawHeaders.fromMultimap(httpConnection.getHeaderFields(), true); - - if (isHttps()) { - HttpsURLConnection httpsConnection = (HttpsURLConnection) httpConnection; - cipherSuite = httpsConnection.getCipherSuite(); - Certificate[] peerCertificatesNonFinal = null; - try { - peerCertificatesNonFinal = httpsConnection.getServerCertificates(); - } catch (SSLPeerUnverifiedException ignored) { - } - peerCertificates = peerCertificatesNonFinal; - localCertificates = httpsConnection.getLocalCertificates(); - } else { - cipherSuite = null; - peerCertificates = null; - localCertificates = null; - } - } - - public void writeTo(DiskLruCache.Editor editor) throws IOException { - OutputStream out = editor.newOutputStream(ENTRY_METADATA); - Writer writer = new BufferedWriter(new OutputStreamWriter(out, UTF_8)); - - writer.write(uri + '\n'); - writer.write(requestMethod + '\n'); - writer.write(Integer.toString(varyHeaders.length()) + '\n'); - for (int i = 0; i < varyHeaders.length(); i++) { - writer.write(varyHeaders.getFieldName(i) + ": " + varyHeaders.getValue(i) + '\n'); - } - - writer.write(responseHeaders.getStatusLine() + '\n'); - writer.write(Integer.toString(responseHeaders.length()) + '\n'); - for (int i = 0; i < responseHeaders.length(); i++) { - writer.write(responseHeaders.getFieldName(i) + ": " + responseHeaders.getValue(i) + '\n'); - } - - if (isHttps()) { - writer.write('\n'); - writer.write(cipherSuite + '\n'); - writeCertArray(writer, peerCertificates); - writeCertArray(writer, localCertificates); - } - writer.close(); - } - - private boolean isHttps() { - return uri.startsWith("https://"); - } - - private Certificate[] readCertArray(StrictLineReader reader) throws IOException { - int length = reader.readInt(); - if (length == -1) { - return null; - } - try { - CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); - Certificate[] result = new Certificate[length]; - for (int i = 0; i < result.length; i++) { - String line = reader.readLine(); - byte[] bytes = Base64.decode(line.getBytes("US-ASCII")); - result[i] = certificateFactory.generateCertificate(new ByteArrayInputStream(bytes)); - } - return result; - } catch (CertificateException e) { - throw new IOException(e); - } - } - - private void writeCertArray(Writer writer, Certificate[] certificates) throws IOException { - if (certificates == null) { - writer.write("-1\n"); - return; - } - try { - writer.write(Integer.toString(certificates.length) + '\n'); - for (Certificate certificate : certificates) { - byte[] bytes = certificate.getEncoded(); - String line = Base64.encode(bytes); - writer.write(line + '\n'); - } - } catch (CertificateEncodingException e) { - throw new IOException(e); - } - } - - public boolean matches(URI uri, String requestMethod, - Map> requestHeaders) { - return this.uri.equals(uri.toString()) - && this.requestMethod.equals(requestMethod) - && new ResponseHeaders(uri, responseHeaders).varyMatches(varyHeaders.toMultimap(false), - requestHeaders); - } - } - - /** - * Returns an input stream that reads the body of a snapshot, closing the - * snapshot when the stream is closed. - */ - private static InputStream newBodyInputStream(final DiskLruCache.Snapshot snapshot) { - return new FilterInputStream(snapshot.getInputStream(ENTRY_BODY)) { - @Override public void close() throws IOException { - snapshot.close(); - super.close(); - } - }; - } - - static class EntryCacheResponse extends CacheResponse { - private final Entry entry; - private final DiskLruCache.Snapshot snapshot; - private final InputStream in; - - public EntryCacheResponse(Entry entry, DiskLruCache.Snapshot snapshot) { - this.entry = entry; - this.snapshot = snapshot; - this.in = newBodyInputStream(snapshot); - } - - @Override public Map> getHeaders() { - return entry.responseHeaders.toMultimap(true); - } - - @Override public InputStream getBody() { - return in; - } - } - - static class EntrySecureCacheResponse extends SecureCacheResponse { - private final Entry entry; - private final DiskLruCache.Snapshot snapshot; - private final InputStream in; - - public EntrySecureCacheResponse(Entry entry, DiskLruCache.Snapshot snapshot) { - this.entry = entry; - this.snapshot = snapshot; - this.in = newBodyInputStream(snapshot); - } - - @Override public Map> getHeaders() { - return entry.responseHeaders.toMultimap(true); - } - - @Override public InputStream getBody() { - return in; - } - - @Override public String getCipherSuite() { - return entry.cipherSuite; - } - - @Override public List getServerCertificateChain() - throws SSLPeerUnverifiedException { - if (entry.peerCertificates == null || entry.peerCertificates.length == 0) { - throw new SSLPeerUnverifiedException(null); - } - return Arrays.asList(entry.peerCertificates.clone()); - } - - @Override public Principal getPeerPrincipal() throws SSLPeerUnverifiedException { - if (entry.peerCertificates == null || entry.peerCertificates.length == 0) { - throw new SSLPeerUnverifiedException(null); - } - return ((X509Certificate) entry.peerCertificates[0]).getSubjectX500Principal(); - } - - @Override public List getLocalCertificateChain() { - if (entry.localCertificates == null || entry.localCertificates.length == 0) { - return null; - } - return Arrays.asList(entry.localCertificates.clone()); - } - - @Override public Principal getLocalPrincipal() { - if (entry.localCertificates == null || entry.localCertificates.length == 0) { - return null; - } - return ((X509Certificate) entry.localCertificates[0]).getSubjectX500Principal(); - } - } -} diff --git a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/HttpTransport.java b/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/HttpTransport.java deleted file mode 100644 index f6d77b250..000000000 --- a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/HttpTransport.java +++ /dev/null @@ -1,485 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed 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. - */ - -package com.squareup.okhttp.internal.http; - -import com.squareup.okhttp.Connection; -import com.squareup.okhttp.internal.AbstractOutputStream; -import com.squareup.okhttp.internal.Util; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.CacheRequest; -import java.net.ProtocolException; -import java.net.Socket; - -import static com.squareup.okhttp.internal.Util.checkOffsetAndCount; - -public final class HttpTransport implements Transport { - /** - * The timeout to use while discarding a stream of input data. Since this is - * used for connection reuse, this timeout should be significantly less than - * the time it takes to establish a new connection. - */ - private static final int DISCARD_STREAM_TIMEOUT_MILLIS = 100; - - public static final int DEFAULT_CHUNK_LENGTH = 1024; - - private final HttpEngine httpEngine; - private final InputStream socketIn; - private final OutputStream socketOut; - - /** - * This stream buffers the request headers and the request body when their - * combined size is less than MAX_REQUEST_BUFFER_LENGTH. By combining them - * we can save socket writes, which in turn saves a packet transmission. - * This is socketOut if the request size is large or unknown. - */ - private OutputStream requestOut; - - public HttpTransport(HttpEngine httpEngine, OutputStream outputStream, InputStream inputStream) { - this.httpEngine = httpEngine; - this.socketOut = outputStream; - this.requestOut = outputStream; - this.socketIn = inputStream; - } - - @Override public OutputStream createRequestBody() throws IOException { - boolean chunked = httpEngine.requestHeaders.isChunked(); - if (!chunked - && httpEngine.policy.getChunkLength() > 0 - && httpEngine.connection.getHttpMinorVersion() != 0) { - httpEngine.requestHeaders.setChunked(); - chunked = true; - } - - // Stream a request body of unknown length. - if (chunked) { - int chunkLength = httpEngine.policy.getChunkLength(); - if (chunkLength == -1) { - chunkLength = DEFAULT_CHUNK_LENGTH; - } - writeRequestHeaders(); - return new ChunkedOutputStream(requestOut, chunkLength); - } - - // Stream a request body of a known length. - int fixedContentLength = httpEngine.policy.getFixedContentLength(); - if (fixedContentLength != -1) { - httpEngine.requestHeaders.setContentLength(fixedContentLength); - writeRequestHeaders(); - return new FixedLengthOutputStream(requestOut, fixedContentLength); - } - - // Buffer a request body of a known length. - int contentLength = httpEngine.requestHeaders.getContentLength(); - if (contentLength != -1) { - writeRequestHeaders(); - return new RetryableOutputStream(contentLength); - } - - // Buffer a request body of an unknown length. Don't write request - // headers until the entire body is ready; otherwise we can't set the - // Content-Length header correctly. - return new RetryableOutputStream(); - } - - @Override public void flushRequest() throws IOException { - requestOut.flush(); - requestOut = socketOut; - } - - @Override public void writeRequestBody(RetryableOutputStream requestBody) throws IOException { - requestBody.writeToSocket(requestOut); - } - - /** - * Prepares the HTTP headers and sends them to the server. - * - *

For streaming requests with a body, headers must be prepared - * before the output stream has been written to. Otherwise - * the body would need to be buffered! - * - *

For non-streaming requests with a body, headers must be prepared - * after the output stream has been written to and closed. - * This ensures that the {@code Content-Length} header field receives the - * proper value. - */ - public void writeRequestHeaders() throws IOException { - httpEngine.writingRequestHeaders(); - RawHeaders headersToSend = httpEngine.requestHeaders.getHeaders(); - byte[] bytes = headersToSend.toBytes(); - requestOut.write(bytes); - } - - @Override public ResponseHeaders readResponseHeaders() throws IOException { - RawHeaders headers = RawHeaders.fromBytes(socketIn); - httpEngine.connection.setHttpMinorVersion(headers.getHttpMinorVersion()); - httpEngine.receiveHeaders(headers); - return new ResponseHeaders(httpEngine.uri, headers); - } - - public boolean makeReusable(boolean streamCancelled, OutputStream requestBodyOut, - InputStream responseBodyIn) { - if (streamCancelled) { - return false; - } - - // We cannot reuse sockets that have incomplete output. - if (requestBodyOut != null && !((AbstractOutputStream) requestBodyOut).isClosed()) { - return false; - } - - // If the request specified that the connection shouldn't be reused, don't reuse it. - if (httpEngine.requestHeaders.hasConnectionClose()) { - return false; - } - - // If the response specified that the connection shouldn't be reused, don't reuse it. - if (httpEngine.responseHeaders != null && httpEngine.responseHeaders.hasConnectionClose()) { - return false; - } - - if (responseBodyIn instanceof UnknownLengthHttpInputStream) { - return false; - } - - if (responseBodyIn != null) { - return discardStream(httpEngine, responseBodyIn); - } - - return true; - } - - /** - * Discards the response body so that the connection can be reused. This - * needs to be done judiciously, since it delays the current request in - * order to speed up a potential future request that may never occur. - */ - private static boolean discardStream(HttpEngine httpEngine, InputStream responseBodyIn) { - Connection connection = httpEngine.connection; - if (connection == null) return false; - Socket socket = connection.getSocket(); - if (socket == null) return false; - try { - int socketTimeout = socket.getSoTimeout(); - socket.setSoTimeout(DISCARD_STREAM_TIMEOUT_MILLIS); - try { - Util.skipAll(responseBodyIn); - return true; - } finally { - socket.setSoTimeout(socketTimeout); - } - } catch (IOException e) { - return false; - } - } - - @Override public InputStream getTransferStream(CacheRequest cacheRequest) throws IOException { - if (!httpEngine.hasResponseBody()) { - return new FixedLengthInputStream(socketIn, cacheRequest, httpEngine, 0); - } - - if (httpEngine.responseHeaders.isChunked()) { - return new ChunkedInputStream(socketIn, cacheRequest, this); - } - - if (httpEngine.responseHeaders.getContentLength() != -1) { - return new FixedLengthInputStream(socketIn, cacheRequest, httpEngine, - httpEngine.responseHeaders.getContentLength()); - } - - // Wrap the input stream from the connection (rather than just returning - // "socketIn" directly here), so that we can control its use after the - // reference escapes. - return new UnknownLengthHttpInputStream(socketIn, cacheRequest, httpEngine); - } - - /** An HTTP body with a fixed length known in advance. */ - private static final class FixedLengthOutputStream extends AbstractOutputStream { - private final OutputStream socketOut; - private int bytesRemaining; - - private FixedLengthOutputStream(OutputStream socketOut, int bytesRemaining) { - this.socketOut = socketOut; - this.bytesRemaining = bytesRemaining; - } - - @Override public void write(byte[] buffer, int offset, int count) throws IOException { - checkNotClosed(); - checkOffsetAndCount(buffer.length, offset, count); - if (count > bytesRemaining) { - throw new ProtocolException("expected " + bytesRemaining + " bytes but received " + count); - } - socketOut.write(buffer, offset, count); - bytesRemaining -= count; - } - - @Override public void flush() throws IOException { - if (closed) { - return; // don't throw; this stream might have been closed on the caller's behalf - } - socketOut.flush(); - } - - @Override public void close() throws IOException { - if (closed) { - return; - } - closed = true; - if (bytesRemaining > 0) { - throw new ProtocolException("unexpected end of stream"); - } - } - } - - /** - * An HTTP body with alternating chunk sizes and chunk bodies. Chunks are - * buffered until {@code maxChunkLength} bytes are ready, at which point the - * chunk is written and the buffer is cleared. - */ - private static final class ChunkedOutputStream extends AbstractOutputStream { - private static final byte[] CRLF = { '\r', '\n' }; - private static final byte[] HEX_DIGITS = { - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' - }; - private static final byte[] FINAL_CHUNK = new byte[] { '0', '\r', '\n', '\r', '\n' }; - - /** Scratch space for up to 8 hex digits, and then a constant CRLF. */ - private final byte[] hex = { 0, 0, 0, 0, 0, 0, 0, 0, '\r', '\n' }; - - private final OutputStream socketOut; - private final int maxChunkLength; - private final ByteArrayOutputStream bufferedChunk; - - private ChunkedOutputStream(OutputStream socketOut, int maxChunkLength) { - this.socketOut = socketOut; - this.maxChunkLength = Math.max(1, dataLength(maxChunkLength)); - this.bufferedChunk = new ByteArrayOutputStream(maxChunkLength); - } - - /** - * Returns the amount of data that can be transmitted in a chunk whose total - * length (data+headers) is {@code dataPlusHeaderLength}. This is presumably - * useful to match sizes with wire-protocol packets. - */ - private int dataLength(int dataPlusHeaderLength) { - int headerLength = 4; // "\r\n" after the size plus another "\r\n" after the data - for (int i = dataPlusHeaderLength - headerLength; i > 0; i >>= 4) { - headerLength++; - } - return dataPlusHeaderLength - headerLength; - } - - @Override public synchronized void write(byte[] buffer, int offset, int count) - throws IOException { - checkNotClosed(); - checkOffsetAndCount(buffer.length, offset, count); - - while (count > 0) { - int numBytesWritten; - - if (bufferedChunk.size() > 0 || count < maxChunkLength) { - // fill the buffered chunk and then maybe write that to the stream - numBytesWritten = Math.min(count, maxChunkLength - bufferedChunk.size()); - // TODO: skip unnecessary copies from buffer->bufferedChunk? - bufferedChunk.write(buffer, offset, numBytesWritten); - if (bufferedChunk.size() == maxChunkLength) { - writeBufferedChunkToSocket(); - } - } else { - // write a single chunk of size maxChunkLength to the stream - numBytesWritten = maxChunkLength; - writeHex(numBytesWritten); - socketOut.write(buffer, offset, numBytesWritten); - socketOut.write(CRLF); - } - - offset += numBytesWritten; - count -= numBytesWritten; - } - } - - /** - * Equivalent to, but cheaper than writing Integer.toHexString().getBytes() - * followed by CRLF. - */ - private void writeHex(int i) throws IOException { - int cursor = 8; - do { - hex[--cursor] = HEX_DIGITS[i & 0xf]; - } while ((i >>>= 4) != 0); - socketOut.write(hex, cursor, hex.length - cursor); - } - - @Override public synchronized void flush() throws IOException { - if (closed) { - return; // don't throw; this stream might have been closed on the caller's behalf - } - writeBufferedChunkToSocket(); - socketOut.flush(); - } - - @Override public synchronized void close() throws IOException { - if (closed) { - return; - } - closed = true; - writeBufferedChunkToSocket(); - socketOut.write(FINAL_CHUNK); - } - - private void writeBufferedChunkToSocket() throws IOException { - int size = bufferedChunk.size(); - if (size <= 0) { - return; - } - - writeHex(size); - bufferedChunk.writeTo(socketOut); - bufferedChunk.reset(); - socketOut.write(CRLF); - } - } - - /** An HTTP body with a fixed length specified in advance. */ - private static class FixedLengthInputStream extends AbstractHttpInputStream { - private int bytesRemaining; - - public FixedLengthInputStream(InputStream is, CacheRequest cacheRequest, HttpEngine httpEngine, - int length) throws IOException { - super(is, httpEngine, cacheRequest); - bytesRemaining = length; - if (bytesRemaining == 0) { - endOfInput(false); - } - } - - @Override public int read(byte[] buffer, int offset, int count) throws IOException { - checkOffsetAndCount(buffer.length, offset, count); - checkNotClosed(); - if (bytesRemaining == 0) { - return -1; - } - int read = in.read(buffer, offset, Math.min(count, bytesRemaining)); - if (read == -1) { - unexpectedEndOfInput(); // the server didn't supply the promised content length - throw new ProtocolException("unexpected end of stream"); - } - bytesRemaining -= read; - cacheWrite(buffer, offset, read); - if (bytesRemaining == 0) { - endOfInput(false); - } - return read; - } - - @Override public int available() throws IOException { - checkNotClosed(); - return bytesRemaining == 0 ? 0 : Math.min(in.available(), bytesRemaining); - } - - @Override public void close() throws IOException { - if (closed) { - return; - } - if (bytesRemaining != 0 && !discardStream(httpEngine, this)) { - unexpectedEndOfInput(); - } - closed = true; - } - } - - /** An HTTP body with alternating chunk sizes and chunk bodies. */ - private static class ChunkedInputStream extends AbstractHttpInputStream { - private static final int NO_CHUNK_YET = -1; - private final HttpTransport transport; - private int bytesRemainingInChunk = NO_CHUNK_YET; - private boolean hasMoreChunks = true; - - ChunkedInputStream(InputStream is, CacheRequest cacheRequest, HttpTransport transport) - throws IOException { - super(is, transport.httpEngine, cacheRequest); - this.transport = transport; - } - - @Override public int read(byte[] buffer, int offset, int count) throws IOException { - checkOffsetAndCount(buffer.length, offset, count); - checkNotClosed(); - - if (!hasMoreChunks) { - return -1; - } - if (bytesRemainingInChunk == 0 || bytesRemainingInChunk == NO_CHUNK_YET) { - readChunkSize(); - if (!hasMoreChunks) { - return -1; - } - } - int read = in.read(buffer, offset, Math.min(count, bytesRemainingInChunk)); - if (read == -1) { - unexpectedEndOfInput(); // the server didn't supply the promised chunk length - throw new IOException("unexpected end of stream"); - } - bytesRemainingInChunk -= read; - cacheWrite(buffer, offset, read); - return read; - } - - private void readChunkSize() throws IOException { - // read the suffix of the previous chunk - if (bytesRemainingInChunk != NO_CHUNK_YET) { - Util.readAsciiLine(in); - } - String chunkSizeString = Util.readAsciiLine(in); - int index = chunkSizeString.indexOf(";"); - if (index != -1) { - chunkSizeString = chunkSizeString.substring(0, index); - } - try { - bytesRemainingInChunk = Integer.parseInt(chunkSizeString.trim(), 16); - } catch (NumberFormatException e) { - throw new ProtocolException("Expected a hex chunk size but was " + chunkSizeString); - } - if (bytesRemainingInChunk == 0) { - hasMoreChunks = false; - RawHeaders rawResponseHeaders = httpEngine.responseHeaders.getHeaders(); - RawHeaders.readHeaders(transport.socketIn, rawResponseHeaders); - httpEngine.receiveHeaders(rawResponseHeaders); - endOfInput(false); - } - } - - @Override public int available() throws IOException { - checkNotClosed(); - if (!hasMoreChunks || bytesRemainingInChunk == NO_CHUNK_YET) { - return 0; - } - return Math.min(in.available(), bytesRemainingInChunk); - } - - @Override public void close() throws IOException { - if (closed) { - return; - } - if (hasMoreChunks && !discardStream(httpEngine, this)) { - unexpectedEndOfInput(); - } - closed = true; - } - } -} diff --git a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/HttpURLConnectionImpl.java b/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/HttpURLConnectionImpl.java deleted file mode 100644 index eabe649db..000000000 --- a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/HttpURLConnectionImpl.java +++ /dev/null @@ -1,556 +0,0 @@ -/* - * 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. - */ - -package com.squareup.okhttp.internal.http; - -import com.squareup.okhttp.Connection; -import com.squareup.okhttp.ConnectionPool; -import com.squareup.okhttp.OkHttpClient; -import com.squareup.okhttp.Route; -import com.squareup.okhttp.internal.AbstractOutputStream; -import com.squareup.okhttp.internal.FaultRecoveringOutputStream; -import com.squareup.okhttp.internal.Util; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.CookieHandler; -import java.net.HttpRetryException; -import java.net.HttpURLConnection; -import java.net.InetSocketAddress; -import java.net.ProtocolException; -import java.net.Proxy; -import java.net.ProxySelector; -import java.net.SocketPermission; -import java.net.URL; -import java.security.Permission; -import java.security.cert.CertificateException; -import java.util.List; -import java.util.Map; -import java.util.Set; -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.SSLHandshakeException; -import javax.net.ssl.SSLSocketFactory; - -import static com.squareup.okhttp.internal.Util.getEffectivePort; - -/** - * This implementation uses HttpEngine to send requests and receive responses. - * This class may use multiple HttpEngines to follow redirects, authentication - * retries, etc. to retrieve the final response body. - * - *

What does 'connected' mean?

- * This class inherits a {@code connected} field from the superclass. That field - * is not used to indicate not whether this URLConnection is - * currently connected. Instead, it indicates whether a connection has ever been - * attempted. Once a connection has been attempted, certain properties (request - * header fields, request method, etc.) are immutable. Test the {@code - * connection} field on this class for null/non-null to determine of an instance - * is currently connected to a server. - */ -public class HttpURLConnectionImpl extends HttpURLConnection { - - /** Numeric status code, 307: Temporary Redirect. */ - static final int HTTP_TEMP_REDIRECT = 307; - - /** - * How many redirects should we follow? Chrome follows 21; Firefox, curl, - * and wget follow 20; Safari follows 16; and HTTP/1.0 recommends 5. - */ - private static final int MAX_REDIRECTS = 20; - - /** - * The minimum number of request body bytes to transmit before we're willing - * to let a routine {@link IOException} bubble up to the user. This is used to - * size a buffer for data that will be replayed upon error. - */ - private static final int MAX_REPLAY_BUFFER_LENGTH = 8192; - - private final boolean followProtocolRedirects; - - /** The proxy requested by the client, or null for a proxy to be selected automatically. */ - final Proxy requestedProxy; - - final ProxySelector proxySelector; - final CookieHandler cookieHandler; - final OkResponseCache responseCache; - final ConnectionPool connectionPool; - /* SSL configuration; necessary for HTTP requests that get redirected to HTTPS. */ - SSLSocketFactory sslSocketFactory; - HostnameVerifier hostnameVerifier; - final Set failedRoutes; - - private final RawHeaders rawRequestHeaders = new RawHeaders(); - - private int redirectionCount; - private FaultRecoveringOutputStream faultRecoveringRequestBody; - - protected IOException httpEngineFailure; - protected HttpEngine httpEngine; - - public HttpURLConnectionImpl(URL url, OkHttpClient client, OkResponseCache responseCache, - Set failedRoutes) { - super(url); - this.followProtocolRedirects = client.getFollowProtocolRedirects(); - this.failedRoutes = failedRoutes; - this.requestedProxy = client.getProxy(); - this.proxySelector = client.getProxySelector(); - this.cookieHandler = client.getCookieHandler(); - this.connectionPool = client.getConnectionPool(); - this.sslSocketFactory = client.getSslSocketFactory(); - this.hostnameVerifier = client.getHostnameVerifier(); - this.responseCache = responseCache; - } - - Set getFailedRoutes() { - return failedRoutes; - } - - @Override public final void connect() throws IOException { - initHttpEngine(); - boolean success; - do { - success = execute(false); - } while (!success); - } - - @Override public final void disconnect() { - // Calling disconnect() before a connection exists should have no effect. - if (httpEngine != null) { - // We close the response body here instead of in - // HttpEngine.release because that is called when input - // has been completely read from the underlying socket. - // However the response body can be a GZIPInputStream that - // still has unread data. - if (httpEngine.hasResponse()) { - Util.closeQuietly(httpEngine.getResponseBody()); - } - httpEngine.release(true); - } - } - - /** - * Returns an input stream from the server in the case of error such as the - * requested file (txt, htm, html) is not found on the remote server. - */ - @Override public final InputStream getErrorStream() { - try { - HttpEngine response = getResponse(); - if (response.hasResponseBody() && response.getResponseCode() >= HTTP_BAD_REQUEST) { - return response.getResponseBody(); - } - return null; - } catch (IOException e) { - return null; - } - } - - /** - * Returns the value of the field at {@code position}. Returns null if there - * are fewer than {@code position} headers. - */ - @Override public final String getHeaderField(int position) { - try { - return getResponse().getResponseHeaders().getHeaders().getValue(position); - } catch (IOException e) { - return null; - } - } - - /** - * Returns the value of the field corresponding to the {@code fieldName}, or - * null if there is no such field. If the field has multiple values, the - * last value is returned. - */ - @Override public final String getHeaderField(String fieldName) { - try { - RawHeaders rawHeaders = getResponse().getResponseHeaders().getHeaders(); - return fieldName == null ? rawHeaders.getStatusLine() : rawHeaders.get(fieldName); - } catch (IOException e) { - return null; - } - } - - @Override public final String getHeaderFieldKey(int position) { - try { - return getResponse().getResponseHeaders().getHeaders().getFieldName(position); - } catch (IOException e) { - return null; - } - } - - @Override public final Map> getHeaderFields() { - try { - return getResponse().getResponseHeaders().getHeaders().toMultimap(true); - } catch (IOException e) { - return null; - } - } - - @Override public final Map> getRequestProperties() { - if (connected) { - throw new IllegalStateException( - "Cannot access request header fields after connection is set"); - } - return rawRequestHeaders.toMultimap(false); - } - - @Override public final InputStream getInputStream() throws IOException { - if (!doInput) { - throw new ProtocolException("This protocol does not support input"); - } - - HttpEngine response = getResponse(); - - // if the requested file does not exist, throw an exception formerly the - // Error page from the server was returned if the requested file was - // text/html this has changed to return FileNotFoundException for all - // file types - if (getResponseCode() >= HTTP_BAD_REQUEST) { - throw new FileNotFoundException(url.toString()); - } - - InputStream result = response.getResponseBody(); - if (result == null) { - throw new ProtocolException("No response body exists; responseCode=" + getResponseCode()); - } - return result; - } - - @Override public final OutputStream getOutputStream() throws IOException { - connect(); - - OutputStream out = httpEngine.getRequestBody(); - if (out == null) { - throw new ProtocolException("method does not support a request body: " + method); - } else if (httpEngine.hasResponse()) { - throw new ProtocolException("cannot write request body after response has been read"); - } - - if (faultRecoveringRequestBody == null) { - faultRecoveringRequestBody = new FaultRecoveringOutputStream(MAX_REPLAY_BUFFER_LENGTH, out) { - @Override protected OutputStream replacementStream(IOException e) throws IOException { - if (httpEngine.getRequestBody() instanceof AbstractOutputStream - && ((AbstractOutputStream) httpEngine.getRequestBody()).isClosed()) { - return null; // Don't recover once the underlying stream has been closed. - } - if (handleFailure(e)) { - return httpEngine.getRequestBody(); - } - return null; // This is a permanent failure. - } - }; - } - - return faultRecoveringRequestBody; - } - - @Override public final Permission getPermission() throws IOException { - String hostName = getURL().getHost(); - int hostPort = Util.getEffectivePort(getURL()); - if (usingProxy()) { - InetSocketAddress proxyAddress = (InetSocketAddress) requestedProxy.address(); - hostName = proxyAddress.getHostName(); - hostPort = proxyAddress.getPort(); - } - return new SocketPermission(hostName + ":" + hostPort, "connect, resolve"); - } - - @Override public final String getRequestProperty(String field) { - if (field == null) { - return null; - } - return rawRequestHeaders.get(field); - } - - private void initHttpEngine() throws IOException { - if (httpEngineFailure != null) { - throw httpEngineFailure; - } else if (httpEngine != null) { - return; - } - - connected = true; - try { - if (doOutput) { - if (method.equals("GET")) { - // they are requesting a stream to write to. This implies a POST method - method = "POST"; - } else if (!method.equals("POST") && !method.equals("PUT")) { - // If the request method is neither POST nor PUT, then you're not writing - throw new ProtocolException(method + " does not support writing"); - } - } - httpEngine = newHttpEngine(method, rawRequestHeaders, null, null); - } catch (IOException e) { - httpEngineFailure = e; - throw e; - } - } - - protected HttpURLConnection getHttpConnectionToCache() { - return this; - } - - private HttpEngine newHttpEngine(String method, RawHeaders requestHeaders, - Connection connection, RetryableOutputStream requestBody) throws IOException { - if (url.getProtocol().equals("http")) { - return new HttpEngine(this, method, requestHeaders, connection, requestBody); - } else if (url.getProtocol().equals("https")) { - return new HttpsURLConnectionImpl.HttpsEngine( - this, method, requestHeaders, connection, requestBody); - } else { - throw new AssertionError(); - } - } - - /** - * Aggressively tries to get the final HTTP response, potentially making - * many HTTP requests in the process in order to cope with redirects and - * authentication. - */ - private HttpEngine getResponse() throws IOException { - initHttpEngine(); - - if (httpEngine.hasResponse()) { - return httpEngine; - } - - while (true) { - if (!execute(true)) { - continue; - } - - Retry retry = processResponseHeaders(); - if (retry == Retry.NONE) { - httpEngine.automaticallyReleaseConnectionToPool(); - return httpEngine; - } - - // The first request was insufficient. Prepare for another... - String retryMethod = method; - OutputStream requestBody = httpEngine.getRequestBody(); - - // Although RFC 2616 10.3.2 specifies that a HTTP_MOVED_PERM - // redirect should keep the same method, Chrome, Firefox and the - // RI all issue GETs when following any redirect. - int responseCode = getResponseCode(); - if (responseCode == HTTP_MULT_CHOICE - || responseCode == HTTP_MOVED_PERM - || responseCode == HTTP_MOVED_TEMP - || responseCode == HTTP_SEE_OTHER) { - retryMethod = "GET"; - requestBody = null; - } - - if (requestBody != null && !(requestBody instanceof RetryableOutputStream)) { - throw new HttpRetryException("Cannot retry streamed HTTP body", - httpEngine.getResponseCode()); - } - - if (retry == Retry.DIFFERENT_CONNECTION) { - httpEngine.automaticallyReleaseConnectionToPool(); - } - - httpEngine.release(false); - - httpEngine = newHttpEngine(retryMethod, rawRequestHeaders, httpEngine.getConnection(), - (RetryableOutputStream) requestBody); - } - } - - /** - * Sends a request and optionally reads a response. Returns true if the - * request was successfully executed, and false if the request can be - * retried. Throws an exception if the request failed permanently. - */ - private boolean execute(boolean readResponse) throws IOException { - try { - httpEngine.sendRequest(); - if (readResponse) { - httpEngine.readResponse(); - } - return true; - } catch (IOException e) { - if (handleFailure(e)) { - return false; - } else { - throw e; - } - } - } - - /** - * Report and attempt to recover from {@code e}. Returns true if the HTTP - * engine was replaced and the request should be retried. Otherwise the - * failure is permanent. - */ - private boolean handleFailure(IOException e) throws IOException { - RouteSelector routeSelector = httpEngine.routeSelector; - if (routeSelector != null && httpEngine.connection != null) { - routeSelector.connectFailed(httpEngine.connection, e); - } - - OutputStream requestBody = httpEngine.getRequestBody(); - boolean canRetryRequestBody = requestBody == null - || requestBody instanceof RetryableOutputStream - || (faultRecoveringRequestBody != null && faultRecoveringRequestBody.isRecoverable()); - if (routeSelector == null && httpEngine.connection == null // No connection. - || routeSelector != null && !routeSelector.hasNext() // No more routes to attempt. - || !isRecoverable(e) - || !canRetryRequestBody) { - httpEngineFailure = e; - return false; - } - - httpEngine.release(true); - RetryableOutputStream retryableOutputStream = requestBody instanceof RetryableOutputStream - ? (RetryableOutputStream) requestBody - : null; - httpEngine = newHttpEngine(method, rawRequestHeaders, null, retryableOutputStream); - httpEngine.routeSelector = routeSelector; // Keep the same routeSelector. - if (faultRecoveringRequestBody != null && faultRecoveringRequestBody.isRecoverable()) { - httpEngine.sendRequest(); - faultRecoveringRequestBody.replaceStream(httpEngine.getRequestBody()); - } - return true; - } - - private boolean isRecoverable(IOException e) { - // If the problem was a CertificateException from the X509TrustManager, - // do not retry, we didn't have an abrupt server initiated exception. - boolean sslFailure = - e instanceof SSLHandshakeException && e.getCause() instanceof CertificateException; - boolean protocolFailure = e instanceof ProtocolException; - return !sslFailure && !protocolFailure; - } - - public HttpEngine getHttpEngine() { - return httpEngine; - } - - enum Retry { - NONE, - SAME_CONNECTION, - DIFFERENT_CONNECTION - } - - /** - * Returns the retry action to take for the current response headers. The - * headers, proxy and target URL or this connection may be adjusted to - * prepare for a follow up request. - */ - private Retry processResponseHeaders() throws IOException { - Proxy selectedProxy = httpEngine.connection != null - ? httpEngine.connection.getRoute().getProxy() - : requestedProxy; - final int responseCode = getResponseCode(); - switch (responseCode) { - case HTTP_PROXY_AUTH: - if (selectedProxy.type() != Proxy.Type.HTTP) { - throw new ProtocolException("Received HTTP_PROXY_AUTH (407) code while not using proxy"); - } - // fall-through - case HTTP_UNAUTHORIZED: - boolean credentialsFound = HttpAuthenticator.processAuthHeader(getResponseCode(), - httpEngine.getResponseHeaders().getHeaders(), rawRequestHeaders, selectedProxy, url); - return credentialsFound ? Retry.SAME_CONNECTION : Retry.NONE; - - case HTTP_MULT_CHOICE: - case HTTP_MOVED_PERM: - case HTTP_MOVED_TEMP: - case HTTP_SEE_OTHER: - case HTTP_TEMP_REDIRECT: - if (!getInstanceFollowRedirects()) { - return Retry.NONE; - } - if (++redirectionCount > MAX_REDIRECTS) { - throw new ProtocolException("Too many redirects: " + redirectionCount); - } - if (responseCode == HTTP_TEMP_REDIRECT && !method.equals("GET") && !method.equals("HEAD")) { - // "If the 307 status code is received in response to a request other than GET or HEAD, - // the user agent MUST NOT automatically redirect the request" - return Retry.NONE; - } - String location = getHeaderField("Location"); - if (location == null) { - return Retry.NONE; - } - URL previousUrl = url; - url = new URL(previousUrl, location); - if (!url.getProtocol().equals("https") && !url.getProtocol().equals("http")) { - return Retry.NONE; // Don't follow redirects to unsupported protocols. - } - boolean sameProtocol = previousUrl.getProtocol().equals(url.getProtocol()); - if (!sameProtocol && !followProtocolRedirects) { - return Retry.NONE; // This client doesn't follow redirects across protocols. - } - boolean sameHost = previousUrl.getHost().equals(url.getHost()); - boolean samePort = getEffectivePort(previousUrl) == getEffectivePort(url); - if (sameHost && samePort && sameProtocol) { - return Retry.SAME_CONNECTION; - } else { - return Retry.DIFFERENT_CONNECTION; - } - - default: - return Retry.NONE; - } - } - - /** @see java.net.HttpURLConnection#setFixedLengthStreamingMode(int) */ - final int getFixedContentLength() { - return fixedContentLength; - } - - /** @see java.net.HttpURLConnection#setChunkedStreamingMode(int) */ - final int getChunkLength() { - return chunkLength; - } - - @Override public final boolean usingProxy() { - return (requestedProxy != null && requestedProxy.type() != Proxy.Type.DIRECT); - } - - @Override public String getResponseMessage() throws IOException { - return getResponse().getResponseHeaders().getHeaders().getResponseMessage(); - } - - @Override public final int getResponseCode() throws IOException { - return getResponse().getResponseCode(); - } - - @Override public final void setRequestProperty(String field, String newValue) { - if (connected) { - throw new IllegalStateException("Cannot set request property after connection is made"); - } - if (field == null) { - throw new NullPointerException("field == null"); - } - rawRequestHeaders.set(field, newValue); - } - - @Override public final void addRequestProperty(String field, String value) { - if (connected) { - throw new IllegalStateException("Cannot add request property after connection is made"); - } - if (field == null) { - throw new NullPointerException("field == null"); - } - rawRequestHeaders.add(field, value); - } -} diff --git a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/HttpsURLConnectionImpl.java b/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/HttpsURLConnectionImpl.java deleted file mode 100644 index 235f86295..000000000 --- a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/HttpsURLConnectionImpl.java +++ /dev/null @@ -1,461 +0,0 @@ -/* - * 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. - */ -package com.squareup.okhttp.internal.http; - -import com.squareup.okhttp.Connection; -import com.squareup.okhttp.OkHttpClient; -import com.squareup.okhttp.Route; -import com.squareup.okhttp.TunnelRequest; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.CacheResponse; -import java.net.HttpURLConnection; -import java.net.ProtocolException; -import java.net.SecureCacheResponse; -import java.net.URL; -import java.security.Permission; -import java.security.Principal; -import java.security.cert.Certificate; -import java.util.List; -import java.util.Map; -import java.util.Set; -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLPeerUnverifiedException; -import javax.net.ssl.SSLSocket; -import javax.net.ssl.SSLSocketFactory; - -import static com.squareup.okhttp.internal.Util.getEffectivePort; - -public final class HttpsURLConnectionImpl extends HttpsURLConnection { - - /** HttpUrlConnectionDelegate allows reuse of HttpURLConnectionImpl. */ - private final HttpUrlConnectionDelegate delegate; - - public HttpsURLConnectionImpl(URL url, OkHttpClient client, OkResponseCache responseCache, - Set failedRoutes) { - super(url); - delegate = new HttpUrlConnectionDelegate(url, client, responseCache, failedRoutes); - } - - @Override public String getCipherSuite() { - SecureCacheResponse cacheResponse = delegate.getSecureCacheResponse(); - if (cacheResponse != null) { - return cacheResponse.getCipherSuite(); - } - SSLSocket sslSocket = getSslSocket(); - if (sslSocket != null) { - return sslSocket.getSession().getCipherSuite(); - } - return null; - } - - @Override public Certificate[] getLocalCertificates() { - SecureCacheResponse cacheResponse = delegate.getSecureCacheResponse(); - if (cacheResponse != null) { - List result = cacheResponse.getLocalCertificateChain(); - return result != null ? result.toArray(new Certificate[result.size()]) : null; - } - SSLSocket sslSocket = getSslSocket(); - if (sslSocket != null) { - return sslSocket.getSession().getLocalCertificates(); - } - return null; - } - - @Override public Certificate[] getServerCertificates() throws SSLPeerUnverifiedException { - SecureCacheResponse cacheResponse = delegate.getSecureCacheResponse(); - if (cacheResponse != null) { - List result = cacheResponse.getServerCertificateChain(); - return result != null ? result.toArray(new Certificate[result.size()]) : null; - } - SSLSocket sslSocket = getSslSocket(); - if (sslSocket != null) { - return sslSocket.getSession().getPeerCertificates(); - } - return null; - } - - @Override public Principal getPeerPrincipal() throws SSLPeerUnverifiedException { - SecureCacheResponse cacheResponse = delegate.getSecureCacheResponse(); - if (cacheResponse != null) { - return cacheResponse.getPeerPrincipal(); - } - SSLSocket sslSocket = getSslSocket(); - if (sslSocket != null) { - return sslSocket.getSession().getPeerPrincipal(); - } - return null; - } - - @Override public Principal getLocalPrincipal() { - SecureCacheResponse cacheResponse = delegate.getSecureCacheResponse(); - if (cacheResponse != null) { - return cacheResponse.getLocalPrincipal(); - } - SSLSocket sslSocket = getSslSocket(); - if (sslSocket != null) { - return sslSocket.getSession().getLocalPrincipal(); - } - return null; - } - - public HttpEngine getHttpEngine() { - return delegate.getHttpEngine(); - } - - private SSLSocket getSslSocket() { - if (delegate.httpEngine == null || delegate.httpEngine.sentRequestMillis == -1) { - throw new IllegalStateException("Connection has not yet been established"); - } - return delegate.httpEngine instanceof HttpsEngine - ? ((HttpsEngine) delegate.httpEngine).sslSocket - : null; // Not HTTPS! Probably an https:// to http:// redirect. - } - - @Override - public void disconnect() { - delegate.disconnect(); - } - - @Override - public InputStream getErrorStream() { - return delegate.getErrorStream(); - } - - @Override - public String getRequestMethod() { - return delegate.getRequestMethod(); - } - - @Override - public int getResponseCode() throws IOException { - return delegate.getResponseCode(); - } - - @Override - public String getResponseMessage() throws IOException { - return delegate.getResponseMessage(); - } - - @Override - public void setRequestMethod(String method) throws ProtocolException { - delegate.setRequestMethod(method); - } - - @Override - public boolean usingProxy() { - return delegate.usingProxy(); - } - - @Override - public boolean getInstanceFollowRedirects() { - return delegate.getInstanceFollowRedirects(); - } - - @Override - public void setInstanceFollowRedirects(boolean followRedirects) { - delegate.setInstanceFollowRedirects(followRedirects); - } - - @Override - public void connect() throws IOException { - connected = true; - delegate.connect(); - } - - @Override - public boolean getAllowUserInteraction() { - return delegate.getAllowUserInteraction(); - } - - @Override - public Object getContent() throws IOException { - return delegate.getContent(); - } - - @SuppressWarnings("unchecked") // Spec does not generify - @Override - public Object getContent(Class[] types) throws IOException { - return delegate.getContent(types); - } - - @Override - public String getContentEncoding() { - return delegate.getContentEncoding(); - } - - @Override - public int getContentLength() { - return delegate.getContentLength(); - } - - @Override - public String getContentType() { - return delegate.getContentType(); - } - - @Override - public long getDate() { - return delegate.getDate(); - } - - @Override - public boolean getDefaultUseCaches() { - return delegate.getDefaultUseCaches(); - } - - @Override - public boolean getDoInput() { - return delegate.getDoInput(); - } - - @Override - public boolean getDoOutput() { - return delegate.getDoOutput(); - } - - @Override - public long getExpiration() { - return delegate.getExpiration(); - } - - @Override - public String getHeaderField(int pos) { - return delegate.getHeaderField(pos); - } - - @Override - public Map> getHeaderFields() { - return delegate.getHeaderFields(); - } - - @Override - public Map> getRequestProperties() { - return delegate.getRequestProperties(); - } - - @Override - public void addRequestProperty(String field, String newValue) { - delegate.addRequestProperty(field, newValue); - } - - @Override - public String getHeaderField(String key) { - return delegate.getHeaderField(key); - } - - @Override - public long getHeaderFieldDate(String field, long defaultValue) { - return delegate.getHeaderFieldDate(field, defaultValue); - } - - @Override - public int getHeaderFieldInt(String field, int defaultValue) { - return delegate.getHeaderFieldInt(field, defaultValue); - } - - @Override - public String getHeaderFieldKey(int position) { - return delegate.getHeaderFieldKey(position); - } - - @Override - public long getIfModifiedSince() { - return delegate.getIfModifiedSince(); - } - - @Override - public InputStream getInputStream() throws IOException { - return delegate.getInputStream(); - } - - @Override - public long getLastModified() { - return delegate.getLastModified(); - } - - @Override - public OutputStream getOutputStream() throws IOException { - return delegate.getOutputStream(); - } - - @Override - public Permission getPermission() throws IOException { - return delegate.getPermission(); - } - - @Override - public String getRequestProperty(String field) { - return delegate.getRequestProperty(field); - } - - @Override - public URL getURL() { - return delegate.getURL(); - } - - @Override - public boolean getUseCaches() { - return delegate.getUseCaches(); - } - - @Override - public void setAllowUserInteraction(boolean newValue) { - delegate.setAllowUserInteraction(newValue); - } - - @Override - public void setDefaultUseCaches(boolean newValue) { - delegate.setDefaultUseCaches(newValue); - } - - @Override - public void setDoInput(boolean newValue) { - delegate.setDoInput(newValue); - } - - @Override - public void setDoOutput(boolean newValue) { - delegate.setDoOutput(newValue); - } - - @Override - public void setIfModifiedSince(long newValue) { - delegate.setIfModifiedSince(newValue); - } - - @Override - public void setRequestProperty(String field, String newValue) { - delegate.setRequestProperty(field, newValue); - } - - @Override - public void setUseCaches(boolean newValue) { - delegate.setUseCaches(newValue); - } - - @Override - public void setConnectTimeout(int timeoutMillis) { - delegate.setConnectTimeout(timeoutMillis); - } - - @Override - public int getConnectTimeout() { - return delegate.getConnectTimeout(); - } - - @Override - public void setReadTimeout(int timeoutMillis) { - delegate.setReadTimeout(timeoutMillis); - } - - @Override - public int getReadTimeout() { - return delegate.getReadTimeout(); - } - - @Override - public String toString() { - return delegate.toString(); - } - - @Override - public void setFixedLengthStreamingMode(int contentLength) { - delegate.setFixedLengthStreamingMode(contentLength); - } - - @Override - public void setChunkedStreamingMode(int chunkLength) { - delegate.setChunkedStreamingMode(chunkLength); - } - - @Override public void setHostnameVerifier(HostnameVerifier hostnameVerifier) { - delegate.hostnameVerifier = hostnameVerifier; - } - - @Override public HostnameVerifier getHostnameVerifier() { - return delegate.hostnameVerifier; - } - - @Override public void setSSLSocketFactory(SSLSocketFactory sslSocketFactory) { - delegate.sslSocketFactory = sslSocketFactory; - } - - @Override public SSLSocketFactory getSSLSocketFactory() { - return delegate.sslSocketFactory; - } - - private final class HttpUrlConnectionDelegate extends HttpURLConnectionImpl { - private HttpUrlConnectionDelegate(URL url, OkHttpClient client, OkResponseCache responseCache, - Set failedRoutes) { - super(url, client, responseCache, failedRoutes); - } - - @Override protected HttpURLConnection getHttpConnectionToCache() { - return HttpsURLConnectionImpl.this; - } - - public SecureCacheResponse getSecureCacheResponse() { - return httpEngine instanceof HttpsEngine - ? (SecureCacheResponse) httpEngine.getCacheResponse() - : null; - } - } - - public static final class HttpsEngine extends HttpEngine { - /** - * Stash of HttpsEngine.connection.socket to implement requests like - * {@link #getCipherSuite} even after the connection has been recycled. - */ - private SSLSocket sslSocket; - - /** - * @param policy the HttpURLConnectionImpl with connection configuration - */ - public HttpsEngine(HttpURLConnectionImpl policy, String method, RawHeaders requestHeaders, - Connection connection, RetryableOutputStream requestBody) throws IOException { - super(policy, method, requestHeaders, connection, requestBody); - this.sslSocket = connection != null ? (SSLSocket) connection.getSocket() : null; - } - - @Override protected void connected(Connection connection) { - this.sslSocket = (SSLSocket) connection.getSocket(); - } - - @Override protected boolean acceptCacheResponseType(CacheResponse cacheResponse) { - return cacheResponse instanceof SecureCacheResponse; - } - - @Override protected boolean includeAuthorityInRequestLine() { - // Even if there is a proxy, it isn't involved. Always request just the file. - return false; - } - - @Override protected TunnelRequest getTunnelConfig() { - String userAgent = requestHeaders.getUserAgent(); - if (userAgent == null) { - userAgent = getDefaultUserAgent(); - } - - URL url = policy.getURL(); - return new TunnelRequest(url.getHost(), getEffectivePort(url), userAgent, - requestHeaders.getProxyAuthorization()); - } - } -} diff --git a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/OkResponseCache.java b/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/OkResponseCache.java deleted file mode 100644 index 5829f0244..000000000 --- a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/OkResponseCache.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2013 Square, Inc. - * - * Licensed 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. - */ -package com.squareup.okhttp.internal.http; - -import com.squareup.okhttp.ResponseSource; -import java.io.IOException; -import java.net.CacheRequest; -import java.net.CacheResponse; -import java.net.HttpURLConnection; -import java.net.URI; -import java.net.URLConnection; -import java.util.List; -import java.util.Map; - -/** - * An extended response cache API. Unlike {@link java.net.ResponseCache}, this - * interface supports conditional caching and statistics. - * - *

Along with the rest of the {@code internal} package, this is not a public - * API. Applications wishing to supply their own caches must use the more - * limited {@link java.net.ResponseCache} interface. - */ -public interface OkResponseCache { - CacheResponse get(URI uri, String requestMethod, Map> requestHeaders) - throws IOException; - - CacheRequest put(URI uri, URLConnection urlConnection) throws IOException; - - /** - * Handles a conditional request hit by updating the stored cache response - * with the headers from {@code httpConnection}. The cached response body is - * not updated. If the stored response has changed since {@code - * conditionalCacheHit} was returned, this does nothing. - */ - void update(CacheResponse conditionalCacheHit, HttpURLConnection connection) throws IOException; - - /** Track an conditional GET that was satisfied by this cache. */ - void trackConditionalCacheHit(); - - /** Track an HTTP response being satisfied by {@code source}. */ - void trackResponse(ResponseSource source); -} diff --git a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/OkResponseCacheAdapter.java b/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/OkResponseCacheAdapter.java deleted file mode 100644 index 2ac915a8d..000000000 --- a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/OkResponseCacheAdapter.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2013 Square, Inc. - * - * Licensed 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. - */ -package com.squareup.okhttp.internal.http; - -import com.squareup.okhttp.ResponseSource; -import java.io.IOException; -import java.net.CacheRequest; -import java.net.CacheResponse; -import java.net.HttpURLConnection; -import java.net.ResponseCache; -import java.net.URI; -import java.net.URLConnection; -import java.util.List; -import java.util.Map; - -public final class OkResponseCacheAdapter implements OkResponseCache { - private final ResponseCache responseCache; - public OkResponseCacheAdapter(ResponseCache responseCache) { - this.responseCache = responseCache; - } - - @Override public CacheResponse get(URI uri, String requestMethod, - Map> requestHeaders) throws IOException { - return responseCache.get(uri, requestMethod, requestHeaders); - } - - @Override public CacheRequest put(URI uri, URLConnection urlConnection) throws IOException { - return responseCache.put(uri, urlConnection); - } - - @Override public void update(CacheResponse conditionalCacheHit, HttpURLConnection connection) - throws IOException { - } - - @Override public void trackConditionalCacheHit() { - } - - @Override public void trackResponse(ResponseSource source) { - } -} diff --git a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/RawHeaders.java b/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/RawHeaders.java deleted file mode 100644 index eba887ec0..000000000 --- a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/RawHeaders.java +++ /dev/null @@ -1,424 +0,0 @@ -/* - * 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. - */ - -package com.squareup.okhttp.internal.http; - -import com.squareup.okhttp.internal.Util; -import java.io.IOException; -import java.io.InputStream; -import java.io.UnsupportedEncodingException; -import java.net.ProtocolException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.TreeMap; - -/** - * The HTTP status and unparsed header fields of a single HTTP message. Values - * are represented as uninterpreted strings; use {@link RequestHeaders} and - * {@link ResponseHeaders} for interpreted headers. This class maintains the - * order of the header fields within the HTTP message. - * - *

This class tracks fields line-by-line. A field with multiple comma- - * separated values on the same line will be treated as a field with a single - * value by this class. It is the caller's responsibility to detect and split - * on commas if their field permits multiple values. This simplifies use of - * single-valued fields whose values routinely contain commas, such as cookies - * or dates. - * - *

This class trims whitespace from values. It never returns values with - * leading or trailing whitespace. - */ -public final class RawHeaders { - private static final Comparator FIELD_NAME_COMPARATOR = new Comparator() { - // @FindBugsSuppressWarnings("ES_COMPARING_PARAMETER_STRING_WITH_EQ") - @Override public int compare(String a, String b) { - if (a == b) { - return 0; - } else if (a == null) { - return -1; - } else if (b == null) { - return 1; - } else { - return String.CASE_INSENSITIVE_ORDER.compare(a, b); - } - } - }; - - private final List namesAndValues = new ArrayList(20); - private String requestLine; - private String statusLine; - private int httpMinorVersion = 1; - private int responseCode = -1; - private String responseMessage; - - public RawHeaders() { - } - - public RawHeaders(RawHeaders copyFrom) { - namesAndValues.addAll(copyFrom.namesAndValues); - requestLine = copyFrom.requestLine; - statusLine = copyFrom.statusLine; - httpMinorVersion = copyFrom.httpMinorVersion; - responseCode = copyFrom.responseCode; - responseMessage = copyFrom.responseMessage; - } - - /** Sets the request line (like "GET / HTTP/1.1"). */ - public void setRequestLine(String requestLine) { - requestLine = requestLine.trim(); - this.requestLine = requestLine; - } - - /** Sets the response status line (like "HTTP/1.0 200 OK"). */ - public void setStatusLine(String statusLine) throws IOException { - // H T T P / 1 . 1 2 0 0 T e m p o r a r y R e d i r e c t - // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 - if (this.responseMessage != null) { - throw new IllegalStateException("statusLine is already set"); - } - // We allow empty message without leading white space since some servers - // do not send the white space when the message is empty. - boolean hasMessage = statusLine.length() > 13; - if (!statusLine.startsWith("HTTP/1.") - || statusLine.length() < 12 - || statusLine.charAt(8) != ' ' - || (hasMessage && statusLine.charAt(12) != ' ')) { - throw new ProtocolException("Unexpected status line: " + statusLine); - } - int httpMinorVersion = statusLine.charAt(7) - '0'; - if (httpMinorVersion < 0 || httpMinorVersion > 9) { - throw new ProtocolException("Unexpected status line: " + statusLine); - } - int responseCode; - try { - responseCode = Integer.parseInt(statusLine.substring(9, 12)); - } catch (NumberFormatException e) { - throw new ProtocolException("Unexpected status line: " + statusLine); - } - this.responseMessage = hasMessage ? statusLine.substring(13) : ""; - this.responseCode = responseCode; - this.statusLine = statusLine; - this.httpMinorVersion = httpMinorVersion; - } - - public void computeResponseStatusLineFromSpdyHeaders() throws IOException { - String status = null; - String version = null; - for (int i = 0; i < namesAndValues.size(); i += 2) { - String name = namesAndValues.get(i); - if (":status".equals(name)) { - status = namesAndValues.get(i + 1); - } else if (":version".equals(name)) { - version = namesAndValues.get(i + 1); - } - } - if (status == null || version == null) { - throw new ProtocolException("Expected ':status' and ':version' headers not present"); - } - setStatusLine(version + " " + status); - } - - /** - * @param method like "GET", "POST", "HEAD", etc. - * @param path like "/foo/bar.html" - * @param version like "HTTP/1.1" - * @param host like "www.android.com:1234" - * @param scheme like "https" - */ - public void addSpdyRequestHeaders(String method, String path, String version, String host, - String scheme) { - // TODO: populate the statusLine for the client's benefit? - add(":method", method); - add(":scheme", scheme); - add(":path", path); - add(":version", version); - add(":host", host); - } - - public String getStatusLine() { - return statusLine; - } - - /** - * Returns the status line's HTTP minor version. This returns 0 for HTTP/1.0 - * and 1 for HTTP/1.1. This returns 1 if the HTTP version is unknown. - */ - public int getHttpMinorVersion() { - return httpMinorVersion != -1 ? httpMinorVersion : 1; - } - - /** Returns the HTTP status code or -1 if it is unknown. */ - public int getResponseCode() { - return responseCode; - } - - /** Returns the HTTP status message or null if it is unknown. */ - public String getResponseMessage() { - return responseMessage; - } - - /** - * Add an HTTP header line containing a field name, a literal colon, and a - * value. - */ - public void addLine(String line) { - int index = line.indexOf(":"); - if (index == -1) { - addLenient("", line); - } else { - addLenient(line.substring(0, index), line.substring(index + 1)); - } - } - - /** Add a field with the specified value. */ - public void add(String fieldName, String value) { - if (fieldName == null) throw new IllegalArgumentException("fieldname == null"); - if (value == null) throw new IllegalArgumentException("value == null"); - if (fieldName.length() == 0 || fieldName.indexOf('\0') != -1 || value.indexOf('\0') != -1) { - throw new IllegalArgumentException("Unexpected header: " + fieldName + ": " + value); - } - addLenient(fieldName, value); - } - - /** - * Add a field with the specified value without any validation. Only - * appropriate for headers from the remote peer. - */ - private void addLenient(String fieldName, String value) { - namesAndValues.add(fieldName); - namesAndValues.add(value.trim()); - } - - public void removeAll(String fieldName) { - for (int i = 0; i < namesAndValues.size(); i += 2) { - if (fieldName.equalsIgnoreCase(namesAndValues.get(i))) { - namesAndValues.remove(i); // field name - namesAndValues.remove(i); // value - } - } - } - - public void addAll(String fieldName, List headerFields) { - for (String value : headerFields) { - add(fieldName, value); - } - } - - /** - * Set a field with the specified value. If the field is not found, it is - * added. If the field is found, the existing values are replaced. - */ - public void set(String fieldName, String value) { - removeAll(fieldName); - add(fieldName, value); - } - - /** Returns the number of field values. */ - public int length() { - return namesAndValues.size() / 2; - } - - /** Returns the field at {@code position} or null if that is out of range. */ - public String getFieldName(int index) { - int fieldNameIndex = index * 2; - if (fieldNameIndex < 0 || fieldNameIndex >= namesAndValues.size()) { - return null; - } - return namesAndValues.get(fieldNameIndex); - } - - /** Returns the value at {@code index} or null if that is out of range. */ - public String getValue(int index) { - int valueIndex = index * 2 + 1; - if (valueIndex < 0 || valueIndex >= namesAndValues.size()) { - return null; - } - return namesAndValues.get(valueIndex); - } - - /** Returns the last value corresponding to the specified field, or null. */ - public String get(String fieldName) { - for (int i = namesAndValues.size() - 2; i >= 0; i -= 2) { - if (fieldName.equalsIgnoreCase(namesAndValues.get(i))) { - return namesAndValues.get(i + 1); - } - } - return null; - } - - /** @param fieldNames a case-insensitive set of HTTP header field names. */ - public RawHeaders getAll(Set fieldNames) { - RawHeaders result = new RawHeaders(); - for (int i = 0; i < namesAndValues.size(); i += 2) { - String fieldName = namesAndValues.get(i); - if (fieldNames.contains(fieldName)) { - result.add(fieldName, namesAndValues.get(i + 1)); - } - } - return result; - } - - /** Returns bytes of a request header for sending on an HTTP transport. */ - public byte[] toBytes() throws UnsupportedEncodingException { - StringBuilder result = new StringBuilder(256); - result.append(requestLine).append("\r\n"); - for (int i = 0; i < namesAndValues.size(); i += 2) { - result.append(namesAndValues.get(i)) - .append(": ") - .append(namesAndValues.get(i + 1)) - .append("\r\n"); - } - result.append("\r\n"); - return result.toString().getBytes("ISO-8859-1"); - } - - /** Parses bytes of a response header from an HTTP transport. */ - public static RawHeaders fromBytes(InputStream in) throws IOException { - RawHeaders headers; - do { - headers = new RawHeaders(); - headers.setStatusLine(Util.readAsciiLine(in)); - readHeaders(in, headers); - } while (headers.getResponseCode() == HttpEngine.HTTP_CONTINUE); - return headers; - } - - /** Reads headers or trailers into {@code out}. */ - public static void readHeaders(InputStream in, RawHeaders out) throws IOException { - // parse the result headers until the first blank line - String line; - while ((line = Util.readAsciiLine(in)).length() != 0) { - out.addLine(line); - } - } - - /** - * Returns an immutable map containing each field to its list of values. The - * status line is mapped to null. - */ - public Map> toMultimap(boolean response) { - Map> result = new TreeMap>(FIELD_NAME_COMPARATOR); - for (int i = 0; i < namesAndValues.size(); i += 2) { - String fieldName = namesAndValues.get(i); - String value = namesAndValues.get(i + 1); - - List allValues = new ArrayList(); - List otherValues = result.get(fieldName); - if (otherValues != null) { - allValues.addAll(otherValues); - } - allValues.add(value); - result.put(fieldName, Collections.unmodifiableList(allValues)); - } - if (response && statusLine != null) { - result.put(null, Collections.unmodifiableList(Collections.singletonList(statusLine))); - } else if (requestLine != null) { - result.put(null, Collections.unmodifiableList(Collections.singletonList(requestLine))); - } - return Collections.unmodifiableMap(result); - } - - /** - * Creates a new instance from the given map of fields to values. If - * present, the null field's last element will be used to set the status - * line. - */ - public static RawHeaders fromMultimap(Map> map, boolean response) - throws IOException { - if (!response) throw new UnsupportedOperationException(); - RawHeaders result = new RawHeaders(); - for (Entry> entry : map.entrySet()) { - String fieldName = entry.getKey(); - List values = entry.getValue(); - if (fieldName != null) { - for (String value : values) { - result.addLenient(fieldName, value); - } - } else if (!values.isEmpty()) { - result.setStatusLine(values.get(values.size() - 1)); - } - } - return result; - } - - /** - * Returns a list of alternating names and values. Names are all lower case. - * No names are repeated. If any name has multiple values, they are - * concatenated using "\0" as a delimiter. - */ - public List toNameValueBlock() { - Set names = new HashSet(); - List result = new ArrayList(); - for (int i = 0; i < namesAndValues.size(); i += 2) { - String name = namesAndValues.get(i).toLowerCase(Locale.US); - String value = namesAndValues.get(i + 1); - - // Drop headers that are forbidden when layering HTTP over SPDY. - if (name.equals("connection") - || name.equals("host") - || name.equals("keep-alive") - || name.equals("proxy-connection") - || name.equals("transfer-encoding")) { - continue; - } - - // If we haven't seen this name before, add the pair to the end of the list... - if (names.add(name)) { - result.add(name); - result.add(value); - continue; - } - - // ...otherwise concatenate the existing values and this value. - for (int j = 0; j < result.size(); j += 2) { - if (name.equals(result.get(j))) { - result.set(j + 1, result.get(j + 1) + "\0" + value); - break; - } - } - } - return result; - } - - public static RawHeaders fromNameValueBlock(List nameValueBlock) { - if (nameValueBlock.size() % 2 != 0) { - throw new IllegalArgumentException("Unexpected name value block: " + nameValueBlock); - } - RawHeaders result = new RawHeaders(); - for (int i = 0; i < nameValueBlock.size(); i += 2) { - String name = nameValueBlock.get(i); - String values = nameValueBlock.get(i + 1); - for (int start = 0; start < values.length(); ) { - int end = values.indexOf('\0', start); - if (end == -1) { - end = values.length(); - } - result.namesAndValues.add(name); - result.namesAndValues.add(values.substring(start, end)); - start = end + 1; - } - } - return result; - } -} diff --git a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/RequestHeaders.java b/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/RequestHeaders.java deleted file mode 100644 index 5ec4fcca3..000000000 --- a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/RequestHeaders.java +++ /dev/null @@ -1,290 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed 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. - */ - -package com.squareup.okhttp.internal.http; - -import java.net.URI; -import java.util.Date; -import java.util.List; -import java.util.Map; - -/** Parsed HTTP request headers. */ -public final class RequestHeaders { - private final URI uri; - private final RawHeaders headers; - - /** Don't use a cache to satisfy this request. */ - private boolean noCache; - private int maxAgeSeconds = -1; - private int maxStaleSeconds = -1; - private int minFreshSeconds = -1; - - /** - * This field's name "only-if-cached" is misleading. It actually means "do - * not use the network". It is set by a client who only wants to make a - * request if it can be fully satisfied by the cache. Cached responses that - * would require validation (ie. conditional gets) are not permitted if this - * header is set. - */ - private boolean onlyIfCached; - - /** - * True if the request contains an authorization field. Although this isn't - * necessarily a shared cache, it follows the spec's strict requirements for - * shared caches. - */ - private boolean hasAuthorization; - - private int contentLength = -1; - private String transferEncoding; - private String userAgent; - private String host; - private String connection; - private String acceptEncoding; - private String contentType; - private String ifModifiedSince; - private String ifNoneMatch; - private String proxyAuthorization; - - public RequestHeaders(URI uri, RawHeaders headers) { - this.uri = uri; - this.headers = headers; - - HeaderParser.CacheControlHandler handler = new HeaderParser.CacheControlHandler() { - @Override public void handle(String directive, String parameter) { - if ("no-cache".equalsIgnoreCase(directive)) { - noCache = true; - } else if ("max-age".equalsIgnoreCase(directive)) { - maxAgeSeconds = HeaderParser.parseSeconds(parameter); - } else if ("max-stale".equalsIgnoreCase(directive)) { - maxStaleSeconds = HeaderParser.parseSeconds(parameter); - } else if ("min-fresh".equalsIgnoreCase(directive)) { - minFreshSeconds = HeaderParser.parseSeconds(parameter); - } else if ("only-if-cached".equalsIgnoreCase(directive)) { - onlyIfCached = true; - } - } - }; - - for (int i = 0; i < headers.length(); i++) { - String fieldName = headers.getFieldName(i); - String value = headers.getValue(i); - if ("Cache-Control".equalsIgnoreCase(fieldName)) { - HeaderParser.parseCacheControl(value, handler); - } else if ("Pragma".equalsIgnoreCase(fieldName)) { - if ("no-cache".equalsIgnoreCase(value)) { - noCache = true; - } - } else if ("If-None-Match".equalsIgnoreCase(fieldName)) { - ifNoneMatch = value; - } else if ("If-Modified-Since".equalsIgnoreCase(fieldName)) { - ifModifiedSince = value; - } else if ("Authorization".equalsIgnoreCase(fieldName)) { - hasAuthorization = true; - } else if ("Content-Length".equalsIgnoreCase(fieldName)) { - try { - contentLength = Integer.parseInt(value); - } catch (NumberFormatException ignored) { - } - } else if ("Transfer-Encoding".equalsIgnoreCase(fieldName)) { - transferEncoding = value; - } else if ("User-Agent".equalsIgnoreCase(fieldName)) { - userAgent = value; - } else if ("Host".equalsIgnoreCase(fieldName)) { - host = value; - } else if ("Connection".equalsIgnoreCase(fieldName)) { - connection = value; - } else if ("Accept-Encoding".equalsIgnoreCase(fieldName)) { - acceptEncoding = value; - } else if ("Content-Type".equalsIgnoreCase(fieldName)) { - contentType = value; - } else if ("Proxy-Authorization".equalsIgnoreCase(fieldName)) { - proxyAuthorization = value; - } - } - } - - public boolean isChunked() { - return "chunked".equalsIgnoreCase(transferEncoding); - } - - public boolean hasConnectionClose() { - return "close".equalsIgnoreCase(connection); - } - - public URI getUri() { - return uri; - } - - public RawHeaders getHeaders() { - return headers; - } - - public boolean isNoCache() { - return noCache; - } - - public int getMaxAgeSeconds() { - return maxAgeSeconds; - } - - public int getMaxStaleSeconds() { - return maxStaleSeconds; - } - - public int getMinFreshSeconds() { - return minFreshSeconds; - } - - public boolean isOnlyIfCached() { - return onlyIfCached; - } - - public boolean hasAuthorization() { - return hasAuthorization; - } - - public int getContentLength() { - return contentLength; - } - - public String getTransferEncoding() { - return transferEncoding; - } - - public String getUserAgent() { - return userAgent; - } - - public String getHost() { - return host; - } - - public String getConnection() { - return connection; - } - - public String getAcceptEncoding() { - return acceptEncoding; - } - - public String getContentType() { - return contentType; - } - - public String getIfModifiedSince() { - return ifModifiedSince; - } - - public String getIfNoneMatch() { - return ifNoneMatch; - } - - public String getProxyAuthorization() { - return proxyAuthorization; - } - - public void setChunked() { - if (this.transferEncoding != null) { - headers.removeAll("Transfer-Encoding"); - } - headers.add("Transfer-Encoding", "chunked"); - this.transferEncoding = "chunked"; - } - - public void setContentLength(int contentLength) { - if (this.contentLength != -1) { - headers.removeAll("Content-Length"); - } - headers.add("Content-Length", Integer.toString(contentLength)); - this.contentLength = contentLength; - } - - public void setUserAgent(String userAgent) { - if (this.userAgent != null) { - headers.removeAll("User-Agent"); - } - headers.add("User-Agent", userAgent); - this.userAgent = userAgent; - } - - public void setHost(String host) { - if (this.host != null) { - headers.removeAll("Host"); - } - headers.add("Host", host); - this.host = host; - } - - public void setConnection(String connection) { - if (this.connection != null) { - headers.removeAll("Connection"); - } - headers.add("Connection", connection); - this.connection = connection; - } - - public void setAcceptEncoding(String acceptEncoding) { - if (this.acceptEncoding != null) { - headers.removeAll("Accept-Encoding"); - } - headers.add("Accept-Encoding", acceptEncoding); - this.acceptEncoding = acceptEncoding; - } - - public void setContentType(String contentType) { - if (this.contentType != null) { - headers.removeAll("Content-Type"); - } - headers.add("Content-Type", contentType); - this.contentType = contentType; - } - - public void setIfModifiedSince(Date date) { - if (ifModifiedSince != null) { - headers.removeAll("If-Modified-Since"); - } - String formattedDate = HttpDate.format(date); - headers.add("If-Modified-Since", formattedDate); - ifModifiedSince = formattedDate; - } - - public void setIfNoneMatch(String ifNoneMatch) { - if (this.ifNoneMatch != null) { - headers.removeAll("If-None-Match"); - } - headers.add("If-None-Match", ifNoneMatch); - this.ifNoneMatch = ifNoneMatch; - } - - /** - * Returns true if the request contains conditions that save the server from - * sending a response that the client has locally. When the caller adds - * conditions, this cache won't participate in the request. - */ - public boolean hasConditions() { - return ifModifiedSince != null || ifNoneMatch != null; - } - - public void addCookies(Map> allCookieHeaders) { - for (Map.Entry> entry : allCookieHeaders.entrySet()) { - String key = entry.getKey(); - if ("Cookie".equalsIgnoreCase(key) || "Cookie2".equalsIgnoreCase(key)) { - headers.addAll(key, entry.getValue()); - } - } - } -} diff --git a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/ResponseHeaders.java b/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/ResponseHeaders.java deleted file mode 100644 index 2ab564dcf..000000000 --- a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/ResponseHeaders.java +++ /dev/null @@ -1,497 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed 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. - */ - -package com.squareup.okhttp.internal.http; - -import com.squareup.okhttp.ResponseSource; -import java.io.IOException; -import java.net.HttpURLConnection; -import java.net.URI; -import java.util.Collections; -import java.util.Date; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeSet; -import java.util.concurrent.TimeUnit; - -import static com.squareup.okhttp.internal.Util.equal; - -/** Parsed HTTP response headers. */ -public final class ResponseHeaders { - - /** HTTP header name for the local time when the request was sent. */ - private static final String SENT_MILLIS = "X-Android-Sent-Millis"; - - /** HTTP header name for the local time when the response was received. */ - private static final String RECEIVED_MILLIS = "X-Android-Received-Millis"; - - /** HTTP synthetic header with the response source. */ - static final String RESPONSE_SOURCE = "X-Android-Response-Source"; - - private final URI uri; - private final RawHeaders headers; - - /** The server's time when this response was served, if known. */ - private Date servedDate; - - /** The last modified date of the response, if known. */ - private Date lastModified; - - /** - * The expiration date of the response, if known. If both this field and the - * max age are set, the max age is preferred. - */ - private Date expires; - - /** - * Extension header set by HttpURLConnectionImpl specifying the timestamp - * when the HTTP request was first initiated. - */ - private long sentRequestMillis; - - /** - * Extension header set by HttpURLConnectionImpl specifying the timestamp - * when the HTTP response was first received. - */ - private long receivedResponseMillis; - - /** - * In the response, this field's name "no-cache" is misleading. It doesn't - * prevent us from caching the response; it only means we have to validate - * the response with the origin server before returning it. We can do this - * with a conditional get. - */ - private boolean noCache; - - /** If true, this response should not be cached. */ - private boolean noStore; - - /** - * The duration past the response's served date that it can be served - * without validation. - */ - private int maxAgeSeconds = -1; - - /** - * The "s-maxage" directive is the max age for shared caches. Not to be - * confused with "max-age" for non-shared caches, As in Firefox and Chrome, - * this directive is not honored by this cache. - */ - private int sMaxAgeSeconds = -1; - - /** - * This request header field's name "only-if-cached" is misleading. It - * actually means "do not use the network". It is set by a client who only - * wants to make a request if it can be fully satisfied by the cache. - * Cached responses that would require validation (ie. conditional gets) are - * not permitted if this header is set. - */ - private boolean isPublic; - private boolean mustRevalidate; - private String etag; - private int ageSeconds = -1; - - /** Case-insensitive set of field names. */ - private Set varyFields = Collections.emptySet(); - - private String contentEncoding; - private String transferEncoding; - private int contentLength = -1; - private String connection; - - public ResponseHeaders(URI uri, RawHeaders headers) { - this.uri = uri; - this.headers = headers; - - HeaderParser.CacheControlHandler handler = new HeaderParser.CacheControlHandler() { - @Override public void handle(String directive, String parameter) { - if ("no-cache".equalsIgnoreCase(directive)) { - noCache = true; - } else if ("no-store".equalsIgnoreCase(directive)) { - noStore = true; - } else if ("max-age".equalsIgnoreCase(directive)) { - maxAgeSeconds = HeaderParser.parseSeconds(parameter); - } else if ("s-maxage".equalsIgnoreCase(directive)) { - sMaxAgeSeconds = HeaderParser.parseSeconds(parameter); - } else if ("public".equalsIgnoreCase(directive)) { - isPublic = true; - } else if ("must-revalidate".equalsIgnoreCase(directive)) { - mustRevalidate = true; - } - } - }; - - for (int i = 0; i < headers.length(); i++) { - String fieldName = headers.getFieldName(i); - String value = headers.getValue(i); - if ("Cache-Control".equalsIgnoreCase(fieldName)) { - HeaderParser.parseCacheControl(value, handler); - } else if ("Date".equalsIgnoreCase(fieldName)) { - servedDate = HttpDate.parse(value); - } else if ("Expires".equalsIgnoreCase(fieldName)) { - expires = HttpDate.parse(value); - } else if ("Last-Modified".equalsIgnoreCase(fieldName)) { - lastModified = HttpDate.parse(value); - } else if ("ETag".equalsIgnoreCase(fieldName)) { - etag = value; - } else if ("Pragma".equalsIgnoreCase(fieldName)) { - if ("no-cache".equalsIgnoreCase(value)) { - noCache = true; - } - } else if ("Age".equalsIgnoreCase(fieldName)) { - ageSeconds = HeaderParser.parseSeconds(value); - } else if ("Vary".equalsIgnoreCase(fieldName)) { - // Replace the immutable empty set with something we can mutate. - if (varyFields.isEmpty()) { - varyFields = new TreeSet(String.CASE_INSENSITIVE_ORDER); - } - for (String varyField : value.split(",")) { - varyFields.add(varyField.trim()); - } - } else if ("Content-Encoding".equalsIgnoreCase(fieldName)) { - contentEncoding = value; - } else if ("Transfer-Encoding".equalsIgnoreCase(fieldName)) { - transferEncoding = value; - } else if ("Content-Length".equalsIgnoreCase(fieldName)) { - try { - contentLength = Integer.parseInt(value); - } catch (NumberFormatException ignored) { - } - } else if ("Connection".equalsIgnoreCase(fieldName)) { - connection = value; - } else if (SENT_MILLIS.equalsIgnoreCase(fieldName)) { - sentRequestMillis = Long.parseLong(value); - } else if (RECEIVED_MILLIS.equalsIgnoreCase(fieldName)) { - receivedResponseMillis = Long.parseLong(value); - } - } - } - - public boolean isContentEncodingGzip() { - return "gzip".equalsIgnoreCase(contentEncoding); - } - - public void stripContentEncoding() { - contentEncoding = null; - headers.removeAll("Content-Encoding"); - } - - public void stripContentLength() { - contentLength = -1; - headers.removeAll("Content-Length"); - } - - public boolean isChunked() { - return "chunked".equalsIgnoreCase(transferEncoding); - } - - public boolean hasConnectionClose() { - return "close".equalsIgnoreCase(connection); - } - - public URI getUri() { - return uri; - } - - public RawHeaders getHeaders() { - return headers; - } - - public Date getServedDate() { - return servedDate; - } - - public Date getLastModified() { - return lastModified; - } - - public Date getExpires() { - return expires; - } - - public boolean isNoCache() { - return noCache; - } - - public boolean isNoStore() { - return noStore; - } - - public int getMaxAgeSeconds() { - return maxAgeSeconds; - } - - public int getSMaxAgeSeconds() { - return sMaxAgeSeconds; - } - - public boolean isPublic() { - return isPublic; - } - - public boolean isMustRevalidate() { - return mustRevalidate; - } - - public String getEtag() { - return etag; - } - - public Set getVaryFields() { - return varyFields; - } - - public String getContentEncoding() { - return contentEncoding; - } - - public int getContentLength() { - return contentLength; - } - - public String getConnection() { - return connection; - } - - public void setLocalTimestamps(long sentRequestMillis, long receivedResponseMillis) { - this.sentRequestMillis = sentRequestMillis; - headers.add(SENT_MILLIS, Long.toString(sentRequestMillis)); - this.receivedResponseMillis = receivedResponseMillis; - headers.add(RECEIVED_MILLIS, Long.toString(receivedResponseMillis)); - } - - public void setResponseSource(ResponseSource responseSource) { - headers.set(RESPONSE_SOURCE, responseSource.toString() + " " + headers.getResponseCode()); - } - - /** - * Returns the current age of the response, in milliseconds. The calculation - * is specified by RFC 2616, 13.2.3 Age Calculations. - */ - private long computeAge(long nowMillis) { - long apparentReceivedAge = - servedDate != null ? Math.max(0, receivedResponseMillis - servedDate.getTime()) : 0; - long receivedAge = - ageSeconds != -1 ? Math.max(apparentReceivedAge, TimeUnit.SECONDS.toMillis(ageSeconds)) - : apparentReceivedAge; - long responseDuration = receivedResponseMillis - sentRequestMillis; - long residentDuration = nowMillis - receivedResponseMillis; - return receivedAge + responseDuration + residentDuration; - } - - /** - * Returns the number of milliseconds that the response was fresh for, - * starting from the served date. - */ - private long computeFreshnessLifetime() { - if (maxAgeSeconds != -1) { - return TimeUnit.SECONDS.toMillis(maxAgeSeconds); - } else if (expires != null) { - long servedMillis = servedDate != null ? servedDate.getTime() : receivedResponseMillis; - long delta = expires.getTime() - servedMillis; - return delta > 0 ? delta : 0; - } else if (lastModified != null && uri.getRawQuery() == null) { - // As recommended by the HTTP RFC and implemented in Firefox, the - // max age of a document should be defaulted to 10% of the - // document's age at the time it was served. Default expiration - // dates aren't used for URIs containing a query. - long servedMillis = servedDate != null ? servedDate.getTime() : sentRequestMillis; - long delta = servedMillis - lastModified.getTime(); - return delta > 0 ? (delta / 10) : 0; - } - return 0; - } - - /** - * Returns true if computeFreshnessLifetime used a heuristic. If we used a - * heuristic to serve a cached response older than 24 hours, we are required - * to attach a warning. - */ - private boolean isFreshnessLifetimeHeuristic() { - return maxAgeSeconds == -1 && expires == null; - } - - /** - * Returns true if this response can be stored to later serve another - * request. - */ - public boolean isCacheable(RequestHeaders request) { - // Always go to network for uncacheable response codes (RFC 2616, 13.4), - // This implementation doesn't support caching partial content. - int responseCode = headers.getResponseCode(); - if (responseCode != HttpURLConnection.HTTP_OK - && responseCode != HttpURLConnection.HTTP_NOT_AUTHORITATIVE - && responseCode != HttpURLConnection.HTTP_MULT_CHOICE - && responseCode != HttpURLConnection.HTTP_MOVED_PERM - && responseCode != HttpURLConnection.HTTP_GONE) { - return false; - } - - // Responses to authorized requests aren't cacheable unless they include - // a 'public', 'must-revalidate' or 's-maxage' directive. - if (request.hasAuthorization() && !isPublic && !mustRevalidate && sMaxAgeSeconds == -1) { - return false; - } - - if (noStore) { - return false; - } - - return true; - } - - /** - * Returns true if a Vary header contains an asterisk. Such responses cannot - * be cached. - */ - public boolean hasVaryAll() { - return varyFields.contains("*"); - } - - /** - * Returns true if none of the Vary headers on this response have changed - * between {@code cachedRequest} and {@code newRequest}. - */ - public boolean varyMatches(Map> cachedRequest, - Map> newRequest) { - for (String field : varyFields) { - if (!equal(cachedRequest.get(field), newRequest.get(field))) { - return false; - } - } - return true; - } - - /** Returns the source to satisfy {@code request} given this cached response. */ - public ResponseSource chooseResponseSource(long nowMillis, RequestHeaders request) { - // If this response shouldn't have been stored, it should never be used - // as a response source. This check should be redundant as long as the - // persistence store is well-behaved and the rules are constant. - if (!isCacheable(request)) { - return ResponseSource.NETWORK; - } - - if (request.isNoCache() || request.hasConditions()) { - return ResponseSource.NETWORK; - } - - long ageMillis = computeAge(nowMillis); - long freshMillis = computeFreshnessLifetime(); - - if (request.getMaxAgeSeconds() != -1) { - freshMillis = Math.min(freshMillis, TimeUnit.SECONDS.toMillis(request.getMaxAgeSeconds())); - } - - long minFreshMillis = 0; - if (request.getMinFreshSeconds() != -1) { - minFreshMillis = TimeUnit.SECONDS.toMillis(request.getMinFreshSeconds()); - } - - long maxStaleMillis = 0; - if (!mustRevalidate && request.getMaxStaleSeconds() != -1) { - maxStaleMillis = TimeUnit.SECONDS.toMillis(request.getMaxStaleSeconds()); - } - - if (!noCache && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) { - if (ageMillis + minFreshMillis >= freshMillis) { - headers.add("Warning", "110 HttpURLConnection \"Response is stale\""); - } - long oneDayMillis = 24 * 60 * 60 * 1000L; - if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) { - headers.add("Warning", "113 HttpURLConnection \"Heuristic expiration\""); - } - return ResponseSource.CACHE; - } - - if (lastModified != null) { - request.setIfModifiedSince(lastModified); - } else if (servedDate != null) { - request.setIfModifiedSince(servedDate); - } - - if (etag != null) { - request.setIfNoneMatch(etag); - } - - return request.hasConditions() ? ResponseSource.CONDITIONAL_CACHE : ResponseSource.NETWORK; - } - - /** - * Returns true if this cached response should be used; false if the - * network response should be used. - */ - public boolean validate(ResponseHeaders networkResponse) { - if (networkResponse.headers.getResponseCode() == HttpURLConnection.HTTP_NOT_MODIFIED) { - return true; - } - - // The HTTP spec says that if the network's response is older than our - // cached response, we may return the cache's response. Like Chrome (but - // unlike Firefox), this client prefers to return the newer response. - if (lastModified != null - && networkResponse.lastModified != null - && networkResponse.lastModified.getTime() < lastModified.getTime()) { - return true; - } - - return false; - } - - /** - * Combines this cached header with a network header as defined by RFC 2616, - * 13.5.3. - */ - public ResponseHeaders combine(ResponseHeaders network) throws IOException { - RawHeaders result = new RawHeaders(); - result.setStatusLine(headers.getStatusLine()); - - for (int i = 0; i < headers.length(); i++) { - String fieldName = headers.getFieldName(i); - String value = headers.getValue(i); - if ("Warning".equals(fieldName) && value.startsWith("1")) { - continue; // drop 100-level freshness warnings - } - if (!isEndToEnd(fieldName) || network.headers.get(fieldName) == null) { - result.add(fieldName, value); - } - } - - for (int i = 0; i < network.headers.length(); i++) { - String fieldName = network.headers.getFieldName(i); - if (isEndToEnd(fieldName)) { - result.add(fieldName, network.headers.getValue(i)); - } - } - - return new ResponseHeaders(uri, result); - } - - /** - * Returns true if {@code fieldName} is an end-to-end HTTP header, as - * defined by RFC 2616, 13.5.1. - */ - private static boolean isEndToEnd(String fieldName) { - return !"Connection".equalsIgnoreCase(fieldName) - && !"Keep-Alive".equalsIgnoreCase(fieldName) - && !"Proxy-Authenticate".equalsIgnoreCase(fieldName) - && !"Proxy-Authorization".equalsIgnoreCase(fieldName) - && !"TE".equalsIgnoreCase(fieldName) - && !"Trailers".equalsIgnoreCase(fieldName) - && !"Transfer-Encoding".equalsIgnoreCase(fieldName) - && !"Upgrade".equalsIgnoreCase(fieldName); - } -} diff --git a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/RetryableOutputStream.java b/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/RetryableOutputStream.java deleted file mode 100644 index 5eb6b7645..000000000 --- a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/RetryableOutputStream.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed 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. - */ - -package com.squareup.okhttp.internal.http; - -import com.squareup.okhttp.internal.AbstractOutputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.net.ProtocolException; - -import static com.squareup.okhttp.internal.Util.checkOffsetAndCount; - -/** - * An HTTP request body that's completely buffered in memory. This allows - * the post body to be transparently re-sent if the HTTP request must be - * sent multiple times. - */ -final class RetryableOutputStream extends AbstractOutputStream { - private final int limit; - private final ByteArrayOutputStream content; - - public RetryableOutputStream(int limit) { - this.limit = limit; - this.content = new ByteArrayOutputStream(limit); - } - - public RetryableOutputStream() { - this.limit = -1; - this.content = new ByteArrayOutputStream(); - } - - @Override public synchronized void close() throws IOException { - if (closed) { - return; - } - closed = true; - if (content.size() < limit) { - throw new ProtocolException( - "content-length promised " + limit + " bytes, but received " + content.size()); - } - } - - @Override public synchronized void write(byte[] buffer, int offset, int count) - throws IOException { - checkNotClosed(); - checkOffsetAndCount(buffer.length, offset, count); - if (limit != -1 && content.size() > limit - count) { - throw new ProtocolException("exceeded content-length limit of " + limit + " bytes"); - } - content.write(buffer, offset, count); - } - - public synchronized int contentLength() throws IOException { - close(); - return content.size(); - } - - public void writeToSocket(OutputStream socketOut) throws IOException { - content.writeTo(socketOut); - } -} diff --git a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/RouteSelector.java b/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/RouteSelector.java deleted file mode 100644 index ce0a71d84..000000000 --- a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/RouteSelector.java +++ /dev/null @@ -1,275 +0,0 @@ -/* - * Copyright (C) 2012 Square, Inc. - * - * Licensed 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. - */ -package com.squareup.okhttp.internal.http; - -import com.squareup.okhttp.Address; -import com.squareup.okhttp.Connection; -import com.squareup.okhttp.ConnectionPool; -import com.squareup.okhttp.Route; -import com.squareup.okhttp.internal.Dns; -import java.io.IOException; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.Proxy; -import java.net.ProxySelector; -import java.net.SocketAddress; -import java.net.URI; -import java.net.UnknownHostException; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.NoSuchElementException; -import java.util.Set; -import javax.net.ssl.SSLHandshakeException; - -import static com.squareup.okhttp.internal.Util.getEffectivePort; - -/** - * Selects routes to connect to an origin server. Each connection requires a - * choice of proxy server, IP address, and TLS mode. Connections may also be - * recycled. - */ -public final class RouteSelector { - /** Uses {@link com.squareup.okhttp.internal.Platform#enableTlsExtensions}. */ - private static final int TLS_MODE_MODERN = 1; - /** Uses {@link com.squareup.okhttp.internal.Platform#supportTlsIntolerantServer}. */ - private static final int TLS_MODE_COMPATIBLE = 0; - /** No TLS mode. */ - private static final int TLS_MODE_NULL = -1; - - private final Address address; - private final URI uri; - private final ProxySelector proxySelector; - private final ConnectionPool pool; - private final Dns dns; - private final Set failedRoutes; - - /* The most recently attempted route. */ - private Proxy lastProxy; - private InetSocketAddress lastInetSocketAddress; - - /* State for negotiating the next proxy to use. */ - private boolean hasNextProxy; - private Proxy userSpecifiedProxy; - private Iterator proxySelectorProxies; - - /* State for negotiating the next InetSocketAddress to use. */ - private InetAddress[] socketAddresses; - private int nextSocketAddressIndex; - private int socketPort; - - /* State for negotiating the next TLS configuration */ - private int nextTlsMode = TLS_MODE_NULL; - - /* State for negotiating failed routes */ - private final List postponedRoutes; - - public RouteSelector(Address address, URI uri, ProxySelector proxySelector, ConnectionPool pool, - Dns dns, Set failedRoutes) { - this.address = address; - this.uri = uri; - this.proxySelector = proxySelector; - this.pool = pool; - this.dns = dns; - this.failedRoutes = failedRoutes; - this.postponedRoutes = new LinkedList(); - - resetNextProxy(uri, address.getProxy()); - } - - /** - * Returns true if there's another route to attempt. Every address has at - * least one route. - */ - public boolean hasNext() { - return hasNextTlsMode() || hasNextInetSocketAddress() || hasNextProxy() || hasNextPostponed(); - } - - /** - * Returns the next route address to attempt. - * - * @throws NoSuchElementException if there are no more routes to attempt. - */ - public Connection next() throws IOException { - // Always prefer pooled connections over new connections. - Connection pooled = pool.get(address); - if (pooled != null) { - return pooled; - } - - // Compute the next route to attempt. - if (!hasNextTlsMode()) { - if (!hasNextInetSocketAddress()) { - if (!hasNextProxy()) { - if (!hasNextPostponed()) { - throw new NoSuchElementException(); - } - return new Connection(nextPostponed()); - } - lastProxy = nextProxy(); - resetNextInetSocketAddress(lastProxy); - } - lastInetSocketAddress = nextInetSocketAddress(); - resetNextTlsMode(); - } - - boolean modernTls = nextTlsMode() == TLS_MODE_MODERN; - Route route = new Route(address, lastProxy, lastInetSocketAddress, modernTls); - if (failedRoutes.contains(route)) { - postponedRoutes.add(route); - // We will only recurse in order to skip previously failed routes. They will be - // tried last. - return next(); - } - - return new Connection(route); - } - - /** - * Clients should invoke this method when they encounter a connectivity - * failure on a connection returned by this route selector. - */ - public void connectFailed(Connection connection, IOException failure) { - Route failedRoute = connection.getRoute(); - if (failedRoute.getProxy().type() != Proxy.Type.DIRECT && proxySelector != null) { - // Tell the proxy selector when we fail to connect on a fresh connection. - proxySelector.connectFailed(uri, failedRoute.getProxy().address(), failure); - } - - failedRoutes.add(failedRoute); - if (!(failure instanceof SSLHandshakeException)) { - // If the problem was not related to SSL then it will also fail with - // a different Tls mode therefore we can be proactive about it. - failedRoutes.add(failedRoute.flipTlsMode()); - } - } - - /** Resets {@link #nextProxy} to the first option. */ - private void resetNextProxy(URI uri, Proxy proxy) { - this.hasNextProxy = true; // This includes NO_PROXY! - if (proxy != null) { - this.userSpecifiedProxy = proxy; - } else { - List proxyList = proxySelector.select(uri); - if (proxyList != null) { - this.proxySelectorProxies = proxyList.iterator(); - } - } - } - - /** Returns true if there's another proxy to try. */ - private boolean hasNextProxy() { - return hasNextProxy; - } - - /** Returns the next proxy to try. May be PROXY.NO_PROXY but never null. */ - private Proxy nextProxy() { - // If the user specifies a proxy, try that and only that. - if (userSpecifiedProxy != null) { - hasNextProxy = false; - return userSpecifiedProxy; - } - - // Try each of the ProxySelector choices until one connection succeeds. If none succeed - // then we'll try a direct connection below. - if (proxySelectorProxies != null) { - while (proxySelectorProxies.hasNext()) { - Proxy candidate = proxySelectorProxies.next(); - if (candidate.type() != Proxy.Type.DIRECT) { - return candidate; - } - } - } - - // Finally try a direct connection. - hasNextProxy = false; - return Proxy.NO_PROXY; - } - - /** Resets {@link #nextInetSocketAddress} to the first option. */ - private void resetNextInetSocketAddress(Proxy proxy) throws UnknownHostException { - socketAddresses = null; // Clear the addresses. Necessary if getAllByName() below throws! - - String socketHost; - if (proxy.type() == Proxy.Type.DIRECT) { - socketHost = uri.getHost(); - socketPort = getEffectivePort(uri); - } else { - SocketAddress proxyAddress = proxy.address(); - if (!(proxyAddress instanceof InetSocketAddress)) { - throw new IllegalArgumentException( - "Proxy.address() is not an " + "InetSocketAddress: " + proxyAddress.getClass()); - } - InetSocketAddress proxySocketAddress = (InetSocketAddress) proxyAddress; - socketHost = proxySocketAddress.getHostName(); - socketPort = proxySocketAddress.getPort(); - } - - // Try each address for best behavior in mixed IPv4/IPv6 environments. - socketAddresses = dns.getAllByName(socketHost); - nextSocketAddressIndex = 0; - } - - /** Returns true if there's another socket address to try. */ - private boolean hasNextInetSocketAddress() { - return socketAddresses != null; - } - - /** Returns the next socket address to try. */ - private InetSocketAddress nextInetSocketAddress() throws UnknownHostException { - InetSocketAddress result = - new InetSocketAddress(socketAddresses[nextSocketAddressIndex++], socketPort); - if (nextSocketAddressIndex == socketAddresses.length) { - socketAddresses = null; // So that hasNextInetSocketAddress() returns false. - nextSocketAddressIndex = 0; - } - - return result; - } - - /** Resets {@link #nextTlsMode} to the first option. */ - private void resetNextTlsMode() { - nextTlsMode = (address.getSslSocketFactory() != null) ? TLS_MODE_MODERN : TLS_MODE_COMPATIBLE; - } - - /** Returns true if there's another TLS mode to try. */ - private boolean hasNextTlsMode() { - return nextTlsMode != TLS_MODE_NULL; - } - - /** Returns the next TLS mode to try. */ - private int nextTlsMode() { - if (nextTlsMode == TLS_MODE_MODERN) { - nextTlsMode = TLS_MODE_COMPATIBLE; - return TLS_MODE_MODERN; - } else if (nextTlsMode == TLS_MODE_COMPATIBLE) { - nextTlsMode = TLS_MODE_NULL; // So that hasNextTlsMode() returns false. - return TLS_MODE_COMPATIBLE; - } else { - throw new AssertionError(); - } - } - - /** Returns true if there is another postponed route to try. */ - private boolean hasNextPostponed() { - return !postponedRoutes.isEmpty(); - } - - /** Returns the next postponed route to try. */ - private Route nextPostponed() { - return postponedRoutes.remove(0); - } -} diff --git a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/SpdyTransport.java b/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/SpdyTransport.java deleted file mode 100644 index 18ab5668e..000000000 --- a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/SpdyTransport.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed 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. - */ - -package com.squareup.okhttp.internal.http; - -import com.squareup.okhttp.internal.spdy.SpdyConnection; -import com.squareup.okhttp.internal.spdy.SpdyStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.CacheRequest; -import java.net.URL; -import java.util.List; - -public final class SpdyTransport implements Transport { - private final HttpEngine httpEngine; - private final SpdyConnection spdyConnection; - private SpdyStream stream; - - public SpdyTransport(HttpEngine httpEngine, SpdyConnection spdyConnection) { - this.httpEngine = httpEngine; - this.spdyConnection = spdyConnection; - } - - @Override public OutputStream createRequestBody() throws IOException { - // TODO: if we aren't streaming up to the server, we should buffer the whole request - writeRequestHeaders(); - return stream.getOutputStream(); - } - - @Override public void writeRequestHeaders() throws IOException { - if (stream != null) { - return; - } - httpEngine.writingRequestHeaders(); - RawHeaders requestHeaders = httpEngine.requestHeaders.getHeaders(); - String version = httpEngine.connection.getHttpMinorVersion() == 1 ? "HTTP/1.1" : "HTTP/1.0"; - URL url = httpEngine.policy.getURL(); - requestHeaders.addSpdyRequestHeaders(httpEngine.method, HttpEngine.requestPath(url), version, - HttpEngine.getOriginAddress(url), httpEngine.uri.getScheme()); - boolean hasRequestBody = httpEngine.hasRequestBody(); - boolean hasResponseBody = true; - stream = spdyConnection.newStream(requestHeaders.toNameValueBlock(), hasRequestBody, - hasResponseBody); - stream.setReadTimeout(httpEngine.policy.getReadTimeout()); - } - - @Override public void writeRequestBody(RetryableOutputStream requestBody) throws IOException { - throw new UnsupportedOperationException(); - } - - @Override public void flushRequest() throws IOException { - stream.getOutputStream().close(); - } - - @Override public ResponseHeaders readResponseHeaders() throws IOException { - List nameValueBlock = stream.getResponseHeaders(); - RawHeaders rawHeaders = RawHeaders.fromNameValueBlock(nameValueBlock); - rawHeaders.computeResponseStatusLineFromSpdyHeaders(); - httpEngine.receiveHeaders(rawHeaders); - return new ResponseHeaders(httpEngine.uri, rawHeaders); - } - - @Override public InputStream getTransferStream(CacheRequest cacheRequest) throws IOException { - return new UnknownLengthHttpInputStream(stream.getInputStream(), cacheRequest, httpEngine); - } - - @Override public boolean makeReusable(boolean streamCancelled, OutputStream requestBodyOut, - InputStream responseBodyIn) { - if (streamCancelled) { - if (stream != null) { - stream.closeLater(SpdyStream.RST_CANCEL); - return true; - } else { - // If stream is null, it either means that writeRequestHeaders wasn't called - // or that SpdyConnection#newStream threw an IOEXception. In both cases there's - // nothing to do here and this stream can't be reused. - return false; - } - } - return true; - } -} diff --git a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/Transport.java b/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/Transport.java deleted file mode 100644 index 518827e88..000000000 --- a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/Transport.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed 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. - */ - -package com.squareup.okhttp.internal.http; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.CacheRequest; - -interface Transport { - /** - * Returns an output stream where the request body can be written. The - * returned stream will of one of two types: - *

    - *
  • Direct. Bytes are written to the socket and - * forgotten. This is most efficient, particularly for large request - * bodies. The returned stream may be buffered; the caller must call - * {@link #flushRequest} before reading the response.
  • - *
  • Buffered. Bytes are written to an in memory - * buffer, and must be explicitly flushed with a call to {@link - * #writeRequestBody}. This allows HTTP authorization (401, 407) - * responses to be retransmitted transparently.
  • - *
- */ - // TODO: don't bother retransmitting the request body? It's quite a corner - // case and there's uncertainty whether Firefox or Chrome do this - OutputStream createRequestBody() throws IOException; - - /** This should update the HTTP engine's sentRequestMillis field. */ - void writeRequestHeaders() throws IOException; - - /** - * Sends the request body returned by {@link #createRequestBody} to the - * remote peer. - */ - void writeRequestBody(RetryableOutputStream requestBody) throws IOException; - - /** Flush the request body to the underlying socket. */ - void flushRequest() throws IOException; - - /** Read response headers and update the cookie manager. */ - ResponseHeaders readResponseHeaders() throws IOException; - - // TODO: make this the content stream? - InputStream getTransferStream(CacheRequest cacheRequest) throws IOException; - - /** Returns true if the underlying connection can be recycled. */ - boolean makeReusable(boolean streamReusable, OutputStream requestBodyOut, - InputStream responseBodyIn); -} diff --git a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/UnknownLengthHttpInputStream.java b/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/UnknownLengthHttpInputStream.java deleted file mode 100644 index 729e0b92d..000000000 --- a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/http/UnknownLengthHttpInputStream.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed 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. - */ -package com.squareup.okhttp.internal.http; - -import java.io.IOException; -import java.io.InputStream; -import java.net.CacheRequest; - -import static com.squareup.okhttp.internal.Util.checkOffsetAndCount; - -/** An HTTP message body terminated by the end of the underlying stream. */ -final class UnknownLengthHttpInputStream extends AbstractHttpInputStream { - private boolean inputExhausted; - - UnknownLengthHttpInputStream(InputStream is, CacheRequest cacheRequest, HttpEngine httpEngine) - throws IOException { - super(is, httpEngine, cacheRequest); - } - - @Override public int read(byte[] buffer, int offset, int count) throws IOException { - checkOffsetAndCount(buffer.length, offset, count); - checkNotClosed(); - if (in == null || inputExhausted) { - return -1; - } - int read = in.read(buffer, offset, count); - if (read == -1) { - inputExhausted = true; - endOfInput(false); - return -1; - } - cacheWrite(buffer, offset, read); - return read; - } - - @Override public int available() throws IOException { - checkNotClosed(); - return in == null ? 0 : in.available(); - } - - @Override public void close() throws IOException { - if (closed) { - return; - } - closed = true; - if (!inputExhausted) { - unexpectedEndOfInput(); - } - } -} diff --git a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/spdy/IncomingStreamHandler.java b/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/spdy/IncomingStreamHandler.java deleted file mode 100644 index 875fff0fd..000000000 --- a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/spdy/IncomingStreamHandler.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed 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. - */ - -package com.squareup.okhttp.internal.spdy; - -import java.io.IOException; - -/** Listener to be notified when a connected peer creates a new stream. */ -public interface IncomingStreamHandler { - IncomingStreamHandler REFUSE_INCOMING_STREAMS = new IncomingStreamHandler() { - @Override public void receive(SpdyStream stream) throws IOException { - stream.close(SpdyStream.RST_REFUSED_STREAM); - } - }; - - /** - * Handle a new stream from this connection's peer. Implementations should - * respond by either {@link SpdyStream#reply replying to the stream} or - * {@link SpdyStream#close closing it}. This response does not need to be - * synchronous. - */ - void receive(SpdyStream stream) throws IOException; -} diff --git a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/spdy/Ping.java b/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/spdy/Ping.java deleted file mode 100644 index c58525578..000000000 --- a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/spdy/Ping.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2012 Square, Inc. - * - * Licensed 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. - */ -package com.squareup.okhttp.internal.spdy; - -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -/** - * A locally-originated ping. - */ -public final class Ping { - private final CountDownLatch latch = new CountDownLatch(1); - private long sent = -1; - private long received = -1; - - Ping() { - } - - void send() { - if (sent != -1) throw new IllegalStateException(); - sent = System.nanoTime(); - } - - void receive() { - if (received != -1 || sent == -1) throw new IllegalStateException(); - received = System.nanoTime(); - latch.countDown(); - } - - void cancel() { - if (received != -1 || sent == -1) throw new IllegalStateException(); - received = sent - 1; - latch.countDown(); - } - - /** - * Returns the round trip time for this ping in nanoseconds, waiting for the - * response to arrive if necessary. Returns -1 if the response was - * cancelled. - */ - public long roundTripTime() throws InterruptedException { - latch.await(); - return received - sent; - } - - /** - * Returns the round trip time for this ping in nanoseconds, or -1 if the - * response was cancelled, or -2 if the timeout elapsed before the round - * trip completed. - */ - public long roundTripTime(long timeout, TimeUnit unit) throws InterruptedException { - if (latch.await(timeout, unit)) { - return received - sent; - } else { - return -2; - } - } -} diff --git a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/spdy/Settings.java b/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/spdy/Settings.java deleted file mode 100644 index 774d79121..000000000 --- a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/spdy/Settings.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright (C) 2012 Square, Inc. - * - * Licensed 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. - */ -package com.squareup.okhttp.internal.spdy; - -final class Settings { - /** - * From the spdy/3 spec, the default initial window size for all streams is - * 64 KiB. (Chrome 25 uses 10 MiB). - */ - static final int DEFAULT_INITIAL_WINDOW_SIZE = 64 * 1024; - - /** Peer request to clear durable settings. */ - static final int FLAG_CLEAR_PREVIOUSLY_PERSISTED_SETTINGS = 0x1; - - /** Sent by servers only. The peer requests this setting persisted for future connections. */ - static final int PERSIST_VALUE = 0x1; - /** Sent by clients only. The client is reminding the server of a persisted value. */ - static final int PERSISTED = 0x2; - - /** Sender's estimate of max incoming kbps. */ - static final int UPLOAD_BANDWIDTH = 0x1; - /** Sender's estimate of max outgoing kbps. */ - static final int DOWNLOAD_BANDWIDTH = 0x2; - /** Sender's estimate of milliseconds between sending a request and receiving a response. */ - static final int ROUND_TRIP_TIME = 0x3; - /** Sender's maximum number of concurrent streams. */ - static final int MAX_CONCURRENT_STREAMS = 0x4; - /** Current CWND in Packets. */ - static final int CURRENT_CWND = 0x5; - /** Retransmission rate. Percentage */ - static final int DOWNLOAD_RETRANS_RATE = 0x6; - /** Window size in bytes. */ - static final int INITIAL_WINDOW_SIZE = 0x7; - /** Window size in bytes. */ - static final int CLIENT_CERTIFICATE_VECTOR_SIZE = 0x8; - /** Total number of settings. */ - static final int COUNT = 0x9; - - /** Bitfield of which flags that values. */ - private int set; - - /** Bitfield of flags that have {@link #PERSIST_VALUE}. */ - private int persistValue; - - /** Bitfield of flags that have {@link #PERSISTED}. */ - private int persisted; - - /** Flag values. */ - private final int[] values = new int[COUNT]; - - void set(int id, int idFlags, int value) { - if (id >= values.length) { - return; // Discard unknown settings. - } - - int bit = 1 << id; - set |= bit; - if ((idFlags & PERSIST_VALUE) != 0) { - persistValue |= bit; - } else { - persistValue &= ~bit; - } - if ((idFlags & PERSISTED) != 0) { - persisted |= bit; - } else { - persisted &= ~bit; - } - - values[id] = value; - } - - /** Returns true if a value has been assigned for the setting {@code id}. */ - boolean isSet(int id) { - int bit = 1 << id; - return (set & bit) != 0; - } - - /** Returns the value for the setting {@code id}, or 0 if unset. */ - int get(int id) { - return values[id]; - } - - /** Returns the flags for the setting {@code id}, or 0 if unset. */ - int flags(int id) { - int result = 0; - if (isPersisted(id)) result |= Settings.PERSISTED; - if (persistValue(id)) result |= Settings.PERSIST_VALUE; - return result; - } - - /** Returns the number of settings that have values assigned. */ - int size() { - return Integer.bitCount(set); - } - - int getUploadBandwidth(int defaultValue) { - int bit = 1 << UPLOAD_BANDWIDTH; - return (bit & set) != 0 ? values[UPLOAD_BANDWIDTH] : defaultValue; - } - - int getDownloadBandwidth(int defaultValue) { - int bit = 1 << DOWNLOAD_BANDWIDTH; - return (bit & set) != 0 ? values[DOWNLOAD_BANDWIDTH] : defaultValue; - } - - int getRoundTripTime(int defaultValue) { - int bit = 1 << ROUND_TRIP_TIME; - return (bit & set) != 0 ? values[ROUND_TRIP_TIME] : defaultValue; - } - - int getMaxConcurrentStreams(int defaultValue) { - int bit = 1 << MAX_CONCURRENT_STREAMS; - return (bit & set) != 0 ? values[MAX_CONCURRENT_STREAMS] : defaultValue; - } - - int getCurrentCwnd(int defaultValue) { - int bit = 1 << CURRENT_CWND; - return (bit & set) != 0 ? values[CURRENT_CWND] : defaultValue; - } - - int getDownloadRetransRate(int defaultValue) { - int bit = 1 << DOWNLOAD_RETRANS_RATE; - return (bit & set) != 0 ? values[DOWNLOAD_RETRANS_RATE] : defaultValue; - } - - int getInitialWindowSize(int defaultValue) { - int bit = 1 << INITIAL_WINDOW_SIZE; - return (bit & set) != 0 ? values[INITIAL_WINDOW_SIZE] : defaultValue; - } - - int getClientCertificateVectorSize(int defaultValue) { - int bit = 1 << CLIENT_CERTIFICATE_VECTOR_SIZE; - return (bit & set) != 0 ? values[CLIENT_CERTIFICATE_VECTOR_SIZE] : defaultValue; - } - - /** - * Returns true if this user agent should use this setting in future SPDY - * connections to the same host. - */ - boolean persistValue(int id) { - int bit = 1 << id; - return (persistValue & bit) != 0; - } - - /** Returns true if this setting was persisted. */ - boolean isPersisted(int id) { - int bit = 1 << id; - return (persisted & bit) != 0; - } - - /** - * Writes {@code other} into this. If any setting is populated by this and - * {@code other}, the value and flags from {@code other} will be kept. - */ - void merge(Settings other) { - for (int i = 0; i < COUNT; i++) { - if (!other.isSet(i)) continue; - set(i, other.flags(i), other.get(i)); - } - } -} diff --git a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/spdy/SpdyConnection.java b/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/spdy/SpdyConnection.java deleted file mode 100644 index fccd14f89..000000000 --- a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/spdy/SpdyConnection.java +++ /dev/null @@ -1,579 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed 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. - */ - -package com.squareup.okhttp.internal.spdy; - -import com.squareup.okhttp.internal.NamedRunnable; -import com.squareup.okhttp.internal.Util; -import java.io.Closeable; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.Socket; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.SynchronousQueue; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; - -import static java.util.concurrent.Executors.defaultThreadFactory; - -/** - * A socket connection to a remote peer. A connection hosts streams which can - * send and receive data. - * - *

Many methods in this API are synchronous: the call is - * completed before the method returns. This is typical for Java but atypical - * for SPDY. This is motivated by exception transparency: an IOException that - * was triggered by a certain caller can be caught and handled by that caller. - */ -public final class SpdyConnection implements Closeable { - - // Internal state of this connection is guarded by 'this'. No blocking - // operations may be performed while holding this lock! - // - // Socket writes are guarded by spdyWriter. - // - // Socket reads are unguarded but are only made by the reader thread. - // - // Certain operations (like SYN_STREAM) need to synchronize on both the - // spdyWriter (to do blocking I/O) and this (to create streams). Such - // operations must synchronize on 'this' last. This ensures that we never - // wait for a blocking operation while holding 'this'. - - static final int FLAG_FIN = 0x1; - static final int FLAG_UNIDIRECTIONAL = 0x2; - - static final int TYPE_DATA = 0x0; - static final int TYPE_SYN_STREAM = 0x1; - static final int TYPE_SYN_REPLY = 0x2; - static final int TYPE_RST_STREAM = 0x3; - static final int TYPE_SETTINGS = 0x4; - static final int TYPE_NOOP = 0x5; - static final int TYPE_PING = 0x6; - static final int TYPE_GOAWAY = 0x7; - static final int TYPE_HEADERS = 0x8; - static final int TYPE_WINDOW_UPDATE = 0x9; - static final int TYPE_CREDENTIAL = 0x10; - static final int VERSION = 3; - - static final int GOAWAY_OK = 0; - static final int GOAWAY_PROTOCOL_ERROR = 1; - static final int GOAWAY_INTERNAL_ERROR = 2; - - private static final ExecutorService executor = - new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS, - new SynchronousQueue(), defaultThreadFactory()); - - /** True if this peer initiated the connection. */ - final boolean client; - - /** - * User code to run in response to an incoming stream. Callbacks must not be - * run on the callback executor. - */ - private final IncomingStreamHandler handler; - private final SpdyReader spdyReader; - private final SpdyWriter spdyWriter; - - private final Map streams = new HashMap(); - private final String hostName; - private int lastGoodStreamId; - private int nextStreamId; - private boolean shutdown; - private long idleStartTimeNs = System.nanoTime(); - - /** Lazily-created map of in-flight pings awaiting a response. Guarded by this. */ - private Map pings; - private int nextPingId; - - /** Lazily-created settings for this connection. */ - Settings settings; - - private SpdyConnection(Builder builder) { - client = builder.client; - handler = builder.handler; - spdyReader = new SpdyReader(builder.in); - spdyWriter = new SpdyWriter(builder.out); - nextStreamId = builder.client ? 1 : 2; - nextPingId = builder.client ? 1 : 2; - - hostName = builder.hostName; - - new Thread(new Reader(), "Spdy Reader " + hostName).start(); - } - - /** - * Returns the number of {@link SpdyStream#isOpen() open streams} on this - * connection. - */ - public synchronized int openStreamCount() { - return streams.size(); - } - - private synchronized SpdyStream getStream(int id) { - return streams.get(id); - } - - synchronized SpdyStream removeStream(int streamId) { - SpdyStream stream = streams.remove(streamId); - if (stream != null && streams.isEmpty()) { - setIdle(true); - } - return stream; - } - - private synchronized void setIdle(boolean value) { - idleStartTimeNs = value ? System.nanoTime() : 0L; - } - - /** Returns true if this connection is idle. */ - public synchronized boolean isIdle() { - return idleStartTimeNs != 0L; - } - - /** Returns the time in ns when this connection became idle or 0L if connection is not idle. */ - public synchronized long getIdleStartTimeNs() { - return idleStartTimeNs; - } - - /** - * Returns a new locally-initiated stream. - * - * @param out true to create an output stream that we can use to send data - * to the remote peer. Corresponds to {@code FLAG_FIN}. - * @param in true to create an input stream that the remote peer can use to - * send data to us. Corresponds to {@code FLAG_UNIDIRECTIONAL}. - */ - public SpdyStream newStream(List requestHeaders, boolean out, boolean in) - throws IOException { - int flags = (out ? 0 : FLAG_FIN) | (in ? 0 : FLAG_UNIDIRECTIONAL); - int associatedStreamId = 0; // TODO: permit the caller to specify an associated stream? - int priority = 0; // TODO: permit the caller to specify a priority? - int slot = 0; // TODO: permit the caller to specify a slot? - SpdyStream stream; - int streamId; - - synchronized (spdyWriter) { - synchronized (this) { - if (shutdown) { - throw new IOException("shutdown"); - } - streamId = nextStreamId; - nextStreamId += 2; - stream = new SpdyStream(streamId, this, flags, priority, slot, requestHeaders, settings); - if (stream.isOpen()) { - streams.put(streamId, stream); - setIdle(false); - } - } - - spdyWriter.synStream(flags, streamId, associatedStreamId, priority, slot, requestHeaders); - } - - return stream; - } - - void writeSynReply(int streamId, int flags, List alternating) throws IOException { - spdyWriter.synReply(flags, streamId, alternating); - } - - /** Writes a complete data frame. */ - void writeFrame(byte[] bytes, int offset, int length) throws IOException { - synchronized (spdyWriter) { - spdyWriter.out.write(bytes, offset, length); - } - } - - void writeSynResetLater(final int streamId, final int statusCode) { - executor.submit( - new NamedRunnable(String.format("Spdy Writer %s stream %d", hostName, streamId)) { - @Override public void execute() { - try { - writeSynReset(streamId, statusCode); - } catch (IOException ignored) { - } - } - }); - } - - void writeSynReset(int streamId, int statusCode) throws IOException { - spdyWriter.rstStream(streamId, statusCode); - } - - void writeWindowUpdateLater(final int streamId, final int deltaWindowSize) { - executor.submit( - new NamedRunnable(String.format("Spdy Writer %s stream %d", hostName, streamId)) { - @Override public void execute() { - try { - writeWindowUpdate(streamId, deltaWindowSize); - } catch (IOException ignored) { - } - } - }); - } - - void writeWindowUpdate(int streamId, int deltaWindowSize) throws IOException { - spdyWriter.windowUpdate(streamId, deltaWindowSize); - } - - /** - * Sends a ping frame to the peer. Use the returned object to await the - * ping's response and observe its round trip time. - */ - public Ping ping() throws IOException { - Ping ping = new Ping(); - int pingId; - synchronized (this) { - if (shutdown) { - throw new IOException("shutdown"); - } - pingId = nextPingId; - nextPingId += 2; - if (pings == null) pings = new HashMap(); - pings.put(pingId, ping); - } - writePing(pingId, ping); - return ping; - } - - private void writePingLater(final int streamId, final Ping ping) { - executor.submit(new NamedRunnable(String.format("Spdy Writer %s ping %d", hostName, streamId)) { - @Override public void execute() { - try { - writePing(streamId, ping); - } catch (IOException ignored) { - } - } - }); - } - - private void writePing(int id, Ping ping) throws IOException { - synchronized (spdyWriter) { - // Observe the sent time immediately before performing I/O. - if (ping != null) ping.send(); - spdyWriter.ping(0, id); - } - } - - private synchronized Ping removePing(int id) { - return pings != null ? pings.remove(id) : null; - } - - /** Sends a noop frame to the peer. */ - public void noop() throws IOException { - spdyWriter.noop(); - } - - public void flush() throws IOException { - synchronized (spdyWriter) { - spdyWriter.out.flush(); - } - } - - /** - * Degrades this connection such that new streams can neither be created - * locally, nor accepted from the remote peer. Existing streams are not - * impacted. This is intended to permit an endpoint to gracefully stop - * accepting new requests without harming previously established streams. - * - * @param statusCode one of {@link #GOAWAY_OK}, {@link - * #GOAWAY_INTERNAL_ERROR} or {@link #GOAWAY_PROTOCOL_ERROR}. - */ - public void shutdown(int statusCode) throws IOException { - synchronized (spdyWriter) { - int lastGoodStreamId; - synchronized (this) { - if (shutdown) { - return; - } - shutdown = true; - lastGoodStreamId = this.lastGoodStreamId; - } - spdyWriter.goAway(0, lastGoodStreamId, statusCode); - } - } - - /** - * Closes this connection. This cancels all open streams and unanswered - * pings. It closes the underlying input and output streams and shuts down - * internal executor services. - */ - @Override public void close() throws IOException { - close(GOAWAY_OK, SpdyStream.RST_CANCEL); - } - - private void close(int shutdownStatusCode, int rstStatusCode) throws IOException { - assert (!Thread.holdsLock(this)); - IOException thrown = null; - try { - shutdown(shutdownStatusCode); - } catch (IOException e) { - thrown = e; - } - - SpdyStream[] streamsToClose = null; - Ping[] pingsToCancel = null; - synchronized (this) { - if (!streams.isEmpty()) { - streamsToClose = streams.values().toArray(new SpdyStream[streams.size()]); - streams.clear(); - setIdle(false); - } - if (pings != null) { - pingsToCancel = pings.values().toArray(new Ping[pings.size()]); - pings = null; - } - } - - if (streamsToClose != null) { - for (SpdyStream stream : streamsToClose) { - try { - stream.close(rstStatusCode); - } catch (IOException e) { - if (thrown != null) thrown = e; - } - } - } - - if (pingsToCancel != null) { - for (Ping ping : pingsToCancel) { - ping.cancel(); - } - } - - try { - spdyReader.close(); - } catch (IOException e) { - thrown = e; - } - try { - spdyWriter.close(); - } catch (IOException e) { - if (thrown == null) thrown = e; - } - - if (thrown != null) throw thrown; - } - - public static class Builder { - private String hostName; - private InputStream in; - private OutputStream out; - private IncomingStreamHandler handler = IncomingStreamHandler.REFUSE_INCOMING_STREAMS; - public boolean client; - - public Builder(boolean client, Socket socket) throws IOException { - this("", client, socket.getInputStream(), socket.getOutputStream()); - } - - public Builder(boolean client, InputStream in, OutputStream out) { - this("", client, in, out); - } - - /** - * @param client true if this peer initiated the connection; false if - * this peer accepted the connection. - */ - public Builder(String hostName, boolean client, Socket socket) throws IOException { - this(hostName, client, socket.getInputStream(), socket.getOutputStream()); - } - - /** - * @param client true if this peer initiated the connection; false if this - * peer accepted the connection. - */ - public Builder(String hostName, boolean client, InputStream in, OutputStream out) { - this.hostName = hostName; - this.client = client; - this.in = in; - this.out = out; - } - - public Builder handler(IncomingStreamHandler handler) { - this.handler = handler; - return this; - } - - public SpdyConnection build() { - return new SpdyConnection(this); - } - } - - private class Reader implements Runnable, SpdyReader.Handler { - @Override public void run() { - int shutdownStatusCode = GOAWAY_INTERNAL_ERROR; - int rstStatusCode = SpdyStream.RST_INTERNAL_ERROR; - try { - while (spdyReader.nextFrame(this)) { - } - shutdownStatusCode = GOAWAY_OK; - rstStatusCode = SpdyStream.RST_CANCEL; - } catch (IOException e) { - shutdownStatusCode = GOAWAY_PROTOCOL_ERROR; - rstStatusCode = SpdyStream.RST_PROTOCOL_ERROR; - } finally { - try { - close(shutdownStatusCode, rstStatusCode); - } catch (IOException ignored) { - } - } - } - - @Override public void data(int flags, int streamId, InputStream in, int length) - throws IOException { - SpdyStream dataStream = getStream(streamId); - if (dataStream == null) { - writeSynResetLater(streamId, SpdyStream.RST_INVALID_STREAM); - Util.skipByReading(in, length); - return; - } - dataStream.receiveData(in, length); - if ((flags & SpdyConnection.FLAG_FIN) != 0) { - dataStream.receiveFin(); - } - } - - @Override - public void synStream(int flags, int streamId, int associatedStreamId, int priority, int slot, - List nameValueBlock) { - final SpdyStream synStream; - final SpdyStream previous; - synchronized (SpdyConnection.this) { - synStream = - new SpdyStream(streamId, SpdyConnection.this, flags, priority, slot, nameValueBlock, - settings); - if (shutdown) { - return; - } - lastGoodStreamId = streamId; - previous = streams.put(streamId, synStream); - } - if (previous != null) { - previous.closeLater(SpdyStream.RST_PROTOCOL_ERROR); - removeStream(streamId); - return; - } - - executor.submit( - new NamedRunnable(String.format("Callback %s stream %d", hostName, streamId)) { - @Override public void execute() { - try { - handler.receive(synStream); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - }); - } - - @Override public void synReply(int flags, int streamId, List nameValueBlock) - throws IOException { - SpdyStream replyStream = getStream(streamId); - if (replyStream == null) { - writeSynResetLater(streamId, SpdyStream.RST_INVALID_STREAM); - return; - } - replyStream.receiveReply(nameValueBlock); - if ((flags & SpdyConnection.FLAG_FIN) != 0) { - replyStream.receiveFin(); - } - } - - @Override public void headers(int flags, int streamId, List nameValueBlock) - throws IOException { - SpdyStream replyStream = getStream(streamId); - if (replyStream != null) { - replyStream.receiveHeaders(nameValueBlock); - } - } - - @Override public void rstStream(int flags, int streamId, int statusCode) { - SpdyStream rstStream = removeStream(streamId); - if (rstStream != null) { - rstStream.receiveRstStream(statusCode); - } - } - - @Override public void settings(int flags, Settings newSettings) { - SpdyStream[] streamsToNotify = null; - synchronized (SpdyConnection.this) { - if (settings == null || (flags & Settings.FLAG_CLEAR_PREVIOUSLY_PERSISTED_SETTINGS) != 0) { - settings = newSettings; - } else { - settings.merge(newSettings); - } - if (!streams.isEmpty()) { - streamsToNotify = streams.values().toArray(new SpdyStream[streams.size()]); - } - } - if (streamsToNotify != null) { - for (SpdyStream stream : streamsToNotify) { - // The synchronization here is ugly. We need to synchronize on 'this' to guard - // reads to 'settings'. We synchronize on 'stream' to guard the state change. - // And we need to acquire the 'stream' lock first, since that may block. - synchronized (stream) { - synchronized (this) { - stream.receiveSettings(settings); - } - } - } - } - } - - @Override public void noop() { - } - - @Override public void ping(int flags, int streamId) { - if (client != (streamId % 2 == 1)) { - // Respond to a client ping if this is a server and vice versa. - writePingLater(streamId, null); - } else { - Ping ping = removePing(streamId); - if (ping != null) { - ping.receive(); - } - } - } - - @Override public void goAway(int flags, int lastGoodStreamId, int statusCode) { - synchronized (SpdyConnection.this) { - shutdown = true; - - // Fail all streams created after the last good stream ID. - for (Iterator> i = streams.entrySet().iterator(); - i.hasNext(); ) { - Map.Entry entry = i.next(); - int streamId = entry.getKey(); - if (streamId > lastGoodStreamId && entry.getValue().isLocallyInitiated()) { - entry.getValue().receiveRstStream(SpdyStream.RST_REFUSED_STREAM); - i.remove(); - } - } - } - } - - @Override public void windowUpdate(int flags, int streamId, int deltaWindowSize) { - SpdyStream stream = getStream(streamId); - if (stream != null) { - stream.receiveWindowUpdate(deltaWindowSize); - } - } - } -} diff --git a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/spdy/SpdyReader.java b/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/spdy/SpdyReader.java deleted file mode 100644 index 7d3f2bd56..000000000 --- a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/spdy/SpdyReader.java +++ /dev/null @@ -1,326 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed 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. - */ - -package com.squareup.okhttp.internal.spdy; - -import com.squareup.okhttp.internal.Util; -import java.io.Closeable; -import java.io.DataInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.UnsupportedEncodingException; -import java.net.ProtocolException; -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Logger; -import java.util.zip.DataFormatException; -import java.util.zip.Inflater; -import java.util.zip.InflaterInputStream; - -/** Read spdy/3 frames. */ -final class SpdyReader implements Closeable { - static final byte[] DICTIONARY; - static { - try { - DICTIONARY = ("\u0000\u0000\u0000\u0007options\u0000\u0000\u0000\u0004hea" - + "d\u0000\u0000\u0000\u0004post\u0000\u0000\u0000\u0003put\u0000\u0000\u0000\u0006dele" - + "te\u0000\u0000\u0000\u0005trace\u0000\u0000\u0000\u0006accept\u0000\u0000\u0000" - + "\u000Eaccept-charset\u0000\u0000\u0000\u000Faccept-encoding\u0000\u0000\u0000\u000Fa" - + "ccept-language\u0000\u0000\u0000\raccept-ranges\u0000\u0000\u0000\u0003age\u0000" - + "\u0000\u0000\u0005allow\u0000\u0000\u0000\rauthorization\u0000\u0000\u0000\rcache-co" - + "ntrol\u0000\u0000\u0000\nconnection\u0000\u0000\u0000\fcontent-base\u0000\u0000" - + "\u0000\u0010content-encoding\u0000\u0000\u0000\u0010content-language\u0000\u0000" - + "\u0000\u000Econtent-length\u0000\u0000\u0000\u0010content-location\u0000\u0000\u0000" - + "\u000Bcontent-md5\u0000\u0000\u0000\rcontent-range\u0000\u0000\u0000\fcontent-type" - + "\u0000\u0000\u0000\u0004date\u0000\u0000\u0000\u0004etag\u0000\u0000\u0000\u0006expe" - + "ct\u0000\u0000\u0000\u0007expires\u0000\u0000\u0000\u0004from\u0000\u0000\u0000" - + "\u0004host\u0000\u0000\u0000\bif-match\u0000\u0000\u0000\u0011if-modified-since" - + "\u0000\u0000\u0000\rif-none-match\u0000\u0000\u0000\bif-range\u0000\u0000\u0000" - + "\u0013if-unmodified-since\u0000\u0000\u0000\rlast-modified\u0000\u0000\u0000\blocati" - + "on\u0000\u0000\u0000\fmax-forwards\u0000\u0000\u0000\u0006pragma\u0000\u0000\u0000" - + "\u0012proxy-authenticate\u0000\u0000\u0000\u0013proxy-authorization\u0000\u0000" - + "\u0000\u0005range\u0000\u0000\u0000\u0007referer\u0000\u0000\u0000\u000Bretry-after" - + "\u0000\u0000\u0000\u0006server\u0000\u0000\u0000\u0002te\u0000\u0000\u0000\u0007trai" - + "ler\u0000\u0000\u0000\u0011transfer-encoding\u0000\u0000\u0000\u0007upgrade\u0000" - + "\u0000\u0000\nuser-agent\u0000\u0000\u0000\u0004vary\u0000\u0000\u0000\u0003via" - + "\u0000\u0000\u0000\u0007warning\u0000\u0000\u0000\u0010www-authenticate\u0000\u0000" - + "\u0000\u0006method\u0000\u0000\u0000\u0003get\u0000\u0000\u0000\u0006status\u0000" - + "\u0000\u0000\u0006200 OK\u0000\u0000\u0000\u0007version\u0000\u0000\u0000\bHTTP/1.1" - + "\u0000\u0000\u0000\u0003url\u0000\u0000\u0000\u0006public\u0000\u0000\u0000\nset-coo" - + "kie\u0000\u0000\u0000\nkeep-alive\u0000\u0000\u0000\u0006origin100101201202205206300" - + "302303304305306307402405406407408409410411412413414415416417502504505203 Non-Authori" - + "tative Information204 No Content301 Moved Permanently400 Bad Request401 Unauthorized" - + "403 Forbidden404 Not Found500 Internal Server Error501 Not Implemented503 Service Un" - + "availableJan Feb Mar Apr May Jun Jul Aug Sept Oct Nov Dec 00:00:00 Mon, Tue, Wed, Th" - + "u, Fri, Sat, Sun, GMTchunked,text/html,image/png,image/jpg,image/gif,application/xml" - + ",application/xhtml+xml,text/plain,text/javascript,publicprivatemax-age=gzip,deflate," - + "sdchcharset=utf-8charset=iso-8859-1,utf-,*,enq=0.").getBytes(Util.UTF_8.name()); - } catch (UnsupportedEncodingException e) { - throw new AssertionError(); - } - } - - private final DataInputStream in; - private final DataInputStream nameValueBlockIn; - private int compressedLimit; - - SpdyReader(InputStream in) { - this.in = new DataInputStream(in); - this.nameValueBlockIn = newNameValueBlockStream(); - } - - /** - * Send the next frame to {@code handler}. Returns true unless there are no - * more frames on the stream. - */ - public boolean nextFrame(Handler handler) throws IOException { - int w1; - try { - w1 = in.readInt(); - } catch (IOException e) { - return false; // This might be a normal socket close. - } - int w2 = in.readInt(); - - boolean control = (w1 & 0x80000000) != 0; - int flags = (w2 & 0xff000000) >>> 24; - int length = (w2 & 0xffffff); - - if (control) { - int version = (w1 & 0x7fff0000) >>> 16; - int type = (w1 & 0xffff); - - if (version != 3) { - throw new ProtocolException("version != 3: " + version); - } - - switch (type) { - case SpdyConnection.TYPE_SYN_STREAM: - readSynStream(handler, flags, length); - return true; - - case SpdyConnection.TYPE_SYN_REPLY: - readSynReply(handler, flags, length); - return true; - - case SpdyConnection.TYPE_RST_STREAM: - readRstStream(handler, flags, length); - return true; - - case SpdyConnection.TYPE_SETTINGS: - readSettings(handler, flags, length); - return true; - - case SpdyConnection.TYPE_NOOP: - if (length != 0) throw ioException("TYPE_NOOP length: %d != 0", length); - handler.noop(); - return true; - - case SpdyConnection.TYPE_PING: - readPing(handler, flags, length); - return true; - - case SpdyConnection.TYPE_GOAWAY: - readGoAway(handler, flags, length); - return true; - - case SpdyConnection.TYPE_HEADERS: - readHeaders(handler, flags, length); - return true; - - case SpdyConnection.TYPE_WINDOW_UPDATE: - readWindowUpdate(handler, flags, length); - return true; - - case SpdyConnection.TYPE_CREDENTIAL: - Util.skipByReading(in, length); - throw new UnsupportedOperationException("TODO"); // TODO: implement - - default: - throw new IOException("Unexpected frame"); - } - } else { - int streamId = w1 & 0x7fffffff; - handler.data(flags, streamId, in, length); - return true; - } - } - - private void readSynStream(Handler handler, int flags, int length) throws IOException { - int w1 = in.readInt(); - int w2 = in.readInt(); - int s3 = in.readShort(); - int streamId = w1 & 0x7fffffff; - int associatedStreamId = w2 & 0x7fffffff; - int priority = (s3 & 0xe000) >>> 13; - int slot = s3 & 0xff; - List nameValueBlock = readNameValueBlock(length - 10); - handler.synStream(flags, streamId, associatedStreamId, priority, slot, nameValueBlock); - } - - private void readSynReply(Handler handler, int flags, int length) throws IOException { - int w1 = in.readInt(); - int streamId = w1 & 0x7fffffff; - List nameValueBlock = readNameValueBlock(length - 4); - handler.synReply(flags, streamId, nameValueBlock); - } - - private void readRstStream(Handler handler, int flags, int length) throws IOException { - if (length != 8) throw ioException("TYPE_RST_STREAM length: %d != 8", length); - int streamId = in.readInt() & 0x7fffffff; - int statusCode = in.readInt(); - handler.rstStream(flags, streamId, statusCode); - } - - private void readHeaders(Handler handler, int flags, int length) throws IOException { - int w1 = in.readInt(); - int streamId = w1 & 0x7fffffff; - List nameValueBlock = readNameValueBlock(length - 4); - handler.headers(flags, streamId, nameValueBlock); - } - - private void readWindowUpdate(Handler handler, int flags, int length) throws IOException { - if (length != 8) throw ioException("TYPE_WINDOW_UPDATE length: %d != 8", length); - int w1 = in.readInt(); - int w2 = in.readInt(); - int streamId = w1 & 0x7fffffff; - int deltaWindowSize = w2 & 0x7fffffff; - handler.windowUpdate(flags, streamId, deltaWindowSize); - } - - private DataInputStream newNameValueBlockStream() { - // Limit the inflater input stream to only those bytes in the Name/Value block. - final InputStream throttleStream = new InputStream() { - @Override public int read() throws IOException { - return Util.readSingleByte(this); - } - - @Override public int read(byte[] buffer, int offset, int byteCount) throws IOException { - byteCount = Math.min(byteCount, compressedLimit); - int consumed = in.read(buffer, offset, byteCount); - compressedLimit -= consumed; - return consumed; - } - - @Override public void close() throws IOException { - in.close(); - } - }; - - // Subclass inflater to install a dictionary when it's needed. - Inflater inflater = new Inflater() { - @Override - public int inflate(byte[] buffer, int offset, int count) throws DataFormatException { - int result = super.inflate(buffer, offset, count); - if (result == 0 && needsDictionary()) { - setDictionary(DICTIONARY); - result = super.inflate(buffer, offset, count); - } - return result; - } - }; - - return new DataInputStream(new InflaterInputStream(throttleStream, inflater)); - } - - private List readNameValueBlock(int length) throws IOException { - this.compressedLimit += length; - try { - int numberOfPairs = nameValueBlockIn.readInt(); - if (numberOfPairs < 0) { - Logger.getLogger(getClass().getName()).warning("numberOfPairs < 0: " + numberOfPairs); - throw ioException("numberOfPairs < 0"); - } - List entries = new ArrayList(numberOfPairs * 2); - for (int i = 0; i < numberOfPairs; i++) { - String name = readString(); - String values = readString(); - if (name.length() == 0) throw ioException("name.length == 0"); - if (values.length() == 0) throw ioException("values.length == 0"); - entries.add(name); - entries.add(values); - } - - if (compressedLimit != 0) { - Logger.getLogger(getClass().getName()).warning("compressedLimit > 0: " + compressedLimit); - } - - return entries; - } catch (DataFormatException e) { - throw new IOException(e.getMessage()); - } - } - - private String readString() throws DataFormatException, IOException { - int length = nameValueBlockIn.readInt(); - byte[] bytes = new byte[length]; - Util.readFully(nameValueBlockIn, bytes); - return new String(bytes, 0, length, "UTF-8"); - } - - private void readPing(Handler handler, int flags, int length) throws IOException { - if (length != 4) throw ioException("TYPE_PING length: %d != 4", length); - int id = in.readInt(); - handler.ping(flags, id); - } - - private void readGoAway(Handler handler, int flags, int length) throws IOException { - if (length != 8) throw ioException("TYPE_GOAWAY length: %d != 8", length); - int lastGoodStreamId = in.readInt() & 0x7fffffff; - int statusCode = in.readInt(); - handler.goAway(flags, lastGoodStreamId, statusCode); - } - - private void readSettings(Handler handler, int flags, int length) throws IOException { - int numberOfEntries = in.readInt(); - if (length != 4 + 8 * numberOfEntries) { - throw ioException("TYPE_SETTINGS length: %d != 4 + 8 * %d", length, numberOfEntries); - } - Settings settings = new Settings(); - for (int i = 0; i < numberOfEntries; i++) { - int w1 = in.readInt(); - int value = in.readInt(); - int idFlags = (w1 & 0xff000000) >>> 24; - int id = w1 & 0xffffff; - settings.set(id, idFlags, value); - } - handler.settings(flags, settings); - } - - private static IOException ioException(String message, Object... args) throws IOException { - throw new IOException(String.format(message, args)); - } - - @Override public void close() throws IOException { - Util.closeAll(in, nameValueBlockIn); - } - - public interface Handler { - void data(int flags, int streamId, InputStream in, int length) throws IOException; - - void synStream(int flags, int streamId, int associatedStreamId, int priority, int slot, - List nameValueBlock); - - void synReply(int flags, int streamId, List nameValueBlock) throws IOException; - void headers(int flags, int streamId, List nameValueBlock) throws IOException; - void rstStream(int flags, int streamId, int statusCode); - void settings(int flags, Settings settings); - void noop(); - void ping(int flags, int streamId); - void goAway(int flags, int lastGoodStreamId, int statusCode); - void windowUpdate(int flags, int streamId, int deltaWindowSize); - } -} diff --git a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/spdy/SpdyStream.java b/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/spdy/SpdyStream.java deleted file mode 100644 index 744a04eaa..000000000 --- a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/spdy/SpdyStream.java +++ /dev/null @@ -1,733 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed 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. - */ - -package com.squareup.okhttp.internal.spdy; - -import com.squareup.okhttp.internal.Util; -import java.io.IOException; -import java.io.InputStream; -import java.io.InterruptedIOException; -import java.io.OutputStream; -import java.net.SocketTimeoutException; -import java.util.ArrayList; -import java.util.List; - -import static com.squareup.okhttp.internal.Util.checkOffsetAndCount; -import static com.squareup.okhttp.internal.Util.pokeInt; -import static java.nio.ByteOrder.BIG_ENDIAN; - -/** A logical bidirectional stream. */ -public final class SpdyStream { - - // Internal state is guarded by this. No long-running or potentially - // blocking operations are performed while the lock is held. - - private static final int DATA_FRAME_HEADER_LENGTH = 8; - - private static final String[] STATUS_CODE_NAMES = { - null, - "PROTOCOL_ERROR", - "INVALID_STREAM", - "REFUSED_STREAM", - "UNSUPPORTED_VERSION", - "CANCEL", - "INTERNAL_ERROR", - "FLOW_CONTROL_ERROR", - "STREAM_IN_USE", - "STREAM_ALREADY_CLOSED", - "INVALID_CREDENTIALS", - "FRAME_TOO_LARGE" - }; - - public static final int RST_PROTOCOL_ERROR = 1; - public static final int RST_INVALID_STREAM = 2; - public static final int RST_REFUSED_STREAM = 3; - public static final int RST_UNSUPPORTED_VERSION = 4; - public static final int RST_CANCEL = 5; - public static final int RST_INTERNAL_ERROR = 6; - public static final int RST_FLOW_CONTROL_ERROR = 7; - public static final int RST_STREAM_IN_USE = 8; - public static final int RST_STREAM_ALREADY_CLOSED = 9; - public static final int RST_INVALID_CREDENTIALS = 10; - public static final int RST_FRAME_TOO_LARGE = 11; - - /** - * The number of unacknowledged bytes at which the input stream will send - * the peer a {@code WINDOW_UPDATE} frame. Must be less than this client's - * window size, otherwise the remote peer will stop sending data on this - * stream. (Chrome 25 uses 5 MiB.) - */ - public static final int WINDOW_UPDATE_THRESHOLD = Settings.DEFAULT_INITIAL_WINDOW_SIZE / 2; - - private final int id; - private final SpdyConnection connection; - private final int priority; - private final int slot; - private long readTimeoutMillis = 0; - private int writeWindowSize; - - /** Headers sent by the stream initiator. Immutable and non null. */ - private final List requestHeaders; - - /** Headers sent in the stream reply. Null if reply is either not sent or not sent yet. */ - private List responseHeaders; - - private final SpdyDataInputStream in = new SpdyDataInputStream(); - private final SpdyDataOutputStream out = new SpdyDataOutputStream(); - - /** - * The reason why this stream was abnormally closed. If there are multiple - * reasons to abnormally close this stream (such as both peers closing it - * near-simultaneously) then this is the first reason known to this peer. - */ - private int rstStatusCode = -1; - - SpdyStream(int id, SpdyConnection connection, int flags, int priority, int slot, - List requestHeaders, Settings settings) { - if (connection == null) throw new NullPointerException("connection == null"); - if (requestHeaders == null) throw new NullPointerException("requestHeaders == null"); - this.id = id; - this.connection = connection; - this.priority = priority; - this.slot = slot; - this.requestHeaders = requestHeaders; - - if (isLocallyInitiated()) { - // I am the sender - in.finished = (flags & SpdyConnection.FLAG_UNIDIRECTIONAL) != 0; - out.finished = (flags & SpdyConnection.FLAG_FIN) != 0; - } else { - // I am the receiver - in.finished = (flags & SpdyConnection.FLAG_FIN) != 0; - out.finished = (flags & SpdyConnection.FLAG_UNIDIRECTIONAL) != 0; - } - - setSettings(settings); - } - - /** - * Returns true if this stream is open. A stream is open until either: - *

    - *
  • A {@code SYN_RESET} frame abnormally terminates the stream. - *
  • Both input and output streams have transmitted all data and - * headers. - *
- * Note that the input stream may continue to yield data even after a stream - * reports itself as not open. This is because input data is buffered. - */ - public synchronized boolean isOpen() { - if (rstStatusCode != -1) { - return false; - } - if ((in.finished || in.closed) && (out.finished || out.closed) && responseHeaders != null) { - return false; - } - return true; - } - - /** Returns true if this stream was created by this peer. */ - public boolean isLocallyInitiated() { - boolean streamIsClient = (id % 2 == 1); - return connection.client == streamIsClient; - } - - public SpdyConnection getConnection() { - return connection; - } - - public List getRequestHeaders() { - return requestHeaders; - } - - /** - * Returns the stream's response headers, blocking if necessary if they - * have not been received yet. - */ - public synchronized List getResponseHeaders() throws IOException { - try { - while (responseHeaders == null && rstStatusCode == -1) { - wait(); - } - if (responseHeaders != null) { - return responseHeaders; - } - throw new IOException("stream was reset: " + rstStatusString()); - } catch (InterruptedException e) { - InterruptedIOException rethrow = new InterruptedIOException(); - rethrow.initCause(e); - throw rethrow; - } - } - - /** - * Returns the reason why this stream was closed, or -1 if it closed - * normally or has not yet been closed. Valid reasons are {@link - * #RST_PROTOCOL_ERROR}, {@link #RST_INVALID_STREAM}, {@link - * #RST_REFUSED_STREAM}, {@link #RST_UNSUPPORTED_VERSION}, {@link - * #RST_CANCEL}, {@link #RST_INTERNAL_ERROR} and {@link - * #RST_FLOW_CONTROL_ERROR}. - */ - public synchronized int getRstStatusCode() { - return rstStatusCode; - } - - /** - * Sends a reply to an incoming stream. - * - * @param out true to create an output stream that we can use to send data - * to the remote peer. Corresponds to {@code FLAG_FIN}. - */ - public void reply(List responseHeaders, boolean out) throws IOException { - assert (!Thread.holdsLock(SpdyStream.this)); - int flags = 0; - synchronized (this) { - if (responseHeaders == null) { - throw new NullPointerException("responseHeaders == null"); - } - if (isLocallyInitiated()) { - throw new IllegalStateException("cannot reply to a locally initiated stream"); - } - if (this.responseHeaders != null) { - throw new IllegalStateException("reply already sent"); - } - this.responseHeaders = responseHeaders; - if (!out) { - this.out.finished = true; - flags |= SpdyConnection.FLAG_FIN; - } - } - connection.writeSynReply(id, flags, responseHeaders); - } - - /** - * Sets the maximum time to wait on input stream reads before failing with a - * {@code SocketTimeoutException}, or {@code 0} to wait indefinitely. - */ - public void setReadTimeout(long readTimeoutMillis) { - this.readTimeoutMillis = readTimeoutMillis; - } - - public long getReadTimeoutMillis() { - return readTimeoutMillis; - } - - /** Returns an input stream that can be used to read data from the peer. */ - public InputStream getInputStream() { - return in; - } - - /** - * Returns an output stream that can be used to write data to the peer. - * - * @throws IllegalStateException if this stream was initiated by the peer - * and a {@link #reply} has not yet been sent. - */ - public OutputStream getOutputStream() { - synchronized (this) { - if (responseHeaders == null && !isLocallyInitiated()) { - throw new IllegalStateException("reply before requesting the output stream"); - } - } - return out; - } - - /** - * Abnormally terminate this stream. This blocks until the {@code RST_STREAM} - * frame has been transmitted. - */ - public void close(int rstStatusCode) throws IOException { - if (!closeInternal(rstStatusCode)) { - return; // Already closed. - } - connection.writeSynReset(id, rstStatusCode); - } - - /** - * Abnormally terminate this stream. This enqueues a {@code RST_STREAM} - * frame and returns immediately. - */ - public void closeLater(int rstStatusCode) { - if (!closeInternal(rstStatusCode)) { - return; // Already closed. - } - connection.writeSynResetLater(id, rstStatusCode); - } - - /** Returns true if this stream was closed. */ - private boolean closeInternal(int rstStatusCode) { - assert (!Thread.holdsLock(this)); - synchronized (this) { - if (this.rstStatusCode != -1) { - return false; - } - if (in.finished && out.finished) { - return false; - } - this.rstStatusCode = rstStatusCode; - notifyAll(); - } - connection.removeStream(id); - return true; - } - - void receiveReply(List strings) throws IOException { - assert (!Thread.holdsLock(SpdyStream.this)); - boolean streamInUseError = false; - boolean open = true; - synchronized (this) { - if (isLocallyInitiated() && responseHeaders == null) { - responseHeaders = strings; - open = isOpen(); - notifyAll(); - } else { - streamInUseError = true; - } - } - if (streamInUseError) { - closeLater(SpdyStream.RST_STREAM_IN_USE); - } else if (!open) { - connection.removeStream(id); - } - } - - void receiveHeaders(List headers) throws IOException { - assert (!Thread.holdsLock(SpdyStream.this)); - boolean protocolError = false; - synchronized (this) { - if (responseHeaders != null) { - List newHeaders = new ArrayList(); - newHeaders.addAll(responseHeaders); - newHeaders.addAll(headers); - this.responseHeaders = newHeaders; - } else { - protocolError = true; - } - } - if (protocolError) { - closeLater(SpdyStream.RST_PROTOCOL_ERROR); - } - } - - void receiveData(InputStream in, int length) throws IOException { - assert (!Thread.holdsLock(SpdyStream.this)); - this.in.receive(in, length); - } - - void receiveFin() { - assert (!Thread.holdsLock(SpdyStream.this)); - boolean open; - synchronized (this) { - this.in.finished = true; - open = isOpen(); - notifyAll(); - } - if (!open) { - connection.removeStream(id); - } - } - - synchronized void receiveRstStream(int statusCode) { - if (rstStatusCode == -1) { - rstStatusCode = statusCode; - notifyAll(); - } - } - - private void setSettings(Settings settings) { - assert (Thread.holdsLock(connection)); // Because 'settings' is guarded by 'connection'. - this.writeWindowSize = - settings != null ? settings.getInitialWindowSize(Settings.DEFAULT_INITIAL_WINDOW_SIZE) - : Settings.DEFAULT_INITIAL_WINDOW_SIZE; - } - - void receiveSettings(Settings settings) { - assert (Thread.holdsLock(this)); - setSettings(settings); - notifyAll(); - } - - synchronized void receiveWindowUpdate(int deltaWindowSize) { - out.unacknowledgedBytes -= deltaWindowSize; - notifyAll(); - } - - private String rstStatusString() { - return rstStatusCode > 0 && rstStatusCode < STATUS_CODE_NAMES.length - ? STATUS_CODE_NAMES[rstStatusCode] : Integer.toString(rstStatusCode); - } - - int getPriority() { - return priority; - } - - int getSlot() { - return slot; - } - - /** - * An input stream that reads the incoming data frames of a stream. Although - * this class uses synchronization to safely receive incoming data frames, - * it is not intended for use by multiple readers. - */ - private final class SpdyDataInputStream extends InputStream { - // Store incoming data bytes in a circular buffer. When the buffer is - // empty, pos == -1. Otherwise pos is the first byte to read and limit - // is the first byte to write. - // - // { - - - X X X X - - - } - // ^ ^ - // pos limit - // - // { X X X - - - - X X X } - // ^ ^ - // limit pos - - private final byte[] buffer = new byte[Settings.DEFAULT_INITIAL_WINDOW_SIZE]; - - /** the next byte to be read, or -1 if the buffer is empty. Never buffer.length */ - private int pos = -1; - - /** the last byte to be read. Never buffer.length */ - private int limit; - - /** True if the caller has closed this stream. */ - private boolean closed; - - /** - * True if either side has cleanly shut down this stream. We will - * receive no more bytes beyond those already in the buffer. - */ - private boolean finished; - - /** - * The total number of bytes consumed by the application (with {@link - * #read}), but not yet acknowledged by sending a {@code WINDOW_UPDATE} - * frame. - */ - private int unacknowledgedBytes = 0; - - @Override public int available() throws IOException { - synchronized (SpdyStream.this) { - checkNotClosed(); - if (pos == -1) { - return 0; - } else if (limit > pos) { - return limit - pos; - } else { - return limit + (buffer.length - pos); - } - } - } - - @Override public int read() throws IOException { - return Util.readSingleByte(this); - } - - @Override public int read(byte[] b, int offset, int count) throws IOException { - synchronized (SpdyStream.this) { - checkOffsetAndCount(b.length, offset, count); - waitUntilReadable(); - checkNotClosed(); - - if (pos == -1) { - return -1; - } - - int copied = 0; - - // drain from [pos..buffer.length) - if (limit <= pos) { - int bytesToCopy = Math.min(count, buffer.length - pos); - System.arraycopy(buffer, pos, b, offset, bytesToCopy); - pos += bytesToCopy; - copied += bytesToCopy; - if (pos == buffer.length) { - pos = 0; - } - } - - // drain from [pos..limit) - if (copied < count) { - int bytesToCopy = Math.min(limit - pos, count - copied); - System.arraycopy(buffer, pos, b, offset + copied, bytesToCopy); - pos += bytesToCopy; - copied += bytesToCopy; - } - - // Flow control: notify the peer that we're ready for more data! - unacknowledgedBytes += copied; - if (unacknowledgedBytes >= WINDOW_UPDATE_THRESHOLD) { - connection.writeWindowUpdateLater(id, unacknowledgedBytes); - unacknowledgedBytes = 0; - } - - if (pos == limit) { - pos = -1; - limit = 0; - } - - return copied; - } - } - - /** - * Returns once the input stream is either readable or finished. Throws - * a {@link SocketTimeoutException} if the read timeout elapses before - * that happens. - */ - private void waitUntilReadable() throws IOException { - long start = 0; - long remaining = 0; - if (readTimeoutMillis != 0) { - start = (System.nanoTime() / 1000000); - remaining = readTimeoutMillis; - } - try { - while (pos == -1 && !finished && !closed && rstStatusCode == -1) { - if (readTimeoutMillis == 0) { - SpdyStream.this.wait(); - } else if (remaining > 0) { - SpdyStream.this.wait(remaining); - remaining = start + readTimeoutMillis - (System.nanoTime() / 1000000); - } else { - throw new SocketTimeoutException(); - } - } - } catch (InterruptedException e) { - throw new InterruptedIOException(); - } - } - - void receive(InputStream in, int byteCount) throws IOException { - assert (!Thread.holdsLock(SpdyStream.this)); - - if (byteCount == 0) { - return; - } - - int pos; - int limit; - int firstNewByte; - boolean finished; - boolean flowControlError; - synchronized (SpdyStream.this) { - finished = this.finished; - pos = this.pos; - firstNewByte = this.limit; - limit = this.limit; - flowControlError = byteCount > buffer.length - available(); - } - - // If the peer sends more data than we can handle, discard it and close the connection. - if (flowControlError) { - Util.skipByReading(in, byteCount); - closeLater(SpdyStream.RST_FLOW_CONTROL_ERROR); - return; - } - - // Discard data received after the stream is finished. It's probably a benign race. - if (finished) { - Util.skipByReading(in, byteCount); - return; - } - - // Fill the buffer without holding any locks. First fill [limit..buffer.length) if that - // won't overwrite unread data. Then fill [limit..pos). We can't hold a lock, otherwise - // writes will be blocked until reads complete. - if (pos < limit) { - int firstCopyCount = Math.min(byteCount, buffer.length - limit); - Util.readFully(in, buffer, limit, firstCopyCount); - limit += firstCopyCount; - byteCount -= firstCopyCount; - if (limit == buffer.length) { - limit = 0; - } - } - if (byteCount > 0) { - Util.readFully(in, buffer, limit, byteCount); - limit += byteCount; - } - - synchronized (SpdyStream.this) { - // Update the new limit, and mark the position as readable if necessary. - this.limit = limit; - if (this.pos == -1) { - this.pos = firstNewByte; - SpdyStream.this.notifyAll(); - } - } - } - - @Override public void close() throws IOException { - synchronized (SpdyStream.this) { - closed = true; - SpdyStream.this.notifyAll(); - } - cancelStreamIfNecessary(); - } - - private void checkNotClosed() throws IOException { - if (closed) { - throw new IOException("stream closed"); - } - if (rstStatusCode != -1) { - throw new IOException("stream was reset: " + rstStatusString()); - } - } - } - - private void cancelStreamIfNecessary() throws IOException { - assert (!Thread.holdsLock(SpdyStream.this)); - boolean open; - boolean cancel; - synchronized (this) { - cancel = !in.finished && in.closed && (out.finished || out.closed); - open = isOpen(); - } - if (cancel) { - // RST this stream to prevent additional data from being sent. This - // is safe because the input stream is closed (we won't use any - // further bytes) and the output stream is either finished or closed - // (so RSTing both streams doesn't cause harm). - SpdyStream.this.close(RST_CANCEL); - } else if (!open) { - connection.removeStream(id); - } - } - - /** - * An output stream that writes outgoing data frames of a stream. This class - * is not thread safe. - */ - private final class SpdyDataOutputStream extends OutputStream { - private final byte[] buffer = new byte[8192]; - private int pos = DATA_FRAME_HEADER_LENGTH; - - /** True if the caller has closed this stream. */ - private boolean closed; - - /** - * True if either side has cleanly shut down this stream. We shall send - * no more bytes. - */ - private boolean finished; - - /** - * The total number of bytes written out to the peer, but not yet - * acknowledged with an incoming {@code WINDOW_UPDATE} frame. Writes - * block if they cause this to exceed the {@code WINDOW_SIZE}. - */ - private int unacknowledgedBytes = 0; - - @Override public void write(int b) throws IOException { - Util.writeSingleByte(this, b); - } - - @Override public void write(byte[] bytes, int offset, int count) throws IOException { - assert (!Thread.holdsLock(SpdyStream.this)); - checkOffsetAndCount(bytes.length, offset, count); - checkNotClosed(); - - while (count > 0) { - if (pos == buffer.length) { - writeFrame(false); - } - int bytesToCopy = Math.min(count, buffer.length - pos); - System.arraycopy(bytes, offset, buffer, pos, bytesToCopy); - pos += bytesToCopy; - offset += bytesToCopy; - count -= bytesToCopy; - } - } - - @Override public void flush() throws IOException { - assert (!Thread.holdsLock(SpdyStream.this)); - checkNotClosed(); - if (pos > DATA_FRAME_HEADER_LENGTH) { - writeFrame(false); - connection.flush(); - } - } - - @Override public void close() throws IOException { - assert (!Thread.holdsLock(SpdyStream.this)); - synchronized (SpdyStream.this) { - if (closed) { - return; - } - closed = true; - } - writeFrame(true); - connection.flush(); - cancelStreamIfNecessary(); - } - - private void writeFrame(boolean last) throws IOException { - assert (!Thread.holdsLock(SpdyStream.this)); - - int length = pos - DATA_FRAME_HEADER_LENGTH; - synchronized (SpdyStream.this) { - waitUntilWritable(length, last); - unacknowledgedBytes += length; - } - int flags = 0; - if (last) { - flags |= SpdyConnection.FLAG_FIN; - } - pokeInt(buffer, 0, id & 0x7fffffff, BIG_ENDIAN); - pokeInt(buffer, 4, (flags & 0xff) << 24 | length & 0xffffff, BIG_ENDIAN); - connection.writeFrame(buffer, 0, pos); - pos = DATA_FRAME_HEADER_LENGTH; - } - - /** - * Returns once the peer is ready to receive {@code count} bytes. - * - * @throws IOException if the stream was finished or closed, or the - * thread was interrupted. - */ - private void waitUntilWritable(int count, boolean last) throws IOException { - try { - while (unacknowledgedBytes + count >= writeWindowSize) { - SpdyStream.this.wait(); // Wait until we receive a WINDOW_UPDATE. - - // The stream may have been closed or reset while we were waiting! - if (!last && closed) { - throw new IOException("stream closed"); - } else if (finished) { - throw new IOException("stream finished"); - } else if (rstStatusCode != -1) { - throw new IOException("stream was reset: " + rstStatusString()); - } - } - } catch (InterruptedException e) { - throw new InterruptedIOException(); - } - } - - private void checkNotClosed() throws IOException { - synchronized (SpdyStream.this) { - if (closed) { - throw new IOException("stream closed"); - } else if (finished) { - throw new IOException("stream finished"); - } else if (rstStatusCode != -1) { - throw new IOException("stream was reset: " + rstStatusString()); - } - } - } - } -} diff --git a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/spdy/SpdyWriter.java b/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/spdy/SpdyWriter.java deleted file mode 100644 index b3d1d1f9a..000000000 --- a/platforms/android/CordovaLib/src/com/squareup/okhttp/internal/spdy/SpdyWriter.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed 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. - */ - -package com.squareup.okhttp.internal.spdy; - -import com.squareup.okhttp.internal.Platform; -import com.squareup.okhttp.internal.Util; -import java.io.ByteArrayOutputStream; -import java.io.Closeable; -import java.io.DataOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.util.List; -import java.util.zip.Deflater; - -/** Write spdy/3 frames. */ -final class SpdyWriter implements Closeable { - final DataOutputStream out; - private final ByteArrayOutputStream nameValueBlockBuffer; - private final DataOutputStream nameValueBlockOut; - - SpdyWriter(OutputStream out) { - this.out = new DataOutputStream(out); - - Deflater deflater = new Deflater(); - deflater.setDictionary(SpdyReader.DICTIONARY); - nameValueBlockBuffer = new ByteArrayOutputStream(); - nameValueBlockOut = new DataOutputStream( - Platform.get().newDeflaterOutputStream(nameValueBlockBuffer, deflater, true)); - } - - public synchronized void synStream(int flags, int streamId, int associatedStreamId, int priority, - int slot, List nameValueBlock) throws IOException { - writeNameValueBlockToBuffer(nameValueBlock); - int length = 10 + nameValueBlockBuffer.size(); - int type = SpdyConnection.TYPE_SYN_STREAM; - - int unused = 0; - out.writeInt(0x80000000 | (SpdyConnection.VERSION & 0x7fff) << 16 | type & 0xffff); - out.writeInt((flags & 0xff) << 24 | length & 0xffffff); - out.writeInt(streamId & 0x7fffffff); - out.writeInt(associatedStreamId & 0x7fffffff); - out.writeShort((priority & 0x7) << 13 | (unused & 0x1f) << 8 | (slot & 0xff)); - nameValueBlockBuffer.writeTo(out); - out.flush(); - } - - public synchronized void synReply(int flags, int streamId, List nameValueBlock) - throws IOException { - writeNameValueBlockToBuffer(nameValueBlock); - int type = SpdyConnection.TYPE_SYN_REPLY; - int length = nameValueBlockBuffer.size() + 4; - - out.writeInt(0x80000000 | (SpdyConnection.VERSION & 0x7fff) << 16 | type & 0xffff); - out.writeInt((flags & 0xff) << 24 | length & 0xffffff); - out.writeInt(streamId & 0x7fffffff); - nameValueBlockBuffer.writeTo(out); - out.flush(); - } - - public synchronized void headers(int flags, int streamId, List nameValueBlock) - throws IOException { - writeNameValueBlockToBuffer(nameValueBlock); - int type = SpdyConnection.TYPE_HEADERS; - int length = nameValueBlockBuffer.size() + 4; - - out.writeInt(0x80000000 | (SpdyConnection.VERSION & 0x7fff) << 16 | type & 0xffff); - out.writeInt((flags & 0xff) << 24 | length & 0xffffff); - out.writeInt(streamId & 0x7fffffff); - nameValueBlockBuffer.writeTo(out); - out.flush(); - } - - public synchronized void rstStream(int streamId, int statusCode) throws IOException { - int flags = 0; - int type = SpdyConnection.TYPE_RST_STREAM; - int length = 8; - out.writeInt(0x80000000 | (SpdyConnection.VERSION & 0x7fff) << 16 | type & 0xffff); - out.writeInt((flags & 0xff) << 24 | length & 0xffffff); - out.writeInt(streamId & 0x7fffffff); - out.writeInt(statusCode); - out.flush(); - } - - public synchronized void data(int flags, int streamId, byte[] data) throws IOException { - int length = data.length; - out.writeInt(streamId & 0x7fffffff); - out.writeInt((flags & 0xff) << 24 | length & 0xffffff); - out.write(data); - out.flush(); - } - - private void writeNameValueBlockToBuffer(List nameValueBlock) throws IOException { - nameValueBlockBuffer.reset(); - int numberOfPairs = nameValueBlock.size() / 2; - nameValueBlockOut.writeInt(numberOfPairs); - for (String s : nameValueBlock) { - nameValueBlockOut.writeInt(s.length()); - nameValueBlockOut.write(s.getBytes("UTF-8")); - } - nameValueBlockOut.flush(); - } - - public synchronized void settings(int flags, Settings settings) throws IOException { - int type = SpdyConnection.TYPE_SETTINGS; - int size = settings.size(); - int length = 4 + size * 8; - out.writeInt(0x80000000 | (SpdyConnection.VERSION & 0x7fff) << 16 | type & 0xffff); - out.writeInt((flags & 0xff) << 24 | length & 0xffffff); - out.writeInt(size); - for (int i = 0; i <= Settings.COUNT; i++) { - if (!settings.isSet(i)) continue; - int settingsFlags = settings.flags(i); - out.writeInt((settingsFlags & 0xff) << 24 | (i & 0xffffff)); - out.writeInt(settings.get(i)); - } - out.flush(); - } - - public synchronized void noop() throws IOException { - int type = SpdyConnection.TYPE_NOOP; - int length = 0; - int flags = 0; - out.writeInt(0x80000000 | (SpdyConnection.VERSION & 0x7fff) << 16 | type & 0xffff); - out.writeInt((flags & 0xff) << 24 | length & 0xffffff); - out.flush(); - } - - public synchronized void ping(int flags, int id) throws IOException { - int type = SpdyConnection.TYPE_PING; - int length = 4; - out.writeInt(0x80000000 | (SpdyConnection.VERSION & 0x7fff) << 16 | type & 0xffff); - out.writeInt((flags & 0xff) << 24 | length & 0xffffff); - out.writeInt(id); - out.flush(); - } - - public synchronized void goAway(int flags, int lastGoodStreamId, int statusCode) - throws IOException { - int type = SpdyConnection.TYPE_GOAWAY; - int length = 8; - out.writeInt(0x80000000 | (SpdyConnection.VERSION & 0x7fff) << 16 | type & 0xffff); - out.writeInt((flags & 0xff) << 24 | length & 0xffffff); - out.writeInt(lastGoodStreamId); - out.writeInt(statusCode); - out.flush(); - } - - public synchronized void windowUpdate(int streamId, int deltaWindowSize) throws IOException { - int type = SpdyConnection.TYPE_WINDOW_UPDATE; - int flags = 0; - int length = 8; - out.writeInt(0x80000000 | (SpdyConnection.VERSION & 0x7fff) << 16 | type & 0xffff); - out.writeInt((flags & 0xff) << 24 | length & 0xffffff); - out.writeInt(streamId); - out.writeInt(deltaWindowSize); - out.flush(); - } - - @Override public void close() throws IOException { - Util.closeAll(out, nameValueBlockOut); - } -} diff --git a/platforms/android/CordovaLib/src/org/apache/cordova/App.java b/platforms/android/CordovaLib/src/org/apache/cordova/App.java deleted file mode 100755 index 86c65720e..000000000 --- a/platforms/android/CordovaLib/src/org/apache/cordova/App.java +++ /dev/null @@ -1,225 +0,0 @@ -/* - 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. -*/ - -package org.apache.cordova; - -import org.apache.cordova.CallbackContext; -import org.apache.cordova.CordovaPlugin; -import org.apache.cordova.LOG; -import org.apache.cordova.PluginResult; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.HashMap; - -/** - * This class exposes methods in Cordova that can be called from JavaScript. - */ -public class App extends CordovaPlugin { - - /** - * Executes the request and returns PluginResult. - * - * @param action The action to execute. - * @param args JSONArry of arguments for the plugin. - * @param callbackContext The callback context from which we were invoked. - * @return A PluginResult object with a status and message. - */ - public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { - PluginResult.Status status = PluginResult.Status.OK; - String result = ""; - - try { - if (action.equals("clearCache")) { - this.clearCache(); - } - else if (action.equals("show")) { - // This gets called from JavaScript onCordovaReady to show the webview. - // I recommend we change the name of the Message as spinner/stop is not - // indicative of what this actually does (shows the webview). - cordova.getActivity().runOnUiThread(new Runnable() { - public void run() { - webView.postMessage("spinner", "stop"); - } - }); - } - else if (action.equals("loadUrl")) { - this.loadUrl(args.getString(0), args.optJSONObject(1)); - } - else if (action.equals("cancelLoadUrl")) { - //this.cancelLoadUrl(); - } - else if (action.equals("clearHistory")) { - this.clearHistory(); - } - else if (action.equals("backHistory")) { - this.backHistory(); - } - else if (action.equals("overrideButton")) { - this.overrideButton(args.getString(0), args.getBoolean(1)); - } - else if (action.equals("overrideBackbutton")) { - this.overrideBackbutton(args.getBoolean(0)); - } - else if (action.equals("exitApp")) { - this.exitApp(); - } - callbackContext.sendPluginResult(new PluginResult(status, result)); - return true; - } catch (JSONException e) { - callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION)); - return false; - } - } - - //-------------------------------------------------------------------------- - // LOCAL METHODS - //-------------------------------------------------------------------------- - - /** - * Clear the resource cache. - */ - public void clearCache() { - cordova.getActivity().runOnUiThread(new Runnable() { - public void run() { - webView.clearCache(true); - } - }); - } - - /** - * Load the url into the webview. - * - * @param url - * @param props Properties that can be passed in to the Cordova activity (i.e. loadingDialog, wait, ...) - * @throws JSONException - */ - public void loadUrl(String url, JSONObject props) throws JSONException { - LOG.d("App", "App.loadUrl("+url+","+props+")"); - int wait = 0; - boolean openExternal = false; - boolean clearHistory = false; - - // If there are properties, then set them on the Activity - HashMap params = new HashMap(); - if (props != null) { - JSONArray keys = props.names(); - for (int i = 0; i < keys.length(); i++) { - String key = keys.getString(i); - if (key.equals("wait")) { - wait = props.getInt(key); - } - else if (key.equalsIgnoreCase("openexternal")) { - openExternal = props.getBoolean(key); - } - else if (key.equalsIgnoreCase("clearhistory")) { - clearHistory = props.getBoolean(key); - } - else { - Object value = props.get(key); - if (value == null) { - - } - else if (value.getClass().equals(String.class)) { - params.put(key, (String)value); - } - else if (value.getClass().equals(Boolean.class)) { - params.put(key, (Boolean)value); - } - else if (value.getClass().equals(Integer.class)) { - params.put(key, (Integer)value); - } - } - } - } - - // If wait property, then delay loading - - if (wait > 0) { - try { - synchronized(this) { - this.wait(wait); - } - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - this.webView.showWebPage(url, openExternal, clearHistory, params); - } - - /** - * Clear page history for the app. - */ - public void clearHistory() { - this.webView.clearHistory(); - } - - /** - * Go to previous page displayed. - * This is the same as pressing the backbutton on Android device. - */ - public void backHistory() { - cordova.getActivity().runOnUiThread(new Runnable() { - public void run() { - webView.backHistory(); - } - }); - } - - /** - * Override the default behavior of the Android back button. - * If overridden, when the back button is pressed, the "backKeyDown" JavaScript event will be fired. - * - * @param override T=override, F=cancel override - */ - public void overrideBackbutton(boolean override) { - LOG.i("App", "WARNING: Back Button Default Behaviour will be overridden. The backbutton event will be fired!"); - webView.bindButton(override); - } - - /** - * Override the default behavior of the Android volume buttons. - * If overridden, when the volume button is pressed, the "volume[up|down]button" JavaScript event will be fired. - * - * @param button volumeup, volumedown - * @param override T=override, F=cancel override - */ - public void overrideButton(String button, boolean override) { - LOG.i("App", "WARNING: Volume Button Default Behaviour will be overridden. The volume event will be fired!"); - webView.bindButton(button, override); - } - - /** - * Return whether the Android back button is overridden by the user. - * - * @return boolean - */ - public boolean isBackbuttonOverridden() { - return webView.isBackButtonBound(); - } - - /** - * Exit the Android application. - */ - public void exitApp() { - this.webView.postMessage("exit", null); - } - -} diff --git a/platforms/android/CordovaLib/src/org/apache/cordova/AuthenticationToken.java b/platforms/android/CordovaLib/src/org/apache/cordova/AuthenticationToken.java deleted file mode 100644 index d3a231a0b..000000000 --- a/platforms/android/CordovaLib/src/org/apache/cordova/AuthenticationToken.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - 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. -*/ -package org.apache.cordova; - -/** - * The Class AuthenticationToken defines the userName and password to be used for authenticating a web resource - */ -public class AuthenticationToken { - private String userName; - private String password; - - /** - * Gets the user name. - * - * @return the user name - */ - public String getUserName() { - return userName; - } - - /** - * Sets the user name. - * - * @param userName - * the new user name - */ - public void setUserName(String userName) { - this.userName = userName; - } - - /** - * Gets the password. - * - * @return the password - */ - public String getPassword() { - return password; - } - - /** - * Sets the password. - * - * @param password - * the new password - */ - public void setPassword(String password) { - this.password = password; - } - - - - -} diff --git a/platforms/android/CordovaLib/src/org/apache/cordova/CallbackContext.java b/platforms/android/CordovaLib/src/org/apache/cordova/CallbackContext.java deleted file mode 100644 index 446c37d9f..000000000 --- a/platforms/android/CordovaLib/src/org/apache/cordova/CallbackContext.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - 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. -*/ -package org.apache.cordova; - -import org.json.JSONArray; - -import android.util.Log; - -import org.apache.cordova.CordovaWebView; -import org.apache.cordova.PluginResult; -import org.json.JSONObject; - -public class CallbackContext { - private static final String LOG_TAG = "CordovaPlugin"; - - private String callbackId; - private CordovaWebView webView; - private boolean finished; - private int changingThreads; - - public CallbackContext(String callbackId, CordovaWebView webView) { - this.callbackId = callbackId; - this.webView = webView; - } - - public boolean isFinished() { - return finished; - } - - public boolean isChangingThreads() { - return changingThreads > 0; - } - - public String getCallbackId() { - return callbackId; - } - - public void sendPluginResult(PluginResult pluginResult) { - synchronized (this) { - if (finished) { - Log.w(LOG_TAG, "Attempted to send a second callback for ID: " + callbackId + "\nResult was: " + pluginResult.getMessage()); - return; - } else { - finished = !pluginResult.getKeepCallback(); - } - } - webView.sendPluginResult(pluginResult, callbackId); - } - - /** - * Helper for success callbacks that just returns the Status.OK by default - * - * @param message The message to add to the success result. - */ - public void success(JSONObject message) { - sendPluginResult(new PluginResult(PluginResult.Status.OK, message)); - } - - /** - * Helper for success callbacks that just returns the Status.OK by default - * - * @param message The message to add to the success result. - */ - public void success(String message) { - sendPluginResult(new PluginResult(PluginResult.Status.OK, message)); - } - - /** - * Helper for success callbacks that just returns the Status.OK by default - * - * @param message The message to add to the success result. - */ - public void success(JSONArray message) { - sendPluginResult(new PluginResult(PluginResult.Status.OK, message)); - } - - /** - * Helper for success callbacks that just returns the Status.OK by default - * - * @param message The message to add to the success result. - */ - public void success(byte[] message) { - sendPluginResult(new PluginResult(PluginResult.Status.OK, message)); - } - - /** - * Helper for success callbacks that just returns the Status.OK by default - * - * @param message The message to add to the success result. - */ - public void success(int message) { - sendPluginResult(new PluginResult(PluginResult.Status.OK, message)); - } - - /** - * Helper for success callbacks that just returns the Status.OK by default - */ - public void success() { - sendPluginResult(new PluginResult(PluginResult.Status.OK)); - } - - /** - * Helper for error callbacks that just returns the Status.ERROR by default - * - * @param message The message to add to the error result. - */ - public void error(JSONObject message) { - sendPluginResult(new PluginResult(PluginResult.Status.ERROR, message)); - } - - /** - * Helper for error callbacks that just returns the Status.ERROR by default - * - * @param message The message to add to the error result. - */ - public void error(String message) { - sendPluginResult(new PluginResult(PluginResult.Status.ERROR, message)); - } - - /** - * Helper for error callbacks that just returns the Status.ERROR by default - * - * @param message The message to add to the error result. - */ - public void error(int message) { - sendPluginResult(new PluginResult(PluginResult.Status.ERROR, message)); - } -} diff --git a/platforms/android/CordovaLib/src/org/apache/cordova/Config.java b/platforms/android/CordovaLib/src/org/apache/cordova/Config.java deleted file mode 100644 index eeedaf20a..000000000 --- a/platforms/android/CordovaLib/src/org/apache/cordova/Config.java +++ /dev/null @@ -1,231 +0,0 @@ -/* - 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. -*/ - -package org.apache.cordova; - -import java.io.IOException; - -import java.util.Locale; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.apache.cordova.LOG; - -import org.xmlpull.v1.XmlPullParserException; - -import android.app.Activity; - -import android.content.res.XmlResourceParser; -import android.graphics.Color; - -import android.util.Log; - -public class Config { - - public static final String TAG = "Config"; - - private Whitelist whitelist = new Whitelist(); - private String startUrl; - - private static Config self = null; - - public static void init(Activity action) { - //Just re-initialize this! Seriously, we lose this all the time - self = new Config(action); - } - - // Intended to be used for testing only; creates an empty configuration. - public static void init() { - if (self == null) { - self = new Config(); - } - } - - // Intended to be used for testing only; creates an empty configuration. - private Config() { - } - - private Config(Activity action) { - if (action == null) { - LOG.i("CordovaLog", "There is no activity. Is this on the lock screen?"); - return; - } - - // First checking the class namespace for config.xml - int id = action.getResources().getIdentifier("config", "xml", action.getClass().getPackage().getName()); - if (id == 0) { - // If we couldn't find config.xml there, we'll look in the namespace from AndroidManifest.xml - id = action.getResources().getIdentifier("config", "xml", action.getPackageName()); - if (id == 0) { - LOG.i("CordovaLog", "config.xml missing. Ignoring..."); - return; - } - } - - // Add implicitly allowed URLs - whitelist.addWhiteListEntry("file:///*", false); - whitelist.addWhiteListEntry("content:///*", false); - whitelist.addWhiteListEntry("data:*", false); - - XmlResourceParser xml = action.getResources().getXml(id); - int eventType = -1; - while (eventType != XmlResourceParser.END_DOCUMENT) { - if (eventType == XmlResourceParser.START_TAG) { - String strNode = xml.getName(); - - if (strNode.equals("access")) { - String origin = xml.getAttributeValue(null, "origin"); - String subdomains = xml.getAttributeValue(null, "subdomains"); - if (origin != null) { - whitelist.addWhiteListEntry(origin, (subdomains != null) && (subdomains.compareToIgnoreCase("true") == 0)); - } - } - else if (strNode.equals("log")) { - String level = xml.getAttributeValue(null, "level"); - Log.d(TAG, "The tags is deprecated. Use instead."); - if (level != null) { - LOG.setLogLevel(level); - } - } - else if (strNode.equals("preference")) { - String name = xml.getAttributeValue(null, "name").toLowerCase(Locale.getDefault()); - /* Java 1.6 does not support switch-based strings - Java 7 does, but we're using Dalvik, which is apparently not Java. - Since we're reading XML, this has to be an ugly if/else. - - Also, due to cast issues, each of them has to call their separate putExtra! - Wheee!!! Isn't Java FUN!?!?!? - - Note: We should probably pass in the classname for the variable splash on splashscreen! - */ - if (name.equalsIgnoreCase("LogLevel")) { - String level = xml.getAttributeValue(null, "value"); - LOG.setLogLevel(level); - } else if (name.equalsIgnoreCase("SplashScreen")) { - String value = xml.getAttributeValue(null, "value"); - int resource = 0; - if (value == null) - { - value = "splash"; - } - resource = action.getResources().getIdentifier(value, "drawable", action.getClass().getPackage().getName()); - - action.getIntent().putExtra(name, resource); - } - else if(name.equalsIgnoreCase("BackgroundColor")) { - int value = xml.getAttributeIntValue(null, "value", Color.BLACK); - action.getIntent().putExtra(name, value); - } - else if(name.equalsIgnoreCase("LoadUrlTimeoutValue")) { - int value = xml.getAttributeIntValue(null, "value", 20000); - action.getIntent().putExtra(name, value); - } - else if(name.equalsIgnoreCase("SplashScreenDelay")) { - int value = xml.getAttributeIntValue(null, "value", 3000); - action.getIntent().putExtra(name, value); - } - else if(name.equalsIgnoreCase("KeepRunning")) - { - boolean value = xml.getAttributeValue(null, "value").equals("true"); - action.getIntent().putExtra(name, value); - } - else if(name.equalsIgnoreCase("InAppBrowserStorageEnabled")) - { - boolean value = xml.getAttributeValue(null, "value").equals("true"); - action.getIntent().putExtra(name, value); - } - else if(name.equalsIgnoreCase("DisallowOverscroll")) - { - boolean value = xml.getAttributeValue(null, "value").equals("true"); - action.getIntent().putExtra(name, value); - } - else - { - String value = xml.getAttributeValue(null, "value"); - action.getIntent().putExtra(name, value); - } - /* - LOG.i("CordovaLog", "Found preference for %s=%s", name, value); - */ - } - else if (strNode.equals("content")) { - String src = xml.getAttributeValue(null, "src"); - - LOG.i("CordovaLog", "Found start page location: %s", src); - - if (src != null) { - Pattern schemeRegex = Pattern.compile("^[a-z-]+://"); - Matcher matcher = schemeRegex.matcher(src); - if (matcher.find()) { - startUrl = src; - } else { - if (src.charAt(0) == '/') { - src = src.substring(1); - } - startUrl = "file:///android_asset/www/" + src; - } - } - } - - } - - try { - eventType = xml.next(); - } catch (XmlPullParserException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - - /** - * Add entry to approved list of URLs (whitelist) - * - * @param origin URL regular expression to allow - * @param subdomains T=include all subdomains under origin - */ - public static void addWhiteListEntry(String origin, boolean subdomains) { - if (self == null) { - return; - } - self.whitelist.addWhiteListEntry(origin, subdomains); - } - - /** - * Determine if URL is in approved list of URLs to load. - * - * @param url - * @return true if whitelisted - */ - public static boolean isUrlWhiteListed(String url) { - if (self == null) { - return false; - } - return self.whitelist.isUrlWhiteListed(url); - } - - public static String getStartUrl() { - if (self == null || self.startUrl == null) { - return "file:///android_asset/www/index.html"; - } - return self.startUrl; - } -} diff --git a/platforms/android/CordovaLib/src/org/apache/cordova/CordovaActivity.java b/platforms/android/CordovaLib/src/org/apache/cordova/CordovaActivity.java deleted file mode 100755 index 567ed1bbe..000000000 --- a/platforms/android/CordovaLib/src/org/apache/cordova/CordovaActivity.java +++ /dev/null @@ -1,1223 +0,0 @@ -/* - 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. -*/ -package org.apache.cordova; - -import java.util.HashMap; -import java.util.Locale; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -import org.apache.cordova.CordovaInterface; -import org.apache.cordova.CordovaPlugin; -import org.apache.cordova.LOG; -import org.json.JSONException; -import org.json.JSONObject; - -import android.annotation.SuppressLint; -import android.app.Activity; -import android.app.AlertDialog; -import android.app.Dialog; -import android.app.ProgressDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.res.Configuration; -import android.graphics.Color; -import android.media.AudioManager; -import android.net.Uri; -import android.os.Bundle; -import android.os.Handler; -import android.util.Log; -import android.view.Display; -import android.view.KeyEvent; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.view.Window; -import android.view.WindowManager; -import android.widget.ImageView; -import android.webkit.ValueCallback; -import android.webkit.WebViewClient; -import android.widget.LinearLayout; - -/** - * This class is the main Android activity that represents the Cordova - * application. It should be extended by the user to load the specific - * html file that contains the application. - * - * As an example: - * - *
- *     package org.apache.cordova.examples;
- *     import android.os.Bundle;
- *     import org.apache.cordova.*;
- *
- *     public class Example extends CordovaActivity {
- *       @Override
- *       public void onCreate(Bundle savedInstanceState) {
- *         super.onCreate(savedInstanceState);
- *
- *         // Set properties for activity
- *         super.setStringProperty("loadingDialog", "Title,Message"); // show loading dialog
- *         super.setStringProperty("errorUrl", "file:///android_asset/www/error.html"); // if error loading file in super.loadUrl().
- *
- *         // Clear cache if you want
- *         super.appView.clearCache(true);
- *
- *         // Load your application
- *         super.setIntegerProperty("splashscreen", R.drawable.splash); // load splash.jpg image from the resource drawable directory
- *         super.loadUrl("file:///android_asset/www/index.html", 3000); // show splash screen 3 sec before loading app
- *       }
- *     }
- * 
- * - * Properties: The application can be configured using the following properties: - * - *
- *      // Display a native loading dialog when loading app.  Format for value = "Title,Message".
- *      // (String - default=null)
- *      super.setStringProperty("loadingDialog", "Wait,Loading Demo...");
- *
- *      // Display a native loading dialog when loading sub-pages.  Format for value = "Title,Message".
- *      // (String - default=null)
- *      super.setStringProperty("loadingPageDialog", "Loading page...");
- *
- *      // Load a splash screen image from the resource drawable directory.
- *      // (Integer - default=0)
- *      super.setIntegerProperty("splashscreen", R.drawable.splash);
- *
- *      // Set the background color.
- *      // (Integer - default=0 or BLACK)
- *      super.setIntegerProperty("backgroundColor", Color.WHITE);
- *
- *      // Time in msec to wait before triggering a timeout error when loading
- *      // with super.loadUrl().  (Integer - default=20000)
- *      super.setIntegerProperty("loadUrlTimeoutValue", 60000);
- *
- *      // URL to load if there's an error loading specified URL with loadUrl().
- *      // Should be a local URL starting with file://. (String - default=null)
- *      super.setStringProperty("errorUrl", "file:///android_asset/www/error.html");
- *
- *      // Enable app to keep running in background. (Boolean - default=true)
- *      super.setBooleanProperty("keepRunning", false);
- * 
- * - * Cordova.xml configuration: - * - *
- *      Cordova uses a configuration file at res/xml/cordova.xml to specify the following settings.
- *
- *      Approved list of URLs that can be loaded into Cordova
- *          <access origin="http://server regexp" subdomains="true" />
- *      Log level: ERROR, WARN, INFO, DEBUG, VERBOSE (default=ERROR)
- *          <log level="DEBUG" />
- * 
- * - */ -public class CordovaActivity extends Activity implements CordovaInterface { - public static String TAG = "CordovaActivity"; - - // The webview for our app - protected CordovaWebView appView; - protected CordovaWebViewClient webViewClient; - - protected LinearLayout root; - protected boolean cancelLoadUrl = false; - protected ProgressDialog spinnerDialog = null; - private final ExecutorService threadPool = Executors.newCachedThreadPool(); - - - // The initial URL for our app - // ie http://server/path/index.html#abc?query - //private String url = null; - - private static int ACTIVITY_STARTING = 0; - private static int ACTIVITY_RUNNING = 1; - private static int ACTIVITY_EXITING = 2; - private int activityState = 0; // 0=starting, 1=running (after 1st resume), 2=shutting down - - // Plugin to call when activity result is received - protected CordovaPlugin activityResultCallback = null; - protected boolean activityResultKeepRunning; - - // Default background color for activity - // (this is not the color for the webview, which is set in HTML) - private int backgroundColor = Color.BLACK; - - /* - * The variables below are used to cache some of the activity properties. - */ - - // Draw a splash screen using an image located in the drawable resource directory. - // This is not the same as calling super.loadSplashscreen(url) - protected int splashscreen = 0; - protected int splashscreenTime = 3000; - - // LoadUrl timeout value in msec (default of 20 sec) - protected int loadUrlTimeoutValue = 20000; - - // Keep app running when pause is received. (default = true) - // If true, then the JavaScript and native code continue to run in the background - // when another application (activity) is started. - protected boolean keepRunning = true; - - private int lastRequestCode; - - private Object responseCode; - - private Intent lastIntent; - - private Object lastResponseCode; - - private String initCallbackClass; - - private Object LOG_TAG; - - /** - * Sets the authentication token. - * - * @param authenticationToken - * @param host - * @param realm - */ - public void setAuthenticationToken(AuthenticationToken authenticationToken, String host, String realm) { - if (this.appView != null && this.appView.viewClient != null) { - this.appView.viewClient.setAuthenticationToken(authenticationToken, host, realm); - } - } - - /** - * Removes the authentication token. - * - * @param host - * @param realm - * - * @return the authentication token or null if did not exist - */ - public AuthenticationToken removeAuthenticationToken(String host, String realm) { - if (this.appView != null && this.appView.viewClient != null) { - return this.appView.viewClient.removeAuthenticationToken(host, realm); - } - return null; - } - - /** - * Gets the authentication token. - * - * In order it tries: - * 1- host + realm - * 2- host - * 3- realm - * 4- no host, no realm - * - * @param host - * @param realm - * - * @return the authentication token - */ - public AuthenticationToken getAuthenticationToken(String host, String realm) { - if (this.appView != null && this.appView.viewClient != null) { - return this.appView.viewClient.getAuthenticationToken(host, realm); - } - return null; - } - - /** - * Clear all authentication tokens. - */ - public void clearAuthenticationTokens() { - if (this.appView != null && this.appView.viewClient != null) { - this.appView.viewClient.clearAuthenticationTokens(); - } - } - - /** - * Called when the activity is first created. - * - * @param savedInstanceState - */ - @SuppressWarnings("deprecation") - @Override - public void onCreate(Bundle savedInstanceState) { - Config.init(this); - LOG.d(TAG, "CordovaActivity.onCreate()"); - super.onCreate(savedInstanceState); - - if(savedInstanceState != null) - { - initCallbackClass = savedInstanceState.getString("callbackClass"); - } - - if(!this.getBooleanProperty("ShowTitle", false)) - { - getWindow().requestFeature(Window.FEATURE_NO_TITLE); - } - - if(this.getBooleanProperty("SetFullscreen", false)) - { - getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, - WindowManager.LayoutParams.FLAG_FULLSCREEN); - } - else - { - getWindow().setFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN, - WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); - } - // This builds the view. We could probably get away with NOT having a LinearLayout, but I like having a bucket! - Display display = getWindowManager().getDefaultDisplay(); - int width = display.getWidth(); - int height = display.getHeight(); - - root = new LinearLayoutSoftKeyboardDetect(this, width, height); - root.setOrientation(LinearLayout.VERTICAL); - root.setBackgroundColor(this.backgroundColor); - root.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT, 0.0F)); - - // Setup the hardware volume controls to handle volume control - setVolumeControlStream(AudioManager.STREAM_MUSIC); - } - - /** - * Get the Android activity. - * - * @return the Activity - */ - public Activity getActivity() { - return this; - } - - /** - * Construct the default web view object. - * - * This is intended to be overridable by subclasses of CordovaIntent which - * require a more specialized web view. - */ - protected CordovaWebView makeWebView() { - return new CordovaWebView(CordovaActivity.this); - } - - /** - * Construct the client for the default web view object. - * - * This is intended to be overridable by subclasses of CordovaIntent which - * require a more specialized web view. - * - * @param webView the default constructed web view object - */ - protected CordovaWebViewClient makeWebViewClient(CordovaWebView webView) { - if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB) { - return new CordovaWebViewClient(this, webView); - } else { - return new IceCreamCordovaWebViewClient(this, webView); - } - } - - /** - * Construct the chrome client for the default web view object. - * - * This is intended to be overridable by subclasses of CordovaIntent which - * require a more specialized web view. - * - * @param webView the default constructed web view object - */ - protected CordovaChromeClient makeChromeClient(CordovaWebView webView) { - return new CordovaChromeClient(this, webView); - } - - /** - * Create and initialize web container with default web view objects. - */ - public void init() { - CordovaWebView webView = makeWebView(); - this.init(webView, makeWebViewClient(webView), makeChromeClient(webView)); - } - - /** - * Initialize web container with web view objects. - * - * @param webView - * @param webViewClient - * @param webChromeClient - */ - @SuppressLint("NewApi") - public void init(CordovaWebView webView, CordovaWebViewClient webViewClient, CordovaChromeClient webChromeClient) { - LOG.d(TAG, "CordovaActivity.init()"); - - // Set up web container - this.appView = webView; - this.appView.setId(100); - - this.appView.setWebViewClient(webViewClient); - this.appView.setWebChromeClient(webChromeClient); - webViewClient.setWebView(this.appView); - webChromeClient.setWebView(this.appView); - - this.appView.setLayoutParams(new LinearLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT, - 1.0F)); - - if (this.getBooleanProperty("DisallowOverscroll", false)) { - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.GINGERBREAD) { - this.appView.setOverScrollMode(CordovaWebView.OVER_SCROLL_NEVER); - } - } - - // Add web view but make it invisible while loading URL - this.appView.setVisibility(View.INVISIBLE); - this.root.addView(this.appView); - setContentView(this.root); - - // Clear cancel flag - this.cancelLoadUrl = false; - - } - - /** - * Load the url into the webview. - * - * @param url - */ - public void loadUrl(String url) { - - // Init web view if not already done - if (this.appView == null) { - this.init(); - } - - this.splashscreenTime = this.getIntegerProperty("SplashScreenDelay", this.splashscreenTime); - if(this.splashscreenTime > 0) - { - this.splashscreen = this.getIntegerProperty("SplashScreen", 0); - if(this.splashscreen != 0) - { - this.showSplashScreen(this.splashscreenTime); - } - } - - // Set backgroundColor - this.backgroundColor = this.getIntegerProperty("BackgroundColor", Color.BLACK); - this.root.setBackgroundColor(this.backgroundColor); - - // If keepRunning - this.keepRunning = this.getBooleanProperty("KeepRunning", true); - - //Check if the view is attached to anything - if(appView.getParent() != null) - { - // Then load the spinner - this.loadSpinner(); - } - //Load the correct splashscreen - - if(this.splashscreen != 0) - { - this.appView.loadUrl(url, this.splashscreenTime); - } - else - { - this.appView.loadUrl(url); - } - } - - /** - * Load the url into the webview after waiting for period of time. - * This is used to display the splashscreen for certain amount of time. - * - * @param url - * @param time The number of ms to wait before loading webview - */ - public void loadUrl(final String url, int time) { - - this.splashscreenTime = time; - this.loadUrl(url); - - /* - // Init web view if not already done - if (this.appView == null) { - this.init(); - } - - this.splashscreenTime = time; - this.splashscreen = this.getIntegerProperty("SplashScreen", 0); - this.showSplashScreen(this.splashscreenTime); - this.appView.loadUrl(url, time); - */ - } - - /* - * Load the spinner - */ - void loadSpinner() { - - // If loadingDialog property, then show the App loading dialog for first page of app - String loading = null; - if ((this.appView == null) || !this.appView.canGoBack()) { - loading = this.getStringProperty("LoadingDialog", null); - } - else { - loading = this.getStringProperty("LoadingPageDialog", null); - } - if (loading != null) { - - String title = ""; - String message = "Loading Application..."; - - if (loading.length() > 0) { - int comma = loading.indexOf(','); - if (comma > 0) { - title = loading.substring(0, comma); - message = loading.substring(comma + 1); - } - else { - title = ""; - message = loading; - } - } - this.spinnerStart(title, message); - } - } - - - /** - * Cancel loadUrl before it has been loaded. - */ - // TODO NO-OP - @Deprecated - public void cancelLoadUrl() { - this.cancelLoadUrl = true; - } - - /** - * Clear the resource cache. - */ - public void clearCache() { - if (this.appView == null) { - this.init(); - } - this.appView.clearCache(true); - } - - /** - * Clear web history in this web view. - */ - public void clearHistory() { - this.appView.clearHistory(); - } - - /** - * Go to previous page in history. (We manage our own history) - * - * @return true if we went back, false if we are already at top - */ - public boolean backHistory() { - if (this.appView != null) { - return appView.backHistory(); - } - return false; - } - - @Override - /** - * Called by the system when the device configuration changes while your activity is running. - * - * @param Configuration newConfig - */ - public void onConfigurationChanged(Configuration newConfig) { - //don't reload the current page when the orientation is changed - super.onConfigurationChanged(newConfig); - } - - /** - * Get boolean property for activity. - * - * @param name - * @param defaultValue - * @return the boolean value of the named property - */ - public boolean getBooleanProperty(String name, boolean defaultValue) { - Bundle bundle = this.getIntent().getExtras(); - if (bundle == null) { - return defaultValue; - } - name = name.toLowerCase(Locale.getDefault()); - Boolean p; - try { - p = (Boolean) bundle.get(name); - } catch (ClassCastException e) { - String s = bundle.get(name).toString(); - if ("true".equals(s)) { - p = true; - } - else { - p = false; - } - } - if (p == null) { - return defaultValue; - } - return p.booleanValue(); - } - - /** - * Get int property for activity. - * - * @param name - * @param defaultValue - * @return the int value for the named property - */ - public int getIntegerProperty(String name, int defaultValue) { - Bundle bundle = this.getIntent().getExtras(); - if (bundle == null) { - return defaultValue; - } - name = name.toLowerCase(Locale.getDefault()); - Integer p; - try { - p = (Integer) bundle.get(name); - } catch (ClassCastException e) { - p = Integer.parseInt(bundle.get(name).toString()); - } - if (p == null) { - return defaultValue; - } - return p.intValue(); - } - - /** - * Get string property for activity. - * - * @param name - * @param defaultValue - * @return the String value for the named property - */ - public String getStringProperty(String name, String defaultValue) { - Bundle bundle = this.getIntent().getExtras(); - if (bundle == null) { - return defaultValue; - } - name = name.toLowerCase(Locale.getDefault()); - String p = bundle.getString(name); - if (p == null) { - return defaultValue; - } - return p; - } - - /** - * Get double property for activity. - * - * @param name - * @param defaultValue - * @return the double value for the named property - */ - public double getDoubleProperty(String name, double defaultValue) { - Bundle bundle = this.getIntent().getExtras(); - if (bundle == null) { - return defaultValue; - } - name = name.toLowerCase(Locale.getDefault()); - Double p; - try { - p = (Double) bundle.get(name); - } catch (ClassCastException e) { - p = Double.parseDouble(bundle.get(name).toString()); - } - if (p == null) { - return defaultValue; - } - return p.doubleValue(); - } - - /** - * Set boolean property on activity. - * This method has been deprecated in 3.0 and will be removed at a future - * time. Please use config.xml instead. - * - * @param name - * @param value - * @deprecated - */ - @Deprecated - public void setBooleanProperty(String name, boolean value) { - Log.d(TAG, "Setting boolean properties in CordovaActivity will be deprecated in 3.0 on July 2013, please use config.xml"); - this.getIntent().putExtra(name.toLowerCase(), value); - } - - /** - * Set int property on activity. - * This method has been deprecated in 3.0 and will be removed at a future - * time. Please use config.xml instead. - * - * @param name - * @param value - * @deprecated - */ - @Deprecated - public void setIntegerProperty(String name, int value) { - Log.d(TAG, "Setting integer properties in CordovaActivity will be deprecated in 3.0 on July 2013, please use config.xml"); - this.getIntent().putExtra(name.toLowerCase(), value); - } - - /** - * Set string property on activity. - * This method has been deprecated in 3.0 and will be removed at a future - * time. Please use config.xml instead. - * - * @param name - * @param value - * @deprecated - */ - @Deprecated - public void setStringProperty(String name, String value) { - Log.d(TAG, "Setting string properties in CordovaActivity will be deprecated in 3.0 on July 2013, please use config.xml"); - this.getIntent().putExtra(name.toLowerCase(), value); - } - - /** - * Set double property on activity. - * This method has been deprecated in 3.0 and will be removed at a future - * time. Please use config.xml instead. - * - * @param name - * @param value - * @deprecated - */ - @Deprecated - public void setDoubleProperty(String name, double value) { - Log.d(TAG, "Setting double properties in CordovaActivity will be deprecated in 3.0 on July 2013, please use config.xml"); - this.getIntent().putExtra(name.toLowerCase(), value); - } - - @Override - /** - * Called when the system is about to start resuming a previous activity. - */ - protected void onPause() { - super.onPause(); - - LOG.d(TAG, "Paused the application!"); - - // Don't process pause if shutting down, since onDestroy() will be called - if (this.activityState == ACTIVITY_EXITING) { - return; - } - - if (this.appView == null) { - return; - } - else - { - this.appView.handlePause(this.keepRunning); - } - - // hide the splash screen to avoid leaking a window - this.removeSplashScreen(); - } - - @Override - /** - * Called when the activity receives a new intent - **/ - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - //Forward to plugins - if (this.appView != null) - this.appView.onNewIntent(intent); - } - - @Override - /** - * Called when the activity will start interacting with the user. - */ - protected void onResume() { - super.onResume(); - //Reload the configuration - Config.init(this); - - LOG.d(TAG, "Resuming the App"); - - - //Code to test CB-3064 - String errorUrl = this.getStringProperty("ErrorUrl", null); - LOG.d(TAG, "CB-3064: The errorUrl is " + errorUrl); - - if (this.activityState == ACTIVITY_STARTING) { - this.activityState = ACTIVITY_RUNNING; - return; - } - - if (this.appView == null) { - return; - } - - this.appView.handleResume(this.keepRunning, this.activityResultKeepRunning); - - // If app doesn't want to run in background - if (!this.keepRunning || this.activityResultKeepRunning) { - - // Restore multitasking state - if (this.activityResultKeepRunning) { - this.keepRunning = this.activityResultKeepRunning; - this.activityResultKeepRunning = false; - } - } - } - - @Override - /** - * The final call you receive before your activity is destroyed. - */ - public void onDestroy() { - LOG.d(TAG, "CordovaActivity.onDestroy()"); - super.onDestroy(); - - // hide the splash screen to avoid leaking a window - this.removeSplashScreen(); - - if (this.appView != null) { - appView.handleDestroy(); - } - else { - this.activityState = ACTIVITY_EXITING; - } - } - - /** - * Send a message to all plugins. - * - * @param id The message id - * @param data The message data - */ - public void postMessage(String id, Object data) { - if (this.appView != null) { - this.appView.postMessage(id, data); - } - } - - /** - * @deprecated - * Add services to res/xml/plugins.xml instead. - * - * Add a class that implements a service. - * - * @param serviceType - * @param className - */ - @Deprecated - public void addService(String serviceType, String className) { - if (this.appView != null && this.appView.pluginManager != null) { - this.appView.pluginManager.addService(serviceType, className); - } - } - - /** - * Send JavaScript statement back to JavaScript. - * (This is a convenience method) - * - * @param statement - */ - public void sendJavascript(String statement) { - if (this.appView != null) { - this.appView.jsMessageQueue.addJavaScript(statement); - } - } - - /** - * Show the spinner. Must be called from the UI thread. - * - * @param title Title of the dialog - * @param message The message of the dialog - */ - public void spinnerStart(final String title, final String message) { - if (this.spinnerDialog != null) { - this.spinnerDialog.dismiss(); - this.spinnerDialog = null; - } - final CordovaActivity me = this; - this.spinnerDialog = ProgressDialog.show(CordovaActivity.this, title, message, true, true, - new DialogInterface.OnCancelListener() { - public void onCancel(DialogInterface dialog) { - me.spinnerDialog = null; - } - }); - } - - /** - * Stop spinner - Must be called from UI thread - */ - public void spinnerStop() { - if (this.spinnerDialog != null && this.spinnerDialog.isShowing()) { - this.spinnerDialog.dismiss(); - this.spinnerDialog = null; - } - } - - /** - * End this activity by calling finish for activity - */ - public void endActivity() { - this.activityState = ACTIVITY_EXITING; - super.finish(); - } - - - /** - * Launch an activity for which you would like a result when it finished. When this activity exits, - * your onActivityResult() method will be called. - * - * @param command The command object - * @param intent The intent to start - * @param requestCode The request code that is passed to callback to identify the activity - */ - public void startActivityForResult(CordovaPlugin command, Intent intent, int requestCode) { - this.activityResultCallback = command; - this.activityResultKeepRunning = this.keepRunning; - - // If multitasking turned on, then disable it for activities that return results - if (command != null) { - this.keepRunning = false; - } - - // Start activity - super.startActivityForResult(intent, requestCode); - } - - @Override - /** - * Called when an activity you launched exits, giving you the requestCode you started it with, - * the resultCode it returned, and any additional data from it. - * - * @param requestCode The request code originally supplied to startActivityForResult(), - * allowing you to identify who this result came from. - * @param resultCode The integer result code returned by the child activity through its setResult(). - * @param data An Intent, which can return result data to the caller (various data can be attached to Intent "extras"). - */ - protected void onActivityResult(int requestCode, int resultCode, Intent intent) { - LOG.d(TAG, "Incoming Result"); - super.onActivityResult(requestCode, resultCode, intent); - Log.d(TAG, "Request code = " + requestCode); - if (appView != null && requestCode == CordovaChromeClient.FILECHOOSER_RESULTCODE) { - ValueCallback mUploadMessage = this.appView.getWebChromeClient().getValueCallback(); - Log.d(TAG, "did we get here?"); - if (null == mUploadMessage) - return; - Uri result = intent == null || resultCode != Activity.RESULT_OK ? null : intent.getData(); - Log.d(TAG, "result = " + result); -// Uri filepath = Uri.parse("file://" + FileUtils.getRealPathFromURI(result, this)); -// Log.d(TAG, "result = " + filepath); - mUploadMessage.onReceiveValue(result); - mUploadMessage = null; - } - CordovaPlugin callback = this.activityResultCallback; - if(callback == null && initCallbackClass != null) { - // The application was restarted, but had defined an initial callback - // before being shut down. - this.activityResultCallback = appView.pluginManager.getPlugin(initCallbackClass); - callback = this.activityResultCallback; - } - if(callback != null) { - LOG.d(TAG, "We have a callback to send this result to"); - callback.onActivityResult(requestCode, resultCode, intent); - } - } - - public void setActivityResultCallback(CordovaPlugin plugin) { - this.activityResultCallback = plugin; - } - - /** - * Report an error to the host application. These errors are unrecoverable (i.e. the main resource is unavailable). - * The errorCode parameter corresponds to one of the ERROR_* constants. - * - * @param errorCode The error code corresponding to an ERROR_* value. - * @param description A String describing the error. - * @param failingUrl The url that failed to load. - */ - public void onReceivedError(final int errorCode, final String description, final String failingUrl) { - final CordovaActivity me = this; - - // If errorUrl specified, then load it - final String errorUrl = me.getStringProperty("errorUrl", null); - if ((errorUrl != null) && (errorUrl.startsWith("file://") || Config.isUrlWhiteListed(errorUrl)) && (!failingUrl.equals(errorUrl))) { - - // Load URL on UI thread - me.runOnUiThread(new Runnable() { - public void run() { - // Stop "app loading" spinner if showing - me.spinnerStop(); - me.appView.showWebPage(errorUrl, false, true, null); - } - }); - } - // If not, then display error dialog - else { - final boolean exit = !(errorCode == WebViewClient.ERROR_HOST_LOOKUP); - me.runOnUiThread(new Runnable() { - public void run() { - if (exit) { - me.appView.setVisibility(View.GONE); - me.displayError("Application Error", description + " (" + failingUrl + ")", "OK", exit); - } - } - }); - } - } - - /** - * Display an error dialog and optionally exit application. - * - * @param title - * @param message - * @param button - * @param exit - */ - public void displayError(final String title, final String message, final String button, final boolean exit) { - final CordovaActivity me = this; - me.runOnUiThread(new Runnable() { - public void run() { - try { - AlertDialog.Builder dlg = new AlertDialog.Builder(me); - dlg.setMessage(message); - dlg.setTitle(title); - dlg.setCancelable(false); - dlg.setPositiveButton(button, - new AlertDialog.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - if (exit) { - me.endActivity(); - } - } - }); - dlg.create(); - dlg.show(); - } catch (Exception e) { - finish(); - } - } - }); - } - - /** - * Determine if URL is in approved list of URLs to load. - * - * @param url - * @return true if the url is whitelisted - */ - public boolean isUrlWhiteListed(String url) { - return Config.isUrlWhiteListed(url); - } - - /* - * Hook in Cordova for menu plugins - * - */ - @Override - public boolean onCreateOptionsMenu(Menu menu) { - this.postMessage("onCreateOptionsMenu", menu); - return super.onCreateOptionsMenu(menu); - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - this.postMessage("onPrepareOptionsMenu", menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - this.postMessage("onOptionsItemSelected", item); - return true; - } - - /** - * Get Activity context. - * - * @return self - * @deprecated - */ - @Deprecated - public Context getContext() { - LOG.d(TAG, "This will be deprecated December 2012"); - return this; - } - - /** - * Load the specified URL in the Cordova webview or a new browser instance. - * - * NOTE: If openExternal is false, only URLs listed in whitelist can be loaded. - * - * @param url The url to load. - * @param openExternal Load url in browser instead of Cordova webview. - * @param clearHistory Clear the history stack, so new page becomes top of history - * @param params Parameters for new app - */ - public void showWebPage(String url, boolean openExternal, boolean clearHistory, HashMap params) { - if (this.appView != null) { - appView.showWebPage(url, openExternal, clearHistory, params); - } - } - - protected Dialog splashDialog; - - /** - * Removes the Dialog that displays the splash screen - */ - public void removeSplashScreen() { - if (splashDialog != null && splashDialog.isShowing()) { - splashDialog.dismiss(); - splashDialog = null; - } - } - - /** - * Shows the splash screen over the full Activity - */ - @SuppressWarnings("deprecation") - protected void showSplashScreen(final int time) { - final CordovaActivity that = this; - - Runnable runnable = new Runnable() { - public void run() { - // Get reference to display - Display display = getWindowManager().getDefaultDisplay(); - - // Create the layout for the dialog - LinearLayout root = new LinearLayout(that.getActivity()); - root.setMinimumHeight(display.getHeight()); - root.setMinimumWidth(display.getWidth()); - root.setOrientation(LinearLayout.VERTICAL); - root.setBackgroundColor(that.getIntegerProperty("backgroundColor", Color.BLACK)); - root.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT, 0.0F)); - root.setBackgroundResource(that.splashscreen); - - // Create and show the dialog - splashDialog = new Dialog(that, android.R.style.Theme_Translucent_NoTitleBar); - // check to see if the splash screen should be full screen - if ((getWindow().getAttributes().flags & WindowManager.LayoutParams.FLAG_FULLSCREEN) - == WindowManager.LayoutParams.FLAG_FULLSCREEN) { - splashDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, - WindowManager.LayoutParams.FLAG_FULLSCREEN); - } - splashDialog.setContentView(root); - splashDialog.setCancelable(false); - splashDialog.show(); - - // Set Runnable to remove splash screen just in case - final Handler handler = new Handler(); - handler.postDelayed(new Runnable() { - public void run() { - removeSplashScreen(); - } - }, time); - } - }; - this.runOnUiThread(runnable); - } - - @Override - public boolean onKeyUp(int keyCode, KeyEvent event) - { - if (appView != null && (appView.isCustomViewShowing() || appView.getFocusedChild() != null ) && - (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_MENU)) { - return appView.onKeyUp(keyCode, event); - } else { - return super.onKeyUp(keyCode, event); - } - } - - /* - * Android 2.x needs to be able to check where the cursor is. Android 4.x does not - * - * (non-Javadoc) - * @see android.app.Activity#onKeyDown(int, android.view.KeyEvent) - */ - - @Override - public boolean onKeyDown(int keyCode, KeyEvent event) - { - //Determine if the focus is on the current view or not - if (appView != null && appView.getFocusedChild() != null && (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_MENU)) { - return appView.onKeyDown(keyCode, event); - } - else - return super.onKeyDown(keyCode, event); - } - - - /** - * Called when a message is sent to plugin. - * - * @param id The message id - * @param data The message data - * @return Object or null - */ - public Object onMessage(String id, Object data) { - LOG.d(TAG, "onMessage(" + id + "," + data + ")"); - if ("splashscreen".equals(id)) { - if ("hide".equals(data.toString())) { - this.removeSplashScreen(); - } - else { - // If the splash dialog is showing don't try to show it again - if (this.splashDialog == null || !this.splashDialog.isShowing()) { - this.splashscreen = this.getIntegerProperty("SplashScreen", 0); - this.showSplashScreen(this.splashscreenTime); - } - } - } - else if ("spinner".equals(id)) { - if ("stop".equals(data.toString())) { - this.spinnerStop(); - this.appView.setVisibility(View.VISIBLE); - } - } - else if ("onReceivedError".equals(id)) { - JSONObject d = (JSONObject) data; - try { - this.onReceivedError(d.getInt("errorCode"), d.getString("description"), d.getString("url")); - } catch (JSONException e) { - e.printStackTrace(); - } - } - else if ("exit".equals(id)) { - this.endActivity(); - } - return null; - } - - public ExecutorService getThreadPool() { - return threadPool; - } - - protected void onSaveInstanceState(Bundle outState) - { - super.onSaveInstanceState(outState); - if(this.activityResultCallback != null) - { - String cClass = this.activityResultCallback.getClass().getName(); - outState.putString("callbackClass", cClass); - } - } -} - diff --git a/platforms/android/CordovaLib/src/org/apache/cordova/CordovaArgs.java b/platforms/android/CordovaLib/src/org/apache/cordova/CordovaArgs.java deleted file mode 100644 index d40d26ebb..000000000 --- a/platforms/android/CordovaLib/src/org/apache/cordova/CordovaArgs.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - 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. -*/ -package org.apache.cordova; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import android.util.Base64; - -public class CordovaArgs { - private JSONArray baseArgs; - - public CordovaArgs(JSONArray args) { - this.baseArgs = args; - } - - - // Pass through the basics to the base args. - public Object get(int index) throws JSONException { - return baseArgs.get(index); - } - - public boolean getBoolean(int index) throws JSONException { - return baseArgs.getBoolean(index); - } - - public double getDouble(int index) throws JSONException { - return baseArgs.getDouble(index); - } - - public int getInt(int index) throws JSONException { - return baseArgs.getInt(index); - } - - public JSONArray getJSONArray(int index) throws JSONException { - return baseArgs.getJSONArray(index); - } - - public JSONObject getJSONObject(int index) throws JSONException { - return baseArgs.getJSONObject(index); - } - - public long getLong(int index) throws JSONException { - return baseArgs.getLong(index); - } - - public String getString(int index) throws JSONException { - return baseArgs.getString(index); - } - - - public Object opt(int index) { - return baseArgs.opt(index); - } - - public boolean optBoolean(int index) { - return baseArgs.optBoolean(index); - } - - public double optDouble(int index) { - return baseArgs.optDouble(index); - } - - public int optInt(int index) { - return baseArgs.optInt(index); - } - - public JSONArray optJSONArray(int index) { - return baseArgs.optJSONArray(index); - } - - public JSONObject optJSONObject(int index) { - return baseArgs.optJSONObject(index); - } - - public long optLong(int index) { - return baseArgs.optLong(index); - } - - public String optString(int index) { - return baseArgs.optString(index); - } - - public boolean isNull(int index) { - return baseArgs.isNull(index); - } - - - // The interesting custom helpers. - public byte[] getArrayBuffer(int index) throws JSONException { - String encoded = baseArgs.getString(index); - return Base64.decode(encoded, Base64.DEFAULT); - } -} - - diff --git a/platforms/android/CordovaLib/src/org/apache/cordova/CordovaChromeClient.java b/platforms/android/CordovaLib/src/org/apache/cordova/CordovaChromeClient.java deleted file mode 100755 index 5977e00ec..000000000 --- a/platforms/android/CordovaLib/src/org/apache/cordova/CordovaChromeClient.java +++ /dev/null @@ -1,396 +0,0 @@ -/* - 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. -*/ -package org.apache.cordova; - -import org.apache.cordova.CordovaInterface; -import org.apache.cordova.LOG; -import org.json.JSONArray; -import org.json.JSONException; - -import android.annotation.TargetApi; -import android.app.AlertDialog; -import android.content.DialogInterface; -import android.content.Intent; -import android.net.Uri; -import android.view.Gravity; -import android.view.KeyEvent; -import android.view.View; -import android.view.ViewGroup.LayoutParams; -import android.webkit.ConsoleMessage; -import android.webkit.JsPromptResult; -import android.webkit.JsResult; -import android.webkit.ValueCallback; -import android.webkit.WebChromeClient; -import android.webkit.WebStorage; -import android.webkit.WebView; -import android.webkit.GeolocationPermissions.Callback; -import android.widget.EditText; -import android.widget.LinearLayout; -import android.widget.ProgressBar; -import android.widget.RelativeLayout; - -/** - * This class is the WebChromeClient that implements callbacks for our web view. - * The kind of callbacks that happen here are on the chrome outside the document, - * such as onCreateWindow(), onConsoleMessage(), onProgressChanged(), etc. Related - * to but different than CordovaWebViewClient. - * - * @see WebChromeClient - * @see WebView guide - * @see CordovaWebViewClient - * @see CordovaWebView - */ -public class CordovaChromeClient extends WebChromeClient { - - public static final int FILECHOOSER_RESULTCODE = 5173; - private static final String LOG_TAG = "CordovaChromeClient"; - private String TAG = "CordovaLog"; - private long MAX_QUOTA = 100 * 1024 * 1024; - protected CordovaInterface cordova; - protected CordovaWebView appView; - - // the video progress view - private View mVideoProgressView; - - // File Chooser - public ValueCallback mUploadMessage; - - /** - * Constructor. - * - * @param cordova - */ - public CordovaChromeClient(CordovaInterface cordova) { - this.cordova = cordova; - } - - /** - * Constructor. - * - * @param ctx - * @param app - */ - public CordovaChromeClient(CordovaInterface ctx, CordovaWebView app) { - this.cordova = ctx; - this.appView = app; - } - - /** - * Constructor. - * - * @param view - */ - public void setWebView(CordovaWebView view) { - this.appView = view; - } - - /** - * Tell the client to display a javascript alert dialog. - * - * @param view - * @param url - * @param message - * @param result - */ - @Override - public boolean onJsAlert(WebView view, String url, String message, final JsResult result) { - AlertDialog.Builder dlg = new AlertDialog.Builder(this.cordova.getActivity()); - dlg.setMessage(message); - dlg.setTitle("Alert"); - //Don't let alerts break the back button - dlg.setCancelable(true); - dlg.setPositiveButton(android.R.string.ok, - new AlertDialog.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - result.confirm(); - } - }); - dlg.setOnCancelListener( - new DialogInterface.OnCancelListener() { - public void onCancel(DialogInterface dialog) { - result.cancel(); - } - }); - dlg.setOnKeyListener(new DialogInterface.OnKeyListener() { - //DO NOTHING - public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_BACK) - { - result.confirm(); - return false; - } - else - return true; - } - }); - dlg.create(); - dlg.show(); - return true; - } - - /** - * Tell the client to display a confirm dialog to the user. - * - * @param view - * @param url - * @param message - * @param result - */ - @Override - public boolean onJsConfirm(WebView view, String url, String message, final JsResult result) { - AlertDialog.Builder dlg = new AlertDialog.Builder(this.cordova.getActivity()); - dlg.setMessage(message); - dlg.setTitle("Confirm"); - dlg.setCancelable(true); - dlg.setPositiveButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - result.confirm(); - } - }); - dlg.setNegativeButton(android.R.string.cancel, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - result.cancel(); - } - }); - dlg.setOnCancelListener( - new DialogInterface.OnCancelListener() { - public void onCancel(DialogInterface dialog) { - result.cancel(); - } - }); - dlg.setOnKeyListener(new DialogInterface.OnKeyListener() { - //DO NOTHING - public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_BACK) - { - result.cancel(); - return false; - } - else - return true; - } - }); - dlg.create(); - dlg.show(); - return true; - } - - /** - * Tell the client to display a prompt dialog to the user. - * If the client returns true, WebView will assume that the client will - * handle the prompt dialog and call the appropriate JsPromptResult method. - * - * Since we are hacking prompts for our own purposes, we should not be using them for - * this purpose, perhaps we should hack console.log to do this instead! - * - * @param view - * @param url - * @param message - * @param defaultValue - * @param result - */ - @Override - public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) { - - // Security check to make sure any requests are coming from the page initially - // loaded in webview and not another loaded in an iframe. - boolean reqOk = false; - if (url.startsWith("file://") || Config.isUrlWhiteListed(url)) { - reqOk = true; - } - - // Calling PluginManager.exec() to call a native service using - // prompt(this.stringify(args), "gap:"+this.stringify([service, action, callbackId, true])); - if (reqOk && defaultValue != null && defaultValue.length() > 3 && defaultValue.substring(0, 4).equals("gap:")) { - JSONArray array; - try { - array = new JSONArray(defaultValue.substring(4)); - String service = array.getString(0); - String action = array.getString(1); - String callbackId = array.getString(2); - String r = this.appView.exposedJsApi.exec(service, action, callbackId, message); - result.confirm(r == null ? "" : r); - } catch (JSONException e) { - e.printStackTrace(); - return false; - } - } - - // Sets the native->JS bridge mode. - else if (reqOk && defaultValue != null && defaultValue.equals("gap_bridge_mode:")) { - try { - this.appView.exposedJsApi.setNativeToJsBridgeMode(Integer.parseInt(message)); - result.confirm(""); - } catch (NumberFormatException e){ - result.confirm(""); - e.printStackTrace(); - } - } - - // Polling for JavaScript messages - else if (reqOk && defaultValue != null && defaultValue.equals("gap_poll:")) { - String r = this.appView.exposedJsApi.retrieveJsMessages("1".equals(message)); - result.confirm(r == null ? "" : r); - } - - // Do NO-OP so older code doesn't display dialog - else if (defaultValue != null && defaultValue.equals("gap_init:")) { - result.confirm("OK"); - } - - // Show dialog - else { - final JsPromptResult res = result; - AlertDialog.Builder dlg = new AlertDialog.Builder(this.cordova.getActivity()); - dlg.setMessage(message); - final EditText input = new EditText(this.cordova.getActivity()); - if (defaultValue != null) { - input.setText(defaultValue); - } - dlg.setView(input); - dlg.setCancelable(false); - dlg.setPositiveButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - String usertext = input.getText().toString(); - res.confirm(usertext); - } - }); - dlg.setNegativeButton(android.R.string.cancel, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - res.cancel(); - } - }); - dlg.create(); - dlg.show(); - } - return true; - } - - /** - * Handle database quota exceeded notification. - */ - @Override - public void onExceededDatabaseQuota(String url, String databaseIdentifier, long currentQuota, long estimatedSize, - long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) - { - LOG.d(TAG, "onExceededDatabaseQuota estimatedSize: %d currentQuota: %d totalUsedQuota: %d", estimatedSize, currentQuota, totalUsedQuota); - quotaUpdater.updateQuota(MAX_QUOTA); - } - - // console.log in api level 7: http://developer.android.com/guide/developing/debug-tasks.html - // Expect this to not compile in a future Android release! - @SuppressWarnings("deprecation") - @Override - public void onConsoleMessage(String message, int lineNumber, String sourceID) - { - //This is only for Android 2.1 - if(android.os.Build.VERSION.SDK_INT == android.os.Build.VERSION_CODES.ECLAIR_MR1) - { - LOG.d(TAG, "%s: Line %d : %s", sourceID, lineNumber, message); - super.onConsoleMessage(message, lineNumber, sourceID); - } - } - - @TargetApi(8) - @Override - public boolean onConsoleMessage(ConsoleMessage consoleMessage) - { - if (consoleMessage.message() != null) - LOG.d(TAG, "%s: Line %d : %s" , consoleMessage.sourceId() , consoleMessage.lineNumber(), consoleMessage.message()); - return super.onConsoleMessage(consoleMessage); - } - - @Override - /** - * Instructs the client to show a prompt to ask the user to set the Geolocation permission state for the specified origin. - * - * @param origin - * @param callback - */ - public void onGeolocationPermissionsShowPrompt(String origin, Callback callback) { - super.onGeolocationPermissionsShowPrompt(origin, callback); - callback.invoke(origin, true, false); - } - - // API level 7 is required for this, see if we could lower this using something else - @Override - public void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback) { - this.appView.showCustomView(view, callback); - } - - @Override - public void onHideCustomView() { - this.appView.hideCustomView(); - } - - @Override - /** - * Ask the host application for a custom progress view to show while - * a