master 0ba9a65318d3 cached
64 files
368.8 KB
94.0k tokens
11 symbols
1 requests
Download .txt
Showing preview only (392K chars total). Download the full file or copy to clipboard to get everything.
Repository: driftyco/cordova-plugin-wkwebview-engine
Branch: master
Commit: 0ba9a65318d3
Files: 64
Total size: 368.8 KB

Directory structure:
gitextract_ak8tiifl/

├── .appveyor.yml
├── .eslintrc.yml
├── .gitignore
├── .jshintrc
├── .travis.yml
├── LICENSE
├── README.md
├── RELEASENOTES.md
├── package.json
├── plugin.xml
├── src/
│   ├── ios/
│   │   ├── CDVWKProcessPoolFactory.h
│   │   ├── CDVWKProcessPoolFactory.m
│   │   ├── CDVWKWebViewEngine.h
│   │   ├── CDVWKWebViewEngine.m
│   │   ├── CDVWKWebViewUIDelegate.h
│   │   ├── CDVWKWebViewUIDelegate.m
│   │   ├── GCDWebServer/
│   │   │   ├── Core/
│   │   │   │   ├── GCDWebServer.h
│   │   │   │   ├── GCDWebServer.m
│   │   │   │   ├── GCDWebServerConnection.h
│   │   │   │   ├── GCDWebServerConnection.m
│   │   │   │   ├── GCDWebServerFunctions.h
│   │   │   │   ├── GCDWebServerFunctions.m
│   │   │   │   ├── GCDWebServerHTTPStatusCodes.h
│   │   │   │   ├── GCDWebServerPrivate.h
│   │   │   │   ├── GCDWebServerRequest.h
│   │   │   │   ├── GCDWebServerRequest.m
│   │   │   │   ├── GCDWebServerResponse.h
│   │   │   │   └── GCDWebServerResponse.m
│   │   │   ├── Requests/
│   │   │   │   ├── GCDWebServerDataRequest.h
│   │   │   │   ├── GCDWebServerDataRequest.m
│   │   │   │   ├── GCDWebServerFileRequest.h
│   │   │   │   ├── GCDWebServerFileRequest.m
│   │   │   │   ├── GCDWebServerMultiPartFormRequest.h
│   │   │   │   ├── GCDWebServerMultiPartFormRequest.m
│   │   │   │   ├── GCDWebServerURLEncodedFormRequest.h
│   │   │   │   └── GCDWebServerURLEncodedFormRequest.m
│   │   │   └── Responses/
│   │   │       ├── GCDWebServerDataResponse.h
│   │   │       ├── GCDWebServerDataResponse.m
│   │   │       ├── GCDWebServerErrorResponse.h
│   │   │       ├── GCDWebServerErrorResponse.m
│   │   │       ├── GCDWebServerFileResponse.h
│   │   │       ├── GCDWebServerFileResponse.m
│   │   │       ├── GCDWebServerStreamedResponse.h
│   │   │       └── GCDWebServerStreamedResponse.m
│   │   ├── LICENSE
│   │   └── wk-plugin.js
│   └── www/
│       └── ios/
│           └── ios-wkwebview-exec.js
└── tests/
    ├── ios/
    │   ├── CDVWKWebViewEngineTest/
    │   │   ├── .gitignore
    │   │   ├── CDVWKWebViewEngineLibTests/
    │   │   │   ├── CDVWKWebViewEngineTest.m
    │   │   │   └── Info.plist
    │   │   └── CDVWKWebViewEngineTest.xcodeproj/
    │   │       ├── project.pbxproj
    │   │       ├── project.xcworkspace/
    │   │       │   ├── contents.xcworkspacedata
    │   │       │   └── xcshareddata/
    │   │       │       └── CDVWKWebViewEngineTest.xccheckout
    │   │       └── xcshareddata/
    │   │           └── xcschemes/
    │   │               ├── CDVWKWebViewEngineLib.xcscheme
    │   │               └── CDVWKWebViewEngineLibTests.xcscheme
    │   ├── CDVWKWebViewEngineTest.xcworkspace/
    │   │   ├── contents.xcworkspacedata
    │   │   └── xcshareddata/
    │   │       ├── CDVWKWebViewEngineTest.xccheckout
    │   │       └── xcschemes/
    │   │           └── CordovaLib.xcscheme
    │   ├── README.md
    │   ├── package.json
    │   └── test.xcconfig
    ├── package.json
    ├── plugin.xml
    └── tests.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .appveyor.yml
================================================
# appveyor file
# http://www.appveyor.com/docs/appveyor-yml

max_jobs: 1

shallow_clone: true

init:
  - git config --global core.autocrlf true

image:
  - Visual Studio 2017

environment:
  nodejs_version: "4"
  matrix:
    - PLATFORM: windows-10-store

install:
  - npm cache clean -f
  - node --version
  - npm install -g cordova-paramedic@https://github.com/apache/cordova-paramedic.git
  - npm install -g cordova

build: off

test_script:
  - cordova-paramedic --config pr\%PLATFORM% --plugin . --justBuild


================================================
FILE: .eslintrc.yml
================================================
root: true
extends: semistandard
rules:
  indent:
    - error
    - 4
  camelcase: off
  padded-blocks: off
  operator-linebreak: off
  no-throw-literal: off

================================================
FILE: .gitignore
================================================
#If ignorance is bliss, then somebody knock the smile off my face

*.csproj.user
*.suo
*.cache
Thumbs.db
*.DS_Store

*.bak
*.cache
*.log
*.swp
*.user

node_modules
xcuserdata



================================================
FILE: .jshintrc
================================================
{
    "browser": true
  , "devel": true
  , "bitwise": true
  , "undef": true
  , "trailing": true
  , "quotmark": false
  , "indent": 4
  , "unused": "vars"
  , "latedef": "nofunc"
  , "globals": {
        "module": false,
        "exports": false,
        "require": false
    }
}


================================================
FILE: .travis.yml
================================================
osx_image: xcode8
language: objective-c
sudo: false
before_install:
    - rvm get head
    - npm cache clean -f
    - npm install -g n
    - n stable
    - node --version
    - xcodebuild -version
install:
    - npm install
script:
    - npm test


================================================
FILE: LICENSE
================================================

                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [yyyy] [name of copyright owner]

   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.

================================================
FILE: README.md
================================================
# DEPRECATED

Repo moved to: [https://github.com/ionic-team/cordova-plugin-ionic-webview](https://github.com/ionic-team/cordova-plugin-ionic-webview)


================================================
FILE: RELEASENOTES.md
================================================
<!--
#
# 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.
#
-->

# Release Notes

### 1.1.3 (Apr 27, 2017)
* [CB-12696](https://issues.apache.org/jira/browse/CB-12696) (iOS) Fixing some Xcode warnings
* [CB-12685](https://issues.apache.org/jira/browse/CB-12685) added `package.json` to tests folder
* [CB-12575](https://issues.apache.org/jira/browse/CB-12575) cordova-plugin-wkwebview-engine missing LICENSE file
* [CB-12519](https://issues.apache.org/jira/browse/CB-12519) added missing license header

### 1.1.2 (Feb 28, 2017)
* [CB-12497](https://issues.apache.org/jira/browse/CB-12497) `location.href` links are silently disallowed
* [CB-12490](https://issues.apache.org/jira/browse/CB-12490) - Updated experimental plugin link
* Allow to configure navigation by gestures
* [CB-12297](https://issues.apache.org/jira/browse/CB-12297) Support `WKProcessPool` for cookie sharing
* [CB-12414](https://issues.apache.org/jira/browse/CB-12414) **iOS:** Forward error from provisional load error to standard load error

### 1.1.1 (Dec 07, 2016)
* [CB-12224](https://issues.apache.org/jira/browse/CB-12224) Updated version and RELEASENOTES.md for release 1.1.1
* [CB-10228](https://issues.apache.org/jira/browse/CB-10228) - AppendUserAgent not working with WKWebView
* [CB-11997](https://issues.apache.org/jira/browse/CB-11997) - Add crash recovery for iOS 8
* [CB-11917](https://issues.apache.org/jira/browse/CB-11917) - Remove pull request template checklist item: "iCLA has been submitted…"
* [CB-11818](https://issues.apache.org/jira/browse/CB-11818) - Avoid retain cycle: WKUserContentController retains its message handler, to break it we cannot pass directly CDVWKWebViewEngine's instance
* [CB-11832](https://issues.apache.org/jira/browse/CB-11832) Incremented plugin version.


### 1.1.0 (Sep 08, 2016)
* [CB-11824](https://issues.apache.org/jira/browse/CB-11824) - Update tests to include objective-c tests
* [CB-11554](https://issues.apache.org/jira/browse/CB-11554) - fixed unit tests
* [CB-11815](https://issues.apache.org/jira/browse/CB-11815) (**iOS**) Fix hard-coded bridge name "cordova"
* [CB-11554](https://issues.apache.org/jira/browse/CB-11554) - too 'brutal' app reload when title is empty
* [CB-11074](https://issues.apache.org/jira/browse/CB-11074) - Ensure settings from `config.xml` are taken into consideration
* Add ability to set the deceleration rate for the scrollview to 'fast'
* [CB-11496](https://issues.apache.org/jira/browse/CB-11496) - Add obj-c unit tests for `WKWebViewConfiguration`, `WKPreference`
* [CB-11496](https://issues.apache.org/jira/browse/CB-11496) - Create Obj-C unit-tests for `wkwebview-engine` (fix linker error)
* [CB-11452](https://issues.apache.org/jira/browse/CB-11452) - Update README.md with latest news about `AllowInlineMediaPlayback` fix
* [CB-9888](https://issues.apache.org/jira/browse/CB-9888) (**iOS**) check & reload `WKWebView`
* [CB-11375](https://issues.apache.org/jira/browse/CB-11375) - `onReset` method of `CDVPlugin` is never called
* Add pull request template.
* [CB-10818](https://issues.apache.org/jira/browse/CB-10818) - Support the scroll deceleration speed preference.
* [CB-10817](https://issues.apache.org/jira/browse/CB-10817) - Will now reload the `webView` if a crash occurs

### 1.0.3 (Apr 15, 2016)
* [CB-10636](https://issues.apache.org/jira/browse/CB-10636) Add `JSHint` for plugins

### 1.0.2 (Feb 09, 2016)
* [CB-10269](https://issues.apache.org/jira/browse/CB-10269) - Replace cordova exec only when present in wkwebview
* [CB-10202](https://issues.apache.org/jira/browse/CB-10202) - Add README quirk about WKWebview does not work with the AllowInlineMediaPlayback preference


### 1.0.1 (Dec 11, 2015)

* [CB-10190](https://issues.apache.org/jira/browse/CB-10190) - WKWebView engine is not releasing the user-agent lock

### 1.0.0 (Dec 04, 2015)

* [CB-10146](https://issues.apache.org/jira/browse/CB-10146) - Add to README WKWebViewEngine quirks that will affect migration from UIWebView
* [CB-10133](https://issues.apache.org/jira/browse/CB-10133) - DataClone DOM Exception 25 thrown for postMessage
* [CB-10106](https://issues.apache.org/jira/browse/CB-10106) - added bridge proxy
* [CB-10107](https://issues.apache.org/jira/browse/CB-10107) - nativeEvalAndFetch called for all bridges
* [CB-10106](https://issues.apache.org/jira/browse/CB-10106) - iOS bridges need to take into account bridge changes
* [CB-10073](https://issues.apache.org/jira/browse/CB-10073) - WKWebViewEngine should post CDVPluginResetNotification
* [CB-10035](https://issues.apache.org/jira/browse/CB-10035) Updated RELEASENOTES to be newest to oldest
* [CB-10002](https://issues.apache.org/jira/browse/CB-10002) - WKWebView should propagate shouldOverrideLoadWithRequest to plugins
* [CB-9979](https://issues.apache.org/jira/browse/CB-9979) [CB-9972](https://issues.apache.org/jira/browse/CB-9972) Change ATS link to new link
* [CB-9636](https://issues.apache.org/jira/browse/CB-9636) - Plugin should detect at runtime iOS 8 and use of file:// url and present an error
* [CB-8839](https://issues.apache.org/jira/browse/CB-8839) - WKWebView ignores DisallowOverscroll preference
* [CB-8556](https://issues.apache.org/jira/browse/CB-8556) - fix handleOpenURL for WKWebViewEngine plugin
* [CB-8666](https://issues.apache.org/jira/browse/CB-8666) - Update CDVWKWebViewEngine plugin to use 4.0.x branch code




================================================
FILE: package.json
================================================
{
  "name": "cordova-plugin-wkwebview-engine",
  "version": "1.1.6",
  "description": "The official Apache Cordova WKWebView Engine Plugin",
  "main": "index.js",
  "repository": {
    "type": "git",
    "url": "https://github.com/ionic-team/cordova-plugin-wkwebview-engine"
  },
  "bugs": {
    "url": "https://github.com/ionic-team/cordova-plugin-wkwebview-engine/issues"
  },
  "keywords": [
    "cordova",
    "wkwebview"
  ],
  "scripts": {
    "test": "npm run eslint && npm run objc-tests",
    "objc-tests": "cd tests/ios && npm test",
    "preobjc-tests": "cd tests/ios && npm install",
    "eslint": "node_modules/.bin/eslint src"
  },
  "author": "Apache Cordova",
  "license": "Apache-2.0",
  "devDependencies": {
    "eslint": "^3.19.0",
    "eslint-config-semistandard": "^11.0.0",
    "eslint-config-standard": "^10.2.1",
    "eslint-plugin-import": "^2.3.0",
    "eslint-plugin-node": "^5.0.0",
    "eslint-plugin-promise": "^3.5.0",
    "eslint-plugin-standard": "^3.0.1"
  }
}


================================================
FILE: plugin.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>

<!--
  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.
-->

<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0"
    xmlns:rim="http://www.blackberry.com/ns/widgets"
    xmlns:android="http://schemas.android.com/apk/res/android"
    id="cordova-plugin-wkwebview-engine"
    version="1.1.6">
    <name>Cordova WKWebView Engine</name>
    <description>Cordova WKWebView Engine Plugin</description>
    <license>Apache 2.0</license>
    <keywords>cordova,wkwebview,webview</keywords>
    <repo>https://git-wip-us.apache.org/repos/asf/cordova-plugin-wkwebview-engine.git</repo>

	<engines>
        <engine name="cordova-ios" version=">=4.0.0-dev" />
        <engine name="apple-ios" version=">=9.0" />
	</engines>

    <!-- ios -->
    <platform name="ios">
        <js-module src="src/www/ios/ios-wkwebview-exec.js" name="ios-wkwebview-exec">
            <clobbers target="cordova.exec" />
        </js-module>

        <config-file target="config.xml" parent="/*">
            <allow-navigation href="http://localhost:8080/*"/>
            <feature name="CDVWKWebViewEngine">
                <param name="ios-package" value="CDVWKWebViewEngine" />
            </feature>
            <preference name="CordovaWebViewEngine" value="CDVWKWebViewEngine" />
        </config-file>

        <framework src="WebKit.framework" weak="true" />

        <header-file src="src/ios/CDVWKWebViewEngine.h" />
        <source-file src="src/ios/CDVWKWebViewEngine.m" />
        <header-file src="src/ios/CDVWKWebViewUIDelegate.h" />
        <source-file src="src/ios/CDVWKWebViewUIDelegate.m" />
        <header-file src="src/ios/CDVWKProcessPoolFactory.h" />
        <source-file src="src/ios/CDVWKProcessPoolFactory.m" />
        <asset src="src/ios/wk-plugin.js" target="wk-plugin.js" />

        <!--GCDWebServer headers-->
        <header-file src="src/ios/GCDWebServer/Core/GCDWebServer.h" />
        <header-file src="src/ios/GCDWebServer/Core/GCDWebServerConnection.h" />
        <header-file src="src/ios/GCDWebServer/Core/GCDWebServerFunctions.h" />
        <header-file src="src/ios/GCDWebServer/Core/GCDWebServerHTTPStatusCodes.h" />
        <header-file src="src/ios/GCDWebServer/Core/GCDWebServerPrivate.h" />
        <header-file src="src/ios/GCDWebServer/Core/GCDWebServerRequest.h" />
        <header-file src="src/ios/GCDWebServer/Core/GCDWebServerResponse.h" />
        <header-file src="src/ios/GCDWebServer/Requests/GCDWebServerDataRequest.h" />
        <header-file src="src/ios/GCDWebServer/Requests/GCDWebServerFileRequest.h" />
        <header-file src="src/ios/GCDWebServer/Requests/GCDWebServerMultiPartFormRequest.h" />
        <header-file src="src/ios/GCDWebServer/Requests/GCDWebServerURLEncodedFormRequest.h" />
        <header-file src="src/ios/GCDWebServer/Responses/GCDWebServerDataResponse.h" />
        <header-file src="src/ios/GCDWebServer/Responses/GCDWebServerErrorResponse.h" />
        <header-file src="src/ios/GCDWebServer/Responses/GCDWebServerFileResponse.h" />
        <header-file src="src/ios/GCDWebServer/Responses/GCDWebServerStreamedResponse.h" />

        <!--GCDWebServer source-->
        <source-file src="src/ios/GCDWebServer/Core/GCDWebServer.m" />
        <source-file src="src/ios/GCDWebServer/Core/GCDWebServerConnection.m" />
        <source-file src="src/ios/GCDWebServer/Core/GCDWebServerFunctions.m" />
        <source-file src="src/ios/GCDWebServer/Core/GCDWebServerRequest.m" />
        <source-file src="src/ios/GCDWebServer/Core/GCDWebServerResponse.m" />
        <source-file src="src/ios/GCDWebServer/Requests/GCDWebServerDataRequest.m" />
        <source-file src="src/ios/GCDWebServer/Requests/GCDWebServerFileRequest.m" />
        <source-file src="src/ios/GCDWebServer/Requests/GCDWebServerMultiPartFormRequest.m" />
        <source-file src="src/ios/GCDWebServer/Requests/GCDWebServerURLEncodedFormRequest.m" />
        <source-file src="src/ios/GCDWebServer/Responses/GCDWebServerDataResponse.m" />
        <source-file src="src/ios/GCDWebServer/Responses/GCDWebServerErrorResponse.m" />
        <source-file src="src/ios/GCDWebServer/Responses/GCDWebServerFileResponse.m" />
        <source-file src="src/ios/GCDWebServer/Responses/GCDWebServerStreamedResponse.m" />

        <!--GCDWebServer dependencies-->
        <framework src="libz.tbd" />

    </platform>
</plugin>


================================================
FILE: src/ios/CDVWKProcessPoolFactory.h
================================================
/*
 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.
 */

#import <WebKit/WebKit.h>

@interface CDVWKProcessPoolFactory : NSObject
@property (nonatomic, retain) WKProcessPool* sharedPool;

+(instancetype) sharedFactory;
-(WKProcessPool*) sharedProcessPool;
@end


================================================
FILE: src/ios/CDVWKProcessPoolFactory.m
================================================
/*
 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.
 */

#import <Foundation/Foundation.h>
#import <WebKit/WebKit.h>
#import "CDVWKProcessPoolFactory.h"

static CDVWKProcessPoolFactory *factory = nil;

@implementation CDVWKProcessPoolFactory

+ (instancetype)sharedFactory
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        factory = [[CDVWKProcessPoolFactory alloc] init];
    });
    
    return factory;
}

- (instancetype)init
{
    if (self = [super init]) {
        _sharedPool = [[WKProcessPool alloc] init];
    }
    return self;
}

- (WKProcessPool*) sharedProcessPool {
    return _sharedPool;
}
@end


================================================
FILE: src/ios/CDVWKWebViewEngine.h
================================================
/*
 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.
 */

#import <WebKit/WebKit.h>
#import <Cordova/CDV.h>

@interface CDVWKWebViewEngine : CDVPlugin <CDVWebViewEngineProtocol, WKScriptMessageHandler, WKNavigationDelegate>

@property (nonatomic, strong, readonly) id <WKUIDelegate> uiDelegate;

@end


================================================
FILE: src/ios/CDVWKWebViewEngine.m
================================================
/*
 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.
 */

#import "CDVWKWebViewEngine.h"
#import "CDVWKWebViewUIDelegate.h"
#import "CDVWKProcessPoolFactory.h"
#import <Cordova/NSDictionary+CordovaPreferences.h>
#import <MobileCoreServices/MobileCoreServices.h>
#import <objc/message.h>
#import "GCDWebServer.h"

#define CDV_LOCAL_SERVER @"http://localhost:8080"
#define CDV_BRIDGE_NAME @"cordova"
#define CDV_IONIC_STOP_SCROLL @"stopScroll"

#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000

@implementation UIScrollView (BugIOS11)

+ (void)load {
    if (@available(iOS 11.0, *)) {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            Class class = [self class];
            SEL originalSelector = @selector(init);
            SEL swizzledSelector = @selector(xxx_init);

            Method originalMethod = class_getInstanceMethod(class, originalSelector);
            Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

            BOOL didAddMethod =
            class_addMethod(class,
                            originalSelector,
                            method_getImplementation(swizzledMethod),
                            method_getTypeEncoding(swizzledMethod));

            if (didAddMethod) {
                class_replaceMethod(class,
                                    swizzledSelector,
                                    method_getImplementation(originalMethod),
                                    method_getTypeEncoding(originalMethod));
            } else {
                method_exchangeImplementations(originalMethod, swizzledMethod);
            }
        });
    }
}

#pragma mark - Method Swizzling

- (id)xxx_init {
    id a = [self xxx_init];
    if (@available(iOS 11.0, *)) {
        NSArray *stack = [NSThread callStackSymbols];
        for(NSString *trace in stack) {
            if([trace containsString:@"WebKit"]) {
                [a setContentInsetAdjustmentBehavior:UIScrollViewContentInsetAdjustmentNever];
                break;
            }
        }
    }
    return a;
}

@end

#endif


@interface CDVWKWeakScriptMessageHandler : NSObject <WKScriptMessageHandler>

@property (nonatomic, weak, readonly) id<WKScriptMessageHandler>scriptMessageHandler;

- (instancetype)initWithScriptMessageHandler:(id<WKScriptMessageHandler>)scriptMessageHandler;

@end


@interface CDVWKWebViewEngine ()

@property (nonatomic, strong, readwrite) UIView* engineWebView;
@property (nonatomic, strong, readwrite) id <WKUIDelegate> uiDelegate;
@property (nonatomic, weak) id <WKScriptMessageHandler> weakScriptMessageHandler;
@property (nonatomic, strong) GCDWebServer *webServer;
@property (nonatomic, readwrite) CGRect frame;

@end

// see forwardingTargetForSelector: selector comment for the reason for this pragma
#pragma clang diagnostic ignored "-Wprotocol"

@implementation CDVWKWebViewEngine

@synthesize engineWebView = _engineWebView;

- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super init];
    if (self) {
        if (NSClassFromString(@"WKWebView") == nil) {
            return nil;
        }
        if(!IsAtLeastiOSVersion(@"9.0")) {
            return nil;
        }
        self.frame = frame;
        self.webServer = [[GCDWebServer alloc] init];
        [self.webServer addGETHandlerForBasePath:@"/" directoryPath:@"/" indexFilename:nil cacheAge:3600 allowRangeRequests:YES];
        NSDictionary *options = @{
                                  GCDWebServerOption_Port: @(8080),
                                  GCDWebServerOption_BindToLocalhost: @(YES),
                                  GCDWebServerOption_ServerName: @"Ionic"
                                  };
        [self.webServer startWithOptions:options error:nil];
    }

    return self;
}

- (WKWebViewConfiguration*) createConfigurationFromSettings:(NSDictionary*)settings
{
    WKWebViewConfiguration* configuration = [[WKWebViewConfiguration alloc] init];
    configuration.processPool = [[CDVWKProcessPoolFactory sharedFactory] sharedProcessPool];
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000
    if(@available(iOS 10.0, *)) {
        configuration.mediaTypesRequiringUserActionForPlayback = WKAudiovisualMediaTypeNone;
    }else{
        configuration.mediaPlaybackRequiresUserAction = YES;
    }
#else
    configuration.mediaTypesRequiringUserActionForPlayback = WKAudiovisualMediaTypeNone;
#endif

    if (settings == nil) {
        return configuration;
    }
    configuration.allowsInlineMediaPlayback = [settings cordovaBoolSettingForKey:@"AllowInlineMediaPlayback" defaultValue:YES];
    configuration.suppressesIncrementalRendering = [settings cordovaBoolSettingForKey:@"SuppressesIncrementalRendering" defaultValue:NO];
    configuration.allowsAirPlayForMediaPlayback = [settings cordovaBoolSettingForKey:@"MediaPlaybackAllowsAirPlay" defaultValue:YES];
    return configuration;
}

- (void)pluginInitialize
{
    // viewController would be available now. we attempt to set all possible delegates to it, by default
    NSDictionary* settings = self.commandDelegate.settings;

    self.uiDelegate = [[CDVWKWebViewUIDelegate alloc] initWithTitle:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"]];

    CDVWKWeakScriptMessageHandler *weakScriptMessageHandler = [[CDVWKWeakScriptMessageHandler alloc] initWithScriptMessageHandler:self];

    WKUserContentController* userContentController = [[WKUserContentController alloc] init];
    [userContentController addScriptMessageHandler:weakScriptMessageHandler name:CDV_BRIDGE_NAME];
    [userContentController addScriptMessageHandler:weakScriptMessageHandler name:CDV_IONIC_STOP_SCROLL];

    // Inject XHR Polyfill
    NSLog(@"CDVWKWebViewEngine: trying to inject XHR polyfill");
    WKUserScript *wkScript = [self wkPluginScript];
    if (wkScript) {
        [userContentController addUserScript:wkScript];
    }

    WKUserScript *configScript = [self configScript];
    if (configScript) {
        [userContentController addUserScript:configScript];
    }

    BOOL autoCordova = [settings cordovaBoolSettingForKey:@"AutoInjectCordova" defaultValue:NO];
    if(autoCordova){
        NSLog(@"CDVWKWebViewEngine: trying to inject XHR polyfill");
        WKUserScript *cordova = [self autoCordovify];
        if (cordova) {
            [userContentController addUserScript:cordova];
        }
    }


    WKWebViewConfiguration* configuration = [self createConfigurationFromSettings:settings];
    configuration.userContentController = userContentController;

    // re-create WKWebView, since we need to update configuration
    WKWebView* wkWebView = [[WKWebView alloc] initWithFrame:self.frame configuration:configuration];

    #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000
    if (@available(iOS 11.0, *)) {
      [wkWebView.scrollView setContentInsetAdjustmentBehavior:UIScrollViewContentInsetAdjustmentNever];
    }
    #endif

    wkWebView.UIDelegate = self.uiDelegate;
    self.engineWebView = wkWebView;

    if (IsAtLeastiOSVersion(@"9.0") && [self.viewController isKindOfClass:[CDVViewController class]]) {
        wkWebView.customUserAgent = ((CDVViewController*) self.viewController).userAgent;
    }

    if ([self.viewController conformsToProtocol:@protocol(WKUIDelegate)]) {
        wkWebView.UIDelegate = (id <WKUIDelegate>)self.viewController;
    }

    if ([self.viewController conformsToProtocol:@protocol(WKNavigationDelegate)]) {
        wkWebView.navigationDelegate = (id <WKNavigationDelegate>)self.viewController;
    } else {
        wkWebView.navigationDelegate = (id <WKNavigationDelegate>)self;
    }

    if ([self.viewController conformsToProtocol:@protocol(WKScriptMessageHandler)]) {
        [wkWebView.configuration.userContentController addScriptMessageHandler:(id < WKScriptMessageHandler >)self.viewController name:CDV_BRIDGE_NAME];
    }

    if (![settings cordovaBoolSettingForKey:@"KeyboardDisplayRequiresUserAction" defaultValue:YES]) {
        [self keyboardDisplayDoesNotRequireUserAction];
    }

    [self updateSettings:settings];

    // check if content thread has died on resume
    NSLog(@"%@", @"CDVWKWebViewEngine will reload WKWebView if required on resume");
    [[NSNotificationCenter defaultCenter]
     addObserver:self
     selector:@selector(onAppWillEnterForeground:)
     name:UIApplicationWillEnterForegroundNotification object:nil];

    NSLog(@"Using Ionic WKWebView");

    [self addURLObserver];
}

// https://github.com/Telerik-Verified-Plugins/WKWebView/commit/04e8296adeb61f289f9c698045c19b62d080c7e3#L609-L620
- (void) keyboardDisplayDoesNotRequireUserAction {
		SEL sel = sel_getUid("_startAssistingNode:userIsInteracting:blurPreviousNode:userObject:");
  	Class WKContentView = NSClassFromString(@"WKContentView");
  	Method method = class_getInstanceMethod(WKContentView, sel);
  	IMP originalImp = method_getImplementation(method);
  	IMP imp = imp_implementationWithBlock(^void(id me, void* arg0, BOOL arg1, BOOL arg2, id arg3) {
    		((void (*)(id, SEL, void*, BOOL, BOOL, id))originalImp)(me, sel, arg0, TRUE, arg2, arg3);
  	});
  	method_setImplementation(method, imp);
}

- (void)onReset {
    [self addURLObserver];
}

static void * KVOContext = &KVOContext;

- (void)addURLObserver {
    if(!IsAtLeastiOSVersion(@"9.0")){
        [self.webView addObserver:self forKeyPath:@"URL" options:0 context:KVOContext];
    }
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
{
    if (context == KVOContext) {
        if (object == [self webView] && [keyPath isEqualToString: @"URL"] && [object valueForKeyPath:keyPath] == nil){
            NSLog(@"URL is nil. Reloading WKWebView");
            [(WKWebView*)_engineWebView reload];
        }
    } else {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}

- (void) onAppWillEnterForeground:(NSNotification*)notification {
    if ([self shouldReloadWebView]) {
        NSLog(@"%@", @"CDVWKWebViewEngine reloading!");
        [(WKWebView*)_engineWebView reload];
    }
}

- (BOOL)shouldReloadWebView
{
    WKWebView* wkWebView = (WKWebView*)_engineWebView;
    return [self shouldReloadWebView:wkWebView.URL title:wkWebView.title];
}

- (BOOL)shouldReloadWebView:(NSURL*)location title:(NSString*)title
{
    BOOL title_is_nil = (title == nil);
    BOOL location_is_blank = [[location absoluteString] isEqualToString:@"about:blank"];

    BOOL reload = (title_is_nil || location_is_blank);

#ifdef DEBUG
    NSLog(@"%@", @"CDVWKWebViewEngine shouldReloadWebView::");
    NSLog(@"CDVWKWebViewEngine shouldReloadWebView title: %@", title);
    NSLog(@"CDVWKWebViewEngine shouldReloadWebView location: %@", [location absoluteString]);
    NSLog(@"CDVWKWebViewEngine shouldReloadWebView reload: %u", reload);
#endif

    return reload;
}


- (id)loadRequest:(NSURLRequest*)request
{
    if (request.URL.fileURL) {
        NSURL *url = [[NSURL URLWithString:CDV_LOCAL_SERVER] URLByAppendingPathComponent:request.URL.path];
        if(request.URL.query) {
            url = [NSURL URLWithString:[@"?" stringByAppendingString:request.URL.query] relativeToURL:url];
        }
        if(request.URL.fragment) {
            url = [NSURL URLWithString:[@"#" stringByAppendingString:request.URL.fragment] relativeToURL:url];
        }
        request = [NSURLRequest requestWithURL:url];
    }
    return [(WKWebView*)_engineWebView loadRequest:request];
}

- (id)loadHTMLString:(NSString*)string baseURL:(NSURL*)baseURL
{
    return [(WKWebView*)_engineWebView loadHTMLString:string baseURL:baseURL];
}

- (NSURL*) URL
{
    return [(WKWebView*)_engineWebView URL];
}

- (BOOL)canLoadRequest:(NSURLRequest *)request
{
    return TRUE;
}

- (void)updateSettings:(NSDictionary*)settings
{
    WKWebView* wkWebView = (WKWebView*)_engineWebView;

    // By default, DisallowOverscroll is false (thus bounce is allowed)
    BOOL bounceAllowed = !([settings cordovaBoolSettingForKey:@"DisallowOverscroll" defaultValue:NO]);

    // prevent webView from bouncing
    if (!bounceAllowed) {
        if ([wkWebView respondsToSelector:@selector(scrollView)]) {
            ((UIScrollView*)[wkWebView scrollView]).bounces = NO;
        } else {
            for (id subview in wkWebView.subviews) {
                if ([[subview class] isSubclassOfClass:[UIScrollView class]]) {
                    ((UIScrollView*)subview).bounces = NO;
                }
            }
        }
    }

    wkWebView.configuration.preferences.minimumFontSize = [settings cordovaFloatSettingForKey:@"MinimumFontSize" defaultValue:0.0];
    wkWebView.allowsLinkPreview = [settings cordovaBoolSettingForKey:@"AllowLinkPreview" defaultValue:NO];
    wkWebView.scrollView.scrollEnabled = [settings cordovaBoolSettingForKey:@"ScrollEnabled" defaultValue:NO];
    wkWebView.allowsBackForwardNavigationGestures = [settings cordovaBoolSettingForKey:@"AllowBackForwardNavigationGestures" defaultValue:NO];
}

- (void)updateWithInfo:(NSDictionary*)info
{
    NSDictionary* scriptMessageHandlers = [info objectForKey:kCDVWebViewEngineScriptMessageHandlers];
    NSDictionary* settings = [info objectForKey:kCDVWebViewEngineWebViewPreferences];
    id navigationDelegate = [info objectForKey:kCDVWebViewEngineWKNavigationDelegate];
    id uiDelegate = [info objectForKey:kCDVWebViewEngineWKUIDelegate];

    WKWebView* wkWebView = (WKWebView*)_engineWebView;

    if (scriptMessageHandlers && [scriptMessageHandlers isKindOfClass:[NSDictionary class]]) {
        NSArray* allKeys = [scriptMessageHandlers allKeys];

        for (NSString* key in allKeys) {
            id object = [scriptMessageHandlers objectForKey:key];
            if ([object conformsToProtocol:@protocol(WKScriptMessageHandler)]) {
                [wkWebView.configuration.userContentController addScriptMessageHandler:object name:key];
            }
        }
    }

    if (navigationDelegate && [navigationDelegate conformsToProtocol:@protocol(WKNavigationDelegate)]) {
        wkWebView.navigationDelegate = navigationDelegate;
    }

    if (uiDelegate && [uiDelegate conformsToProtocol:@protocol(WKUIDelegate)]) {
        wkWebView.UIDelegate = uiDelegate;
    }

    if (settings && [settings isKindOfClass:[NSDictionary class]]) {
        [self updateSettings:settings];
    }
}

// This forwards the methods that are in the header that are not implemented here.
// Both WKWebView and UIWebView implement the below:
//     loadHTMLString:baseURL:
//     loadRequest:
- (id)forwardingTargetForSelector:(SEL)aSelector
{
    return _engineWebView;
}

- (UIView*)webView
{
    return self.engineWebView;
}

- (WKUserScript*)wkPluginScript
{
    NSString *scriptFile = [[NSBundle mainBundle] pathForResource:@"www/wk-plugin" ofType:@"js"];
    if (scriptFile == nil) {
        NSLog(@"CDVWKWebViewEngine: WK plugin was not found");
        return nil;
    }
    NSError *error = nil;
    NSString *source = [NSString stringWithContentsOfFile:scriptFile encoding:NSUTF8StringEncoding error:&error];
    if (source == nil || error != nil) {
        NSLog(@"CDVWKWebViewEngine: WK plugin can not be loaded: %@", error);
        return nil;
    }

    return [[WKUserScript alloc] initWithSource:source
                                  injectionTime:WKUserScriptInjectionTimeAtDocumentStart
                               forMainFrameOnly:YES];
}

- (WKUserScript*)configScript
{
    Class keyboard = NSClassFromString(@"CDVIonicKeyboard");
    BOOL keyboardPlugin = keyboard != nil;
    if(!keyboardPlugin) {
        return nil;
    }

    BOOL keyboardResizes = [self.commandDelegate.settings cordovaBoolSettingForKey:@"KeyboardResizes" defaultValue:YES];
    NSString *source = [NSString stringWithFormat:
                        @"window.Ionic = window.Ionic || {};"
                        @"window.Ionic.keyboardPlugin=true;"
                        @"window.Ionic.keyboardResizes=%@",
                        keyboardResizes ? @"true" : @"false"];

    return [[WKUserScript alloc] initWithSource:source
                                  injectionTime:WKUserScriptInjectionTimeAtDocumentStart
                               forMainFrameOnly:YES];
}

- (WKUserScript*)autoCordovify
{
    NSURL *cordovaURL = [[NSBundle mainBundle] URLForResource:@"www/cordova" withExtension:@"js"];
    if (cordovaURL == nil) {
        NSLog(@"CDVWKWebViewEngine: cordova.js WAS NOT FOUND");
        return nil;
    }
    NSError *error = nil;
    NSString *source = [NSString stringWithContentsOfURL:cordovaURL encoding:NSUTF8StringEncoding error:&error];
    if (source == nil || error != nil) {
        NSLog(@"CDVWKWebViewEngine: cordova.js can not be loaded: %@", error);
        return nil;
    }
    NSLog(@"CDVWKWebViewEngine: auto injecting cordova");
    NSString *cordovaPath = [CDV_LOCAL_SERVER stringByAppendingString:cordovaURL.URLByDeletingLastPathComponent.path];
    NSString *replacement = [NSString stringWithFormat:@"var pathPrefix = '%@/';", cordovaPath];
    source = [source stringByReplacingOccurrencesOfString:@"var pathPrefix = findCordovaPath();" withString:replacement];

    return [[WKUserScript alloc] initWithSource:source
                                  injectionTime:WKUserScriptInjectionTimeAtDocumentStart
                               forMainFrameOnly:YES];
}

#pragma mark WKScriptMessageHandler implementation

- (void)userContentController:(WKUserContentController*)userContentController didReceiveScriptMessage:(WKScriptMessage*)message
{
    if ([message.name isEqualToString:CDV_BRIDGE_NAME]) {
        [self handleCordovaMessage: message];
    } else if ([message.name isEqualToString:CDV_IONIC_STOP_SCROLL]) {
        [self handleStopScroll];
    }
}

- (void)handleCordovaMessage:(WKScriptMessage*)message
{
    CDVViewController* vc = (CDVViewController*)self.viewController;

    NSArray* jsonEntry = message.body; // NSString:callbackId, NSString:service, NSString:action, NSArray:args
    CDVInvokedUrlCommand* command = [CDVInvokedUrlCommand commandFromJson:jsonEntry];
    CDV_EXEC_LOG(@"Exec(%@): Calling %@.%@", command.callbackId, command.className, command.methodName);

    if (![vc.commandQueue execute:command]) {
#ifdef DEBUG
        NSError* error = nil;
        NSString* commandJson = nil;
        NSData* jsonData = [NSJSONSerialization dataWithJSONObject:jsonEntry
                                                           options:0
                                                             error:&error];

        if (error == nil) {
            commandJson = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
        }

        static NSUInteger maxLogLength = 1024;
        NSString* commandString = ([commandJson length] > maxLogLength) ?
        [NSString stringWithFormat : @"%@[...]", [commandJson substringToIndex:maxLogLength]] :
        commandJson;

        NSLog(@"FAILED pluginJSON = %@", commandString);
#endif
    }
}

- (void)handleStopScroll
{
    WKWebView* wkWebView = (WKWebView*)_engineWebView;
    NSLog(@"CDVWKWebViewEngine: handleStopScroll");
    [self recursiveStopScroll:[wkWebView scrollView]];
    [wkWebView evaluateJavaScript:@"window.IonicStopScroll.fire()" completionHandler:nil];
}

- (void)recursiveStopScroll:(UIView *)node
{
    if([node isKindOfClass: [UIScrollView class]]) {
        UIScrollView *nodeAsScroll = (UIScrollView *)node;

        if([nodeAsScroll isScrollEnabled] && ![nodeAsScroll isHidden]) {
            [nodeAsScroll setScrollEnabled: NO];
            [nodeAsScroll setScrollEnabled: YES];
        }
    }

    // iterate tree recursivelly
    for (UIView *child in [node subviews]) {
        [self recursiveStopScroll:child];
    }
}


#pragma mark WKNavigationDelegate implementation

- (void)webView:(WKWebView*)webView didStartProvisionalNavigation:(WKNavigation*)navigation
{
    [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVPluginResetNotification object:webView]];
}

- (void)webView:(WKWebView*)webView didFinishNavigation:(WKNavigation*)navigation
{
    CDVViewController* vc = (CDVViewController*)self.viewController;
    [CDVUserAgentUtil releaseLock:vc.userAgentLockToken];

    [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVPageDidLoadNotification object:webView]];
}

- (void)webView:(WKWebView*)theWebView didFailProvisionalNavigation:(WKNavigation*)navigation withError:(NSError*)error
{
    [self webView:theWebView didFailNavigation:navigation withError:error];
}

- (void)webView:(WKWebView*)theWebView didFailNavigation:(WKNavigation*)navigation withError:(NSError*)error
{
    CDVViewController* vc = (CDVViewController*)self.viewController;
    [CDVUserAgentUtil releaseLock:vc.userAgentLockToken];

    NSString* message = [NSString stringWithFormat:@"Failed to load webpage with error: %@", [error localizedDescription]];
    NSLog(@"%@", message);

    NSURL* errorUrl = vc.errorURL;
    if (errorUrl) {
        errorUrl = [NSURL URLWithString:[NSString stringWithFormat:@"?error=%@", [message stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]] relativeToURL:errorUrl];
        NSLog(@"%@", [errorUrl absoluteString]);
        [theWebView loadRequest:[NSURLRequest requestWithURL:errorUrl]];
    }
}

- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView
{
    [webView reload];
}

- (BOOL)defaultResourcePolicyForURL:(NSURL*)url
{
    // all file:// urls are allowed
    if ([url isFileURL]) {
        return YES;
    }

    return NO;
}


- (void) webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction*)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
{
    NSURL* url = [navigationAction.request URL];
    CDVViewController* vc = (CDVViewController*)self.viewController;

    /*
     * Give plugins the chance to handle the url
     */
    BOOL anyPluginsResponded = NO;
    BOOL shouldAllowRequest = NO;

    for (NSString* pluginName in vc.pluginObjects) {
        CDVPlugin* plugin = [vc.pluginObjects objectForKey:pluginName];
        SEL selector = NSSelectorFromString(@"shouldOverrideLoadWithRequest:navigationType:");
        if ([plugin respondsToSelector:selector]) {
            anyPluginsResponded = YES;
            // https://issues.apache.org/jira/browse/CB-12497
            int navType = (int)navigationAction.navigationType;
            if (WKNavigationTypeOther == navigationAction.navigationType) {
                navType = (int)UIWebViewNavigationTypeOther;
            }
            shouldAllowRequest = (((BOOL (*)(id, SEL, id, int))objc_msgSend)(plugin, selector, navigationAction.request, navType));
            if (!shouldAllowRequest) {
                break;
            }
        }
    }

    if (!anyPluginsResponded) {
        /*
         * Handle all other types of urls (tel:, sms:), and requests to load a url in the main webview.
         */
        shouldAllowRequest = [self defaultResourcePolicyForURL:url];
        if (!shouldAllowRequest) {
            [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVPluginHandleOpenURLNotification object:url]];
        }
    }


    if (shouldAllowRequest) {
        NSString *scheme = url.scheme;
        if ([scheme isEqualToString:@"tel"] ||
            [scheme isEqualToString:@"mailto"] ||
            [scheme isEqualToString:@"facetime"] ||
            [scheme isEqualToString:@"sms"] ||
            [scheme isEqualToString:@"maps"] ||
            [scheme isEqualToString:@"itms-services"]) {
            [[UIApplication sharedApplication] openURL:url];
            decisionHandler(WKNavigationActionPolicyCancel);
        } else {
            decisionHandler(WKNavigationActionPolicyAllow);
        }
    } else {
        decisionHandler(WKNavigationActionPolicyCancel);
    }
}

@end

#pragma mark - CDVWKWeakScriptMessageHandler

@implementation CDVWKWeakScriptMessageHandler

- (instancetype)initWithScriptMessageHandler:(id<WKScriptMessageHandler>)scriptMessageHandler
{
    self = [super init];
    if (self) {
        _scriptMessageHandler = scriptMessageHandler;
    }
    return self;
}

- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
{
    [self.scriptMessageHandler userContentController:userContentController didReceiveScriptMessage:message];
}

@end


================================================
FILE: src/ios/CDVWKWebViewUIDelegate.h
================================================
/*
 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.
 */

#import <WebKit/WebKit.h>

@interface CDVWKWebViewUIDelegate : NSObject <WKUIDelegate>

@property (nonatomic, copy) NSString* title;

- (instancetype)initWithTitle:(NSString*)title;

@end


================================================
FILE: src/ios/CDVWKWebViewUIDelegate.m
================================================
/*
 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.
 */

#import "CDVWKWebViewUIDelegate.h"

@implementation CDVWKWebViewUIDelegate

- (instancetype)initWithTitle:(NSString*)title
{
    self = [super init];
    if (self) {
        self.title = title;
    }

    return self;
}

- (void)     webView:(WKWebView*)webView runJavaScriptAlertPanelWithMessage:(NSString*)message
    initiatedByFrame:(WKFrameInfo*)frame completionHandler:(void (^)(void))completionHandler
{
    UIAlertController* alert = [UIAlertController alertControllerWithTitle:self.title
                                                                   message:message
                                                            preferredStyle:UIAlertControllerStyleAlert];

    UIAlertAction* ok = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", @"OK")
                                                 style:UIAlertActionStyleDefault
                                               handler:^(UIAlertAction* action)
        {
            completionHandler();
            [alert dismissViewControllerAnimated:YES completion:nil];
        }];

    [alert addAction:ok];

    UIViewController* rootController = [UIApplication sharedApplication].delegate.window.rootViewController;

    [rootController presentViewController:alert animated:YES completion:nil];
}

- (void)     webView:(WKWebView*)webView runJavaScriptConfirmPanelWithMessage:(NSString*)message
    initiatedByFrame:(WKFrameInfo*)frame completionHandler:(void (^)(BOOL result))completionHandler
{
    UIAlertController* alert = [UIAlertController alertControllerWithTitle:self.title
                                                                   message:message
                                                            preferredStyle:UIAlertControllerStyleAlert];

    UIAlertAction* ok = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", @"OK")
                                                 style:UIAlertActionStyleDefault
                                               handler:^(UIAlertAction* action)
        {
            completionHandler(YES);
            [alert dismissViewControllerAnimated:YES completion:nil];
        }];

    [alert addAction:ok];

    UIAlertAction* cancel = [UIAlertAction actionWithTitle:NSLocalizedString(@"Cancel", @"Cancel")
                                                     style:UIAlertActionStyleDefault
                                                   handler:^(UIAlertAction* action)
        {
            completionHandler(NO);
            [alert dismissViewControllerAnimated:YES completion:nil];
        }];
    [alert addAction:cancel];

    UIViewController* rootController = [UIApplication sharedApplication].delegate.window.rootViewController;

    [rootController presentViewController:alert animated:YES completion:nil];
}

- (void)      webView:(WKWebView*)webView runJavaScriptTextInputPanelWithPrompt:(NSString*)prompt
          defaultText:(NSString*)defaultText initiatedByFrame:(WKFrameInfo*)frame
    completionHandler:(void (^)(NSString* result))completionHandler
{
    UIAlertController* alert = [UIAlertController alertControllerWithTitle:self.title
                                                                   message:prompt
                                                            preferredStyle:UIAlertControllerStyleAlert];

    UIAlertAction* ok = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", @"OK")
                                                 style:UIAlertActionStyleDefault
                                               handler:^(UIAlertAction* action)
        {
            completionHandler(((UITextField*)alert.textFields[0]).text);
            [alert dismissViewControllerAnimated:YES completion:nil];
        }];

    [alert addAction:ok];

    UIAlertAction* cancel = [UIAlertAction actionWithTitle:NSLocalizedString(@"Cancel", @"Cancel")
                                                     style:UIAlertActionStyleDefault
                                                   handler:^(UIAlertAction* action)
        {
            completionHandler(nil);
            [alert dismissViewControllerAnimated:YES completion:nil];
        }];
    [alert addAction:cancel];

    [alert addTextFieldWithConfigurationHandler:^(UITextField* textField) {
        textField.text = defaultText;
    }];

    UIViewController* rootController = [UIApplication sharedApplication].delegate.window.rootViewController;

    [rootController presentViewController:alert animated:YES completion:nil];
}

@end


================================================
FILE: src/ios/GCDWebServer/Core/GCDWebServer.h
================================================
/*
 Copyright (c) 2012-2015, Pierre-Olivier Latour
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are met:
 * Redistributions of source code must retain the above copyright
 notice, this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright
 notice, this list of conditions and the following disclaimer in the
 documentation and/or other materials provided with the distribution.
 * The name of Pierre-Olivier Latour may not be used to endorse
 or promote products derived from this software without specific
 prior written permission.
 
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
 DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#import <TargetConditionals.h>

#import "GCDWebServerRequest.h"
#import "GCDWebServerResponse.h"

/**
 *  The GCDWebServerMatchBlock is called for every handler added to the
 *  GCDWebServer whenever a new HTTP request has started (i.e. HTTP headers have
 *  been received). The block is passed the basic info for the request (HTTP method,
 *  URL, headers...) and must decide if it wants to handle it or not.
 *
 *  If the handler can handle the request, the block must return a new
 *  GCDWebServerRequest instance created with the same basic info.
 *  Otherwise, it simply returns nil.
 */
typedef GCDWebServerRequest* (^GCDWebServerMatchBlock)(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery);

/**
 *  The GCDWebServerProcessBlock is called after the HTTP request has been fully
 *  received (i.e. the entire HTTP body has been read). The block is passed the
 *  GCDWebServerRequest created at the previous step by the GCDWebServerMatchBlock.
 *
 *  The block must return a GCDWebServerResponse or nil on error, which will
 *  result in a 500 HTTP status code returned to the client. It's however
 *  recommended to return a GCDWebServerErrorResponse on error so more useful
 *  information can be returned to the client.
 */
typedef GCDWebServerResponse* (^GCDWebServerProcessBlock)(__kindof GCDWebServerRequest* request);

/**
 *  The GCDWebServerAsynchronousProcessBlock works like the GCDWebServerProcessBlock
 *  except the GCDWebServerResponse can be returned to the server at a later time
 *  allowing for asynchronous generation of the response.
 *
 *  The block must eventually call "completionBlock" passing a GCDWebServerResponse
 *  or nil on error, which will result in a 500 HTTP status code returned to the client.
 *  It's however recommended to return a GCDWebServerErrorResponse on error so more
 *  useful information can be returned to the client.
 */
typedef void (^GCDWebServerCompletionBlock)(GCDWebServerResponse* response);
typedef void (^GCDWebServerAsyncProcessBlock)(__kindof GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock);

/**
 *  The port used by the GCDWebServer (NSNumber / NSUInteger).
 *
 *  The default value is 0 i.e. let the OS pick a random port.
 */
extern NSString* const GCDWebServerOption_Port;

/**
 *  The Bonjour name used by the GCDWebServer (NSString). If set to an empty string,
 *  the name will automatically take the value of the GCDWebServerOption_ServerName
 *  option. If this option is set to nil, Bonjour will be disabled.
 *
 *  The default value is nil.
 */
extern NSString* const GCDWebServerOption_BonjourName;

/**
 *  The Bonjour service type used by the GCDWebServer (NSString).
 *
 *  The default value is "_http._tcp", the service type for HTTP web servers.
 */
extern NSString* const GCDWebServerOption_BonjourType;

/**
 *  Request a port mapping in the NAT gateway (NSNumber / BOOL).
 *
 *  This uses the DNSService API under the hood which supports IPv4 mappings only.
 *
 *  The default value is NO.
 *
 *  @warning The external port set up by the NAT gateway may be different than
 *  the one used by the GCDWebServer.
 */
extern NSString* const GCDWebServerOption_RequestNATPortMapping;

/**
 *  Only accept HTTP requests coming from localhost i.e. not from the outside
 *  network (NSNumber / BOOL).
 *
 *  The default value is NO.
 *
 *  @warning Bonjour and NAT port mapping should be disabled if using this option
 *  since the server will not be reachable from the outside network anyway.
 */
extern NSString* const GCDWebServerOption_BindToLocalhost;

/**
 *  The maximum number of incoming HTTP requests that can be queued waiting to
 *  be handled before new ones are dropped (NSNumber / NSUInteger).
 *
 *  The default value is 16.
 */
extern NSString* const GCDWebServerOption_MaxPendingConnections;

/**
 *  The value for "Server" HTTP header used by the GCDWebServer (NSString).
 *
 *  The default value is the GCDWebServer class name.
 */
extern NSString* const GCDWebServerOption_ServerName;

/**
 *  The authentication method used by the GCDWebServer
 *  (one of "GCDWebServerAuthenticationMethod_...").
 *
 *  The default value is nil i.e. authentication is disabled.
 */
extern NSString* const GCDWebServerOption_AuthenticationMethod;

/**
 *  The authentication realm used by the GCDWebServer (NSString).
 *
 *  The default value is the same as the GCDWebServerOption_ServerName option.
 */
extern NSString* const GCDWebServerOption_AuthenticationRealm;

/**
 *  The authentication accounts used by the GCDWebServer
 *  (NSDictionary of username / password pairs).
 *
 *  The default value is nil i.e. no accounts.
 */
extern NSString* const GCDWebServerOption_AuthenticationAccounts;

/**
 *  The class used by the GCDWebServer when instantiating GCDWebServerConnection
 *  (subclass of GCDWebServerConnection).
 *
 *  The default value is the GCDWebServerConnection class.
 */
extern NSString* const GCDWebServerOption_ConnectionClass;

/**
 *  Allow the GCDWebServer to pretend "HEAD" requests are actually "GET" ones
 *  and automatically discard the HTTP body of the response (NSNumber / BOOL).
 *
 *  The default value is YES.
 */
extern NSString* const GCDWebServerOption_AutomaticallyMapHEADToGET;

/**
 *  The interval expressed in seconds used by the GCDWebServer to decide how to
 *  coalesce calls to -webServerDidConnect: and -webServerDidDisconnect:
 *  (NSNumber / double). Coalescing will be disabled if the interval is <= 0.0.
 *
 *  The default value is 1.0 second.
 */
extern NSString* const GCDWebServerOption_ConnectedStateCoalescingInterval;

/**
 *  Set the dispatch queue priority on which server connection will be 
 *  run (NSNumber / long).
 *
 *
 *  The default value is DISPATCH_QUEUE_PRIORITY_DEFAULT.
 */
extern NSString* const GCDWebServerOption_DispatchQueuePriority;

#if TARGET_OS_IPHONE

/**
 *  Enables the GCDWebServer to automatically suspend itself (as if -stop was
 *  called) when the iOS app goes into the background and the last
 *  GCDWebServerConnection is closed, then resume itself (as if -start was called)
 *  when the iOS app comes back to the foreground (NSNumber / BOOL).
 *
 *  See the README.md file for more information about this option.
 *
 *  The default value is YES.
 *
 *  @warning The running property will be NO while the GCDWebServer is suspended.
 */
extern NSString* const GCDWebServerOption_AutomaticallySuspendInBackground;

#endif

/**
 *  HTTP Basic Authentication scheme (see https://tools.ietf.org/html/rfc2617).
 *
 *  @warning Use of this authentication scheme is not recommended as the
 *  passwords are sent in clear.
 */
extern NSString* const GCDWebServerAuthenticationMethod_Basic;

/**
 *  HTTP Digest Access Authentication scheme (see https://tools.ietf.org/html/rfc2617).
 */
extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;

@class GCDWebServer;

/**
 *  Delegate methods for GCDWebServer.
 *
 *  @warning These methods are always called on the main thread in a serialized way.
 */
@protocol GCDWebServerDelegate <NSObject>
@optional

/**
 *  This method is called after the server has successfully started.
 */
- (void)webServerDidStart:(GCDWebServer*)server;

/**
 *  This method is called after the Bonjour registration for the server has
 *  successfully completed.
 *
 *  Use the "bonjourServerURL" property to retrieve the Bonjour address of the
 *  server.
 */
- (void)webServerDidCompleteBonjourRegistration:(GCDWebServer*)server;

/**
 *  This method is called after the NAT port mapping for the server has been
 *  updated.
 *
 *  Use the "publicServerURL" property to retrieve the public address of the
 *  server.
 */
- (void)webServerDidUpdateNATPortMapping:(GCDWebServer*)server;

/**
 *  This method is called when the first GCDWebServerConnection is opened by the
 *  server to serve a series of HTTP requests.
 *
 *  A series of HTTP requests is considered ongoing as long as new HTTP requests
 *  keep coming (and new GCDWebServerConnection instances keep being opened),
 *  until before the last HTTP request has been responded to (and the
 *  corresponding last GCDWebServerConnection closed).
 */
- (void)webServerDidConnect:(GCDWebServer*)server;

/**
 *  This method is called when the last GCDWebServerConnection is closed after
 *  the server has served a series of HTTP requests.
 *
 *  The GCDWebServerOption_ConnectedStateCoalescingInterval option can be used
 *  to have the server wait some extra delay before considering that the series
 *  of HTTP requests has ended (in case there some latency between consecutive
 *  requests). This effectively coalesces the calls to -webServerDidConnect:
 *  and -webServerDidDisconnect:.
 */
- (void)webServerDidDisconnect:(GCDWebServer*)server;

/**
 *  This method is called after the server has stopped.
 */
- (void)webServerDidStop:(GCDWebServer*)server;

@end

/**
 *  The GCDWebServer class listens for incoming HTTP requests on a given port,
 *  then passes each one to a "handler" capable of generating an HTTP response
 *  for it, which is then sent back to the client.
 *
 *  GCDWebServer instances can be created and used from any thread but it's
 *  recommended to have the main thread's runloop be running so internal callbacks
 *  can be handled e.g. for Bonjour registration.
 *
 *  See the README.md file for more information about the architecture of GCDWebServer.
 */
@interface GCDWebServer : NSObject

/**
 *  Sets the delegate for the server.
 */
@property(nonatomic, assign) id<GCDWebServerDelegate> delegate;

/**
 *  Returns YES if the server is currently running.
 */
@property(nonatomic, readonly, getter=isRunning) BOOL running;

/**
 *  Returns the port used by the server.
 *
 *  @warning This property is only valid if the server is running.
 */
@property(nonatomic, readonly) NSUInteger port;

/**
 *  Returns the Bonjour name used by the server.
 *
 *  @warning This property is only valid if the server is running and Bonjour
 *  registration has successfully completed, which can take up to a few seconds.
 */
@property(nonatomic, readonly) NSString* bonjourName;

/**
 *  Returns the Bonjour service type used by the server.
 *
 *  @warning This property is only valid if the server is running and Bonjour
 *  registration has successfully completed, which can take up to a few seconds.
 */
@property(nonatomic, readonly) NSString* bonjourType;

/**
 *  This method is the designated initializer for the class.
 */
- (instancetype)init;

/**
 *  Adds to the server a handler that generates responses synchronously when handling incoming HTTP requests.
 *
 *  Handlers are called in a LIFO queue, so if multiple handlers can potentially
 *  respond to a given request, the latest added one wins.
 *
 *  @warning Addling handlers while the server is running is not allowed.
 */
- (void)addHandlerWithMatchBlock:(GCDWebServerMatchBlock)matchBlock processBlock:(GCDWebServerProcessBlock)processBlock;

/**
 *  Adds to the server a handler that generates responses asynchronously when handling incoming HTTP requests.
 *
 *  Handlers are called in a LIFO queue, so if multiple handlers can potentially
 *  respond to a given request, the latest added one wins.
 *
 *  @warning Addling handlers while the server is running is not allowed.
 */
- (void)addHandlerWithMatchBlock:(GCDWebServerMatchBlock)matchBlock asyncProcessBlock:(GCDWebServerAsyncProcessBlock)processBlock;

/**
 *  Removes all handlers previously added to the server.
 *
 *  @warning Removing handlers while the server is running is not allowed.
 */
- (void)removeAllHandlers;

/**
 *  Starts the server with explicit options. This method is the designated way
 *  to start the server.
 *
 *  Returns NO if the server failed to start and sets "error" argument if not NULL.
 */
- (BOOL)startWithOptions:(NSDictionary*)options error:(NSError**)error;

/**
 *  Stops the server and prevents it to accepts new HTTP requests.
 *
 *  @warning Stopping the server does not abort GCDWebServerConnection instances
 *  currently handling already received HTTP requests. These connections will
 *  continue to execute normally until completion.
 */
- (void)stop;

@end

@interface GCDWebServer (Extensions)

/**
 *  Returns the server's URL.
 *
 *  @warning This property is only valid if the server is running.
 */
@property(nonatomic, readonly) NSURL* serverURL;

/**
 *  Returns the server's Bonjour URL.
 *
 *  @warning This property is only valid if the server is running and Bonjour
 *  registration has successfully completed, which can take up to a few seconds.
 *  Also be aware this property will not automatically update if the Bonjour hostname
 *  has been dynamically changed after the server started running (this should be rare).
 */
@property(nonatomic, readonly) NSURL* bonjourServerURL;

/**
 *  Returns the server's public URL.
 *
 *  @warning This property is only valid if the server is running and NAT port
 *  mapping is active.
 */
@property(nonatomic, readonly) NSURL* publicServerURL;

/**
 *  Starts the server on port 8080 (OS X & iOS Simulator) or port 80 (iOS)
 *  using the default Bonjour name.
 *
 *  Returns NO if the server failed to start.
 */
- (BOOL)start;

/**
 *  Starts the server on a given port and with a specific Bonjour name.
 *  Pass a nil Bonjour name to disable Bonjour entirely or an empty string to
 *  use the default name.
 *
 *  Returns NO if the server failed to start.
 */
- (BOOL)startWithPort:(NSUInteger)port bonjourName:(NSString*)name;

#if !TARGET_OS_IPHONE

/**
 *  Runs the server synchronously using -startWithPort:bonjourName: until a
 *  SIGINT signal is received i.e. Ctrl-C. This method is intended to be used
 *  by command line tools.
 *
 *  Returns NO if the server failed to start.
 *
 *  @warning This method must be used from the main thread only.
 */
- (BOOL)runWithPort:(NSUInteger)port bonjourName:(NSString*)name;

/**
 *  Runs the server synchronously using -startWithOptions: until a SIGTERM or
 *  SIGINT signal is received i.e. Ctrl-C in Terminal. This method is intended to
 *  be used by command line tools.
 *
 *  Returns NO if the server failed to start and sets "error" argument if not NULL.
 *
 *  @warning This method must be used from the main thread only.
 */
- (BOOL)runWithOptions:(NSDictionary*)options error:(NSError**)error;

#endif

@end

@interface GCDWebServer (Handlers)

/**
 *  Adds a default handler to the server to handle all incoming HTTP requests
 *  with a given HTTP method and generate responses synchronously.
 */
- (void)addDefaultHandlerForMethod:(NSString*)method requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block;

/**
 *  Adds a default handler to the server to handle all incoming HTTP requests
 *  with a given HTTP method and generate responses asynchronously.
 */
- (void)addDefaultHandlerForMethod:(NSString*)method requestClass:(Class)aClass asyncProcessBlock:(GCDWebServerAsyncProcessBlock)block;

/**
 *  Adds a handler to the server to handle incoming HTTP requests with a given
 *  HTTP method and a specific case-insensitive path  and generate responses
 *  synchronously.
 */
- (void)addHandlerForMethod:(NSString*)method path:(NSString*)path requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block;

/**
 *  Adds a handler to the server to handle incoming HTTP requests with a given
 *  HTTP method and a specific case-insensitive path and generate responses
 *  asynchronously.
 */
- (void)addHandlerForMethod:(NSString*)method path:(NSString*)path requestClass:(Class)aClass asyncProcessBlock:(GCDWebServerAsyncProcessBlock)block;

/**
 *  Adds a handler to the server to handle incoming HTTP requests with a given
 *  HTTP method and a path matching a case-insensitive regular expression and
 *  generate responses synchronously.
 */
- (void)addHandlerForMethod:(NSString*)method pathRegex:(NSString*)regex requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block;

/**
 *  Adds a handler to the server to handle incoming HTTP requests with a given
 *  HTTP method and a path matching a case-insensitive regular expression and
 *  generate responses asynchronously.
 */
- (void)addHandlerForMethod:(NSString*)method pathRegex:(NSString*)regex requestClass:(Class)aClass asyncProcessBlock:(GCDWebServerAsyncProcessBlock)block;

@end

@interface GCDWebServer (GETHandlers)

/**
 *  Adds a handler to the server to respond to incoming "GET" HTTP requests
 *  with a specific case-insensitive path with in-memory data.
 */
- (void)addGETHandlerForPath:(NSString*)path staticData:(NSData*)staticData contentType:(NSString*)contentType cacheAge:(NSUInteger)cacheAge;

/**
 *  Adds a handler to the server to respond to incoming "GET" HTTP requests
 *  with a specific case-insensitive path with a file.
 */
- (void)addGETHandlerForPath:(NSString*)path filePath:(NSString*)filePath isAttachment:(BOOL)isAttachment cacheAge:(NSUInteger)cacheAge allowRangeRequests:(BOOL)allowRangeRequests;

/**
 *  Adds a handler to the server to respond to incoming "GET" HTTP requests
 *  with a case-insensitive path inside a base path with the corresponding file
 *  inside a local directory. If no local file matches the request path, a 401
 *  HTTP status code is returned to the client.
 *
 *  The "indexFilename" argument allows to specify an "index" file name to use
 *  when the request path corresponds to a directory.
 */
- (void)addGETHandlerForBasePath:(NSString*)basePath directoryPath:(NSString*)directoryPath indexFilename:(NSString*)indexFilename cacheAge:(NSUInteger)cacheAge allowRangeRequests:(BOOL)allowRangeRequests;

@end

/**
 *  GCDWebServer provides its own built-in logging facility which is used by
 *  default. It simply sends log messages to stderr assuming it is connected
 *  to a terminal type device.
 *
 *  GCDWebServer is also compatible with a limited set of third-party logging
 *  facilities. If one of them is available at compile time, GCDWebServer will
 *  automatically use it in place of the built-in one.
 *
 *  Currently supported third-party logging facilities are:
 *  - XLFacility (by the same author as GCDWebServer): https://github.com/swisspol/XLFacility
 *  - CocoaLumberjack: https://github.com/CocoaLumberjack/CocoaLumberjack
 *
 *  For both the built-in logging facility and CocoaLumberjack, the default
 *  logging level is INFO (or DEBUG if the preprocessor constant "DEBUG"
 *  evaluates to non-zero at compile time).
 *
 *  It's possible to have GCDWebServer use a custom logging facility by defining
 *  the "__GCDWEBSERVER_LOGGING_HEADER__" preprocessor constant in Xcode build
 *  settings to the name of a custom header file (escaped like \"MyLogging.h\").
 *  This header file must define the following set of macros:
 *
 *    GWS_LOG_DEBUG(...)
 *    GWS_LOG_VERBOSE(...)
 *    GWS_LOG_INFO(...)
 *    GWS_LOG_WARNING(...)
 *    GWS_LOG_ERROR(...)
 *
 *  IMPORTANT: These macros must behave like NSLog(). Furthermore the GWS_LOG_DEBUG()
 *  macro should not do anything unless the preprocessor constant "DEBUG" evaluates
 *  to non-zero.
 *
 *  The logging methods below send log messages to the same logging facility
 *  used by GCDWebServer. They can be used for consistency wherever you interact
 *  with GCDWebServer in your code (e.g. in the implementation of handlers).
 */
@interface GCDWebServer (Logging)

/**
 *  Sets the log level of the logging facility below which log messages are discarded.
 *
 *  @warning The interpretation of the "level" argument depends on the logging
 *  facility used at compile time.
 *
 *  If using the built-in logging facility, the log levels are as follow:
 *  DEBUG = 0
 *  VERBOSE = 1
 *  INFO = 2
 *  WARNING = 3
 *  ERROR = 4
 */
+ (void)setLogLevel:(int)level;

/**
 *  Logs a message to the logging facility at the VERBOSE level.
 */
- (void)logVerbose:(NSString*)format, ... NS_FORMAT_FUNCTION(1, 2);

/**
 *  Logs a message to the logging facility at the INFO level.
 */
- (void)logInfo:(NSString*)format, ... NS_FORMAT_FUNCTION(1, 2);

/**
 *  Logs a message to the logging facility at the WARNING level.
 */
- (void)logWarning:(NSString*)format, ... NS_FORMAT_FUNCTION(1, 2);

/**
 *  Logs a message to the logging facility at the ERROR level.
 */
- (void)logError:(NSString*)format, ... NS_FORMAT_FUNCTION(1, 2);

@end

#ifdef __GCDWEBSERVER_ENABLE_TESTING__

@interface GCDWebServer (Testing)

/**
 *  Activates recording of HTTP requests and responses which create files in the
 *  current directory containing the raw data for all requests and responses.
 *
 *  @warning The current directory must not contain any prior recording files.
 */
@property(nonatomic, getter=isRecordingEnabled) BOOL recordingEnabled;

/**
 *  Runs tests by playing back pre-recorded HTTP requests in the given directory
 *  and comparing the generated responses with the pre-recorded ones.
 *
 *  Returns the number of failed tests or -1 if server failed to start.
 */
- (NSInteger)runTestsWithOptions:(NSDictionary*)options inDirectory:(NSString*)path;

@end

#endif


================================================
FILE: src/ios/GCDWebServer/Core/GCDWebServer.m
================================================
/*
 Copyright (c) 2012-2015, Pierre-Olivier Latour
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are met:
 * Redistributions of source code must retain the above copyright
 notice, this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright
 notice, this list of conditions and the following disclaimer in the
 documentation and/or other materials provided with the distribution.
 * The name of Pierre-Olivier Latour may not be used to endorse
 or promote products derived from this software without specific
 prior written permission.
 
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
 DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#if !__has_feature(objc_arc)
#error GCDWebServer requires ARC
#endif

#import <TargetConditionals.h>
#if TARGET_OS_IPHONE
#import <UIKit/UIKit.h>
#else
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
#import <AppKit/AppKit.h>
#endif
#endif
#import <netinet/in.h>
#import <dns_sd.h>

#import "GCDWebServerPrivate.h"

#if TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR
#define kDefaultPort 80
#else
#define kDefaultPort 8080
#endif

#define kBonjourResolutionTimeout 5.0

NSString* const GCDWebServerOption_Port = @"Port";
NSString* const GCDWebServerOption_BonjourName = @"BonjourName";
NSString* const GCDWebServerOption_BonjourType = @"BonjourType";
NSString* const GCDWebServerOption_RequestNATPortMapping = @"RequestNATPortMapping";
NSString* const GCDWebServerOption_BindToLocalhost = @"BindToLocalhost";
NSString* const GCDWebServerOption_MaxPendingConnections = @"MaxPendingConnections";
NSString* const GCDWebServerOption_ServerName = @"ServerName";
NSString* const GCDWebServerOption_AuthenticationMethod = @"AuthenticationMethod";
NSString* const GCDWebServerOption_AuthenticationRealm = @"AuthenticationRealm";
NSString* const GCDWebServerOption_AuthenticationAccounts = @"AuthenticationAccounts";
NSString* const GCDWebServerOption_ConnectionClass = @"ConnectionClass";
NSString* const GCDWebServerOption_AutomaticallyMapHEADToGET = @"AutomaticallyMapHEADToGET";
NSString* const GCDWebServerOption_ConnectedStateCoalescingInterval = @"ConnectedStateCoalescingInterval";
NSString* const GCDWebServerOption_DispatchQueuePriority = @"DispatchQueuePriority";
#if TARGET_OS_IPHONE
NSString* const GCDWebServerOption_AutomaticallySuspendInBackground = @"AutomaticallySuspendInBackground";
#endif

NSString* const GCDWebServerAuthenticationMethod_Basic = @"Basic";
NSString* const GCDWebServerAuthenticationMethod_DigestAccess = @"DigestAccess";

#if defined(__GCDWEBSERVER_LOGGING_FACILITY_BUILTIN__)
#if DEBUG
GCDWebServerLoggingLevel GCDWebServerLogLevel = kGCDWebServerLoggingLevel_Debug;
#else
GCDWebServerLoggingLevel GCDWebServerLogLevel = kGCDWebServerLoggingLevel_Info;
#endif
#elif defined(__GCDWEBSERVER_LOGGING_FACILITY_COCOALUMBERJACK__)
#if DEBUG
DDLogLevel GCDWebServerLogLevel = DDLogLevelDebug;
#else
DDLogLevel GCDWebServerLogLevel = DDLogLevelInfo;
#endif
#endif

#if !TARGET_OS_IPHONE
static BOOL _run;
#endif

#ifdef __GCDWEBSERVER_LOGGING_FACILITY_BUILTIN__

void GCDWebServerLogMessage(GCDWebServerLoggingLevel level, NSString* format, ...) {
  static const char* levelNames[] = {"DEBUG", "VERBOSE", "INFO", "WARNING", "ERROR"};
  static int enableLogging = -1;
  if (enableLogging < 0) {
    enableLogging = (isatty(STDERR_FILENO) ? 1 : 0);
  }
  if (enableLogging) {
    va_list arguments;
    va_start(arguments, format);
    NSString* message = [[NSString alloc] initWithFormat:format arguments:arguments];
    va_end(arguments);
    fprintf(stderr, "[%s] %s\n", levelNames[level], [message UTF8String]);
  }
}

#endif

#if !TARGET_OS_IPHONE

static void _SignalHandler(int signal) {
  _run = NO;
  printf("\n");
}

#endif

#if !TARGET_OS_IPHONE || defined(__GCDWEBSERVER_ENABLE_TESTING__)

// This utility function is used to ensure scheduled callbacks on the main thread are called when running the server synchronously
// https://developer.apple.com/library/mac/documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.html
// The main queue works with the application’s run loop to interleave the execution of queued tasks with the execution of other event sources attached to the run loop
// TODO: Ensure all scheduled blocks on the main queue are also executed
static void _ExecuteMainThreadRunLoopSources() {
  SInt32 result;
  do {
    result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, true);
  } while (result == kCFRunLoopRunHandledSource);
}

#endif

@interface GCDWebServerHandler () {
@private
  GCDWebServerMatchBlock _matchBlock;
  GCDWebServerAsyncProcessBlock _asyncProcessBlock;
}
@end

@implementation GCDWebServerHandler

@synthesize matchBlock = _matchBlock, asyncProcessBlock = _asyncProcessBlock;

- (id)initWithMatchBlock:(GCDWebServerMatchBlock)matchBlock asyncProcessBlock:(GCDWebServerAsyncProcessBlock)processBlock {
  if ((self = [super init])) {
    _matchBlock = [matchBlock copy];
    _asyncProcessBlock = [processBlock copy];
  }
  return self;
}

@end

@interface GCDWebServer () {
@private
  id<GCDWebServerDelegate> __unsafe_unretained _delegate;
  dispatch_queue_t _syncQueue;
  dispatch_group_t _sourceGroup;
  NSMutableArray* _handlers;
  NSInteger _activeConnections;  // Accessed through _syncQueue only
  BOOL _connected;  // Accessed on main thread only
  CFRunLoopTimerRef _disconnectTimer;  // Accessed on main thread only

  NSDictionary* _options;
  NSString* _serverName;
  NSString* _authenticationRealm;
  NSMutableDictionary* _authenticationBasicAccounts;
  NSMutableDictionary* _authenticationDigestAccounts;
  Class _connectionClass;
  BOOL _mapHEADToGET;
  CFTimeInterval _disconnectDelay;
  dispatch_queue_priority_t _dispatchQueuePriority;
  NSUInteger _port;
  dispatch_source_t _source4;
  dispatch_source_t _source6;
  CFNetServiceRef _registrationService;
  CFNetServiceRef _resolutionService;
  DNSServiceRef _dnsService;
  CFSocketRef _dnsSocket;
  CFRunLoopSourceRef _dnsSource;
  NSString* _dnsAddress;
  NSUInteger _dnsPort;
  BOOL _bindToLocalhost;
#if TARGET_OS_IPHONE
  BOOL _suspendInBackground;
  UIBackgroundTaskIdentifier _backgroundTask;
#endif
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
  BOOL _recording;
#endif
}
@end

@implementation GCDWebServer

@synthesize delegate = _delegate, handlers = _handlers, port = _port, serverName = _serverName, authenticationRealm = _authenticationRealm,
            authenticationBasicAccounts = _authenticationBasicAccounts, authenticationDigestAccounts = _authenticationDigestAccounts,
            shouldAutomaticallyMapHEADToGET = _mapHEADToGET, dispatchQueuePriority = _dispatchQueuePriority;

+ (void)initialize {
  GCDWebServerInitializeFunctions();
}

- (instancetype)init {
  if ((self = [super init])) {
    _syncQueue = dispatch_queue_create([NSStringFromClass([self class]) UTF8String], DISPATCH_QUEUE_SERIAL);
    _sourceGroup = dispatch_group_create();
    _handlers = [[NSMutableArray alloc] init];
#if TARGET_OS_IPHONE
    _backgroundTask = UIBackgroundTaskInvalid;
#endif
  }
  return self;
}

- (void)dealloc {
  GWS_DCHECK(_connected == NO);
  GWS_DCHECK(_activeConnections == 0);
  GWS_DCHECK(_options == nil);  // The server can never be dealloc'ed while running because of the retain-cycle with the dispatch source
  GWS_DCHECK(_disconnectTimer == NULL);  // The server can never be dealloc'ed while the disconnect timer is pending because of the retain-cycle

#if !OS_OBJECT_USE_OBJC_RETAIN_RELEASE
  dispatch_release(_sourceGroup);
  dispatch_release(_syncQueue);
#endif
}

#if TARGET_OS_IPHONE

// Always called on main thread
- (void)_startBackgroundTask {
  GWS_DCHECK([NSThread isMainThread]);
  if (_backgroundTask == UIBackgroundTaskInvalid) {
    GWS_LOG_DEBUG(@"Did start background task");
    _backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{

      GWS_LOG_WARNING(@"Application is being suspended while %@ is still connected", [self class]);
      [self _endBackgroundTask];

    }];
  } else {
    GWS_DNOT_REACHED();
  }
}

#endif

// Always called on main thread
- (void)_didConnect {
  GWS_DCHECK([NSThread isMainThread]);
  GWS_DCHECK(_connected == NO);
  _connected = YES;
  GWS_LOG_DEBUG(@"Did connect");

#if TARGET_OS_IPHONE
  if ([[UIApplication sharedApplication] applicationState] != UIApplicationStateBackground) {
    [self _startBackgroundTask];
  }
#endif

  if ([_delegate respondsToSelector:@selector(webServerDidConnect:)]) {
    [_delegate webServerDidConnect:self];
  }
}

- (void)willStartConnection:(GCDWebServerConnection*)connection {
  dispatch_sync(_syncQueue, ^{

    GWS_DCHECK(_activeConnections >= 0);
    if (_activeConnections == 0) {
      dispatch_async(dispatch_get_main_queue(), ^{
        if (_disconnectTimer) {
          CFRunLoopTimerInvalidate(_disconnectTimer);
          CFRelease(_disconnectTimer);
          _disconnectTimer = NULL;
        }
        if (_connected == NO) {
          [self _didConnect];
        }
      });
    }
    _activeConnections += 1;

  });
}

#if TARGET_OS_IPHONE

// Always called on main thread
- (void)_endBackgroundTask {
  GWS_DCHECK([NSThread isMainThread]);
  if (_backgroundTask != UIBackgroundTaskInvalid) {
    if (_suspendInBackground && ([[UIApplication sharedApplication] applicationState] == UIApplicationStateBackground) && _source4) {
      [self _stop];
    }
    [[UIApplication sharedApplication] endBackgroundTask:_backgroundTask];
    _backgroundTask = UIBackgroundTaskInvalid;
    GWS_LOG_DEBUG(@"Did end background task");
  }
}

#endif

// Always called on main thread
- (void)_didDisconnect {
  GWS_DCHECK([NSThread isMainThread]);
  GWS_DCHECK(_connected == YES);
  _connected = NO;
  GWS_LOG_DEBUG(@"Did disconnect");

#if TARGET_OS_IPHONE
  [self _endBackgroundTask];
#endif

  if ([_delegate respondsToSelector:@selector(webServerDidDisconnect:)]) {
    [_delegate webServerDidDisconnect:self];
  }
}

- (void)didEndConnection:(GCDWebServerConnection*)connection {
  dispatch_sync(_syncQueue, ^{
    GWS_DCHECK(_activeConnections > 0);
    _activeConnections -= 1;
    if (_activeConnections == 0) {
      dispatch_async(dispatch_get_main_queue(), ^{
        if ((_disconnectDelay > 0.0) && (_source4 != NULL)) {
          if (_disconnectTimer) {
            CFRunLoopTimerInvalidate(_disconnectTimer);
            CFRelease(_disconnectTimer);
          }
          _disconnectTimer = CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent() + _disconnectDelay, 0.0, 0, 0, ^(CFRunLoopTimerRef timer) {
            GWS_DCHECK([NSThread isMainThread]);
            [self _didDisconnect];
            CFRelease(_disconnectTimer);
            _disconnectTimer = NULL;
          });
          CFRunLoopAddTimer(CFRunLoopGetMain(), _disconnectTimer, kCFRunLoopCommonModes);
        } else {
          [self _didDisconnect];
        }
      });
    }
  });
}

- (NSString*)bonjourName {
  CFStringRef name = _resolutionService ? CFNetServiceGetName(_resolutionService) : NULL;
  return name && CFStringGetLength(name) ? CFBridgingRelease(CFStringCreateCopy(kCFAllocatorDefault, name)) : nil;
}

- (NSString*)bonjourType {
  CFStringRef type = _resolutionService ? CFNetServiceGetType(_resolutionService) : NULL;
  return type && CFStringGetLength(type) ? CFBridgingRelease(CFStringCreateCopy(kCFAllocatorDefault, type)) : nil;
}

- (void)addHandlerWithMatchBlock:(GCDWebServerMatchBlock)matchBlock processBlock:(GCDWebServerProcessBlock)processBlock {
  [self addHandlerWithMatchBlock:matchBlock
               asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
                 completionBlock(processBlock(request));
               }];
}

- (void)addHandlerWithMatchBlock:(GCDWebServerMatchBlock)matchBlock asyncProcessBlock:(GCDWebServerAsyncProcessBlock)processBlock {
  GWS_DCHECK(_options == nil);
  GCDWebServerHandler* handler = [[GCDWebServerHandler alloc] initWithMatchBlock:matchBlock asyncProcessBlock:processBlock];
  [_handlers insertObject:handler atIndex:0];
}

- (void)removeAllHandlers {
  GWS_DCHECK(_options == nil);
  [_handlers removeAllObjects];
}

static void _NetServiceRegisterCallBack(CFNetServiceRef service, CFStreamError* error, void* info) {
  GWS_DCHECK([NSThread isMainThread]);
  @autoreleasepool {
    if (error->error) {
      GWS_LOG_ERROR(@"Bonjour registration error %i (domain %i)", (int)error->error, (int)error->domain);
    } else {
      GCDWebServer* server = (__bridge GCDWebServer*)info;
      GWS_LOG_VERBOSE(@"Bonjour registration complete for %@", [server class]);
      if (!CFNetServiceResolveWithTimeout(server->_resolutionService, kBonjourResolutionTimeout, NULL)) {
        GWS_LOG_ERROR(@"Failed starting Bonjour resolution");
        GWS_DNOT_REACHED();
      }
    }
  }
}

static void _NetServiceResolveCallBack(CFNetServiceRef service, CFStreamError* error, void* info) {
  GWS_DCHECK([NSThread isMainThread]);
  @autoreleasepool {
    if (error->error) {
      if ((error->domain != kCFStreamErrorDomainNetServices) && (error->error != kCFNetServicesErrorTimeout)) {
        GWS_LOG_ERROR(@"Bonjour resolution error %i (domain %i)", (int)error->error, (int)error->domain);
      }
    } else {
      GCDWebServer* server = (__bridge GCDWebServer*)info;
      GWS_LOG_INFO(@"%@ now locally reachable at %@", [server class], server.bonjourServerURL);
      if ([server.delegate respondsToSelector:@selector(webServerDidCompleteBonjourRegistration:)]) {
        [server.delegate webServerDidCompleteBonjourRegistration:server];
      }
    }
  }
}

static void _DNSServiceCallBack(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode, uint32_t externalAddress, DNSServiceProtocol protocol, uint16_t internalPort, uint16_t externalPort, uint32_t ttl, void* context) {
  GWS_DCHECK([NSThread isMainThread]);
  @autoreleasepool {
    GCDWebServer* server = (__bridge GCDWebServer*)context;
    if ((errorCode == kDNSServiceErr_NoError) || (errorCode == kDNSServiceErr_DoubleNAT)) {
      struct sockaddr_in addr4;
      bzero(&addr4, sizeof(addr4));
      addr4.sin_len = sizeof(addr4);
      addr4.sin_family = AF_INET;
      addr4.sin_addr.s_addr = externalAddress;  // Already in network byte order
      server->_dnsAddress = GCDWebServerStringFromSockAddr((const struct sockaddr*)&addr4, NO);
      server->_dnsPort = ntohs(externalPort);
      GWS_LOG_INFO(@"%@ now publicly reachable at %@", [server class], server.publicServerURL);
    } else {
      GWS_LOG_ERROR(@"DNS service error %i", errorCode);
      server->_dnsAddress = nil;
      server->_dnsPort = 0;
    }
    if ([server.delegate respondsToSelector:@selector(webServerDidUpdateNATPortMapping:)]) {
      [server.delegate webServerDidUpdateNATPortMapping:server];
    }
  }
}

static void _SocketCallBack(CFSocketRef s, CFSocketCallBackType type, CFDataRef address, const void* data, void* info) {
  GWS_DCHECK([NSThread isMainThread]);
  @autoreleasepool {
    GCDWebServer* server = (__bridge GCDWebServer*)info;
    DNSServiceErrorType status = DNSServiceProcessResult(server->_dnsService);
    if (status != kDNSServiceErr_NoError) {
      GWS_LOG_ERROR(@"DNS service error %i", status);
    }
  }
}

static inline id _GetOption(NSDictionary* options, NSString* key, id defaultValue) {
  id value = [options objectForKey:key];
  return value ? value : defaultValue;
}

static inline NSString* _EncodeBase64(NSString* string) {
  NSData* data = [string dataUsingEncoding:NSUTF8StringEncoding];
#if (TARGET_OS_IPHONE && !(__IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_7_0)) || (!TARGET_OS_IPHONE && !(__MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_9))
  if (![data respondsToSelector:@selector(base64EncodedDataWithOptions:)]) {
    return [data base64Encoding];
  }
#endif
  return [[NSString alloc] initWithData:[data base64EncodedDataWithOptions:0] encoding:NSASCIIStringEncoding];
}

- (int)_createListeningSocket:(BOOL)useIPv6
                 localAddress:(const void*)address
                       length:(socklen_t)length
        maxPendingConnections:(NSUInteger)maxPendingConnections
                        error:(NSError**)error {
  int listeningSocket = socket(useIPv6 ? PF_INET6 : PF_INET, SOCK_STREAM, IPPROTO_TCP);
  if (listeningSocket > 0) {
    int yes = 1;
    setsockopt(listeningSocket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));

    if (bind(listeningSocket, address, length) == 0) {
      if (listen(listeningSocket, (int)maxPendingConnections) == 0) {
        GWS_LOG_DEBUG(@"Did open %s listening socket %i", useIPv6 ? "IPv6" : "IPv4", listeningSocket);
        return listeningSocket;
      } else {
        if (error) {
          *error = GCDWebServerMakePosixError(errno);
        }
        GWS_LOG_ERROR(@"Failed starting %s listening socket: %s (%i)", useIPv6 ? "IPv6" : "IPv4", strerror(errno), errno);
        close(listeningSocket);
      }
    } else {
      if (error) {
        *error = GCDWebServerMakePosixError(errno);
      }
      GWS_LOG_ERROR(@"Failed binding %s listening socket: %s (%i)", useIPv6 ? "IPv6" : "IPv4", strerror(errno), errno);
      close(listeningSocket);
    }

  } else {
    if (error) {
      *error = GCDWebServerMakePosixError(errno);
    }
    GWS_LOG_ERROR(@"Failed creating %s listening socket: %s (%i)", useIPv6 ? "IPv6" : "IPv4", strerror(errno), errno);
  }
  return -1;
}

- (dispatch_source_t)_createDispatchSourceWithListeningSocket:(int)listeningSocket isIPv6:(BOOL)isIPv6 {
  dispatch_group_enter(_sourceGroup);
  dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, listeningSocket, 0, dispatch_get_global_queue(_dispatchQueuePriority, 0));
  dispatch_source_set_cancel_handler(source, ^{

    @autoreleasepool {
      int result = close(listeningSocket);
      if (result != 0) {
        GWS_LOG_ERROR(@"Failed closing %s listening socket: %s (%i)", isIPv6 ? "IPv6" : "IPv4", strerror(errno), errno);
      } else {
        GWS_LOG_DEBUG(@"Did close %s listening socket %i", isIPv6 ? "IPv6" : "IPv4", listeningSocket);
      }
    }
    dispatch_group_leave(_sourceGroup);

  });
  dispatch_source_set_event_handler(source, ^{

    @autoreleasepool {
      struct sockaddr_storage remoteSockAddr;
      socklen_t remoteAddrLen = sizeof(remoteSockAddr);
      int socket = accept(listeningSocket, (struct sockaddr*)&remoteSockAddr, &remoteAddrLen);
      if (socket > 0) {
        NSData* remoteAddress = [NSData dataWithBytes:&remoteSockAddr length:remoteAddrLen];

        struct sockaddr_storage localSockAddr;
        socklen_t localAddrLen = sizeof(localSockAddr);
        NSData* localAddress = nil;
        if (getsockname(socket, (struct sockaddr*)&localSockAddr, &localAddrLen) == 0) {
          localAddress = [NSData dataWithBytes:&localSockAddr length:localAddrLen];
          GWS_DCHECK((!isIPv6 && localSockAddr.ss_family == AF_INET) || (isIPv6 && localSockAddr.ss_family == AF_INET6));
        } else {
          GWS_DNOT_REACHED();
        }

        int noSigPipe = 1;
        setsockopt(socket, SOL_SOCKET, SO_NOSIGPIPE, &noSigPipe, sizeof(noSigPipe));  // Make sure this socket cannot generate SIG_PIPE

        GCDWebServerConnection* connection = [[_connectionClass alloc] initWithServer:self localAddress:localAddress remoteAddress:remoteAddress socket:socket];  // Connection will automatically retain itself while opened
        [connection self];  // Prevent compiler from complaining about unused variable / useless statement
      } else {
        GWS_LOG_ERROR(@"Failed accepting %s socket: %s (%i)", isIPv6 ? "IPv6" : "IPv4", strerror(errno), errno);
      }
    }

  });
  return source;
}

- (BOOL)_start:(NSError**)error {
  GWS_DCHECK(_source4 == NULL);

  NSUInteger port = [_GetOption(_options, GCDWebServerOption_Port, @0) unsignedIntegerValue];
  BOOL bindToLocalhost = [_GetOption(_options, GCDWebServerOption_BindToLocalhost, @NO) boolValue];
  NSUInteger maxPendingConnections = [_GetOption(_options, GCDWebServerOption_MaxPendingConnections, @16) unsignedIntegerValue];

  struct sockaddr_in addr4;
  bzero(&addr4, sizeof(addr4));
  addr4.sin_len = sizeof(addr4);
  addr4.sin_family = AF_INET;
  addr4.sin_port = htons(port);
  addr4.sin_addr.s_addr = bindToLocalhost ? htonl(INADDR_LOOPBACK) : htonl(INADDR_ANY);
  int listeningSocket4 = [self _createListeningSocket:NO localAddress:&addr4 length:sizeof(addr4) maxPendingConnections:maxPendingConnections error:error];
  if (listeningSocket4 <= 0) {
    return NO;
  }
  if (port == 0) {
    struct sockaddr_in addr;
    socklen_t addrlen = sizeof(addr);
    if (getsockname(listeningSocket4, (struct sockaddr*)&addr, &addrlen) == 0) {
      port = ntohs(addr.sin_port);
    } else {
      GWS_LOG_ERROR(@"Failed retrieving socket address: %s (%i)", strerror(errno), errno);
    }
  }

  struct sockaddr_in6 addr6;
  bzero(&addr6, sizeof(addr6));
  addr6.sin6_len = sizeof(addr6);
  addr6.sin6_family = AF_INET6;
  addr6.sin6_port = htons(port);
  addr6.sin6_addr = bindToLocalhost ? in6addr_loopback : in6addr_any;
  int listeningSocket6 = [self _createListeningSocket:YES localAddress:&addr6 length:sizeof(addr6) maxPendingConnections:maxPendingConnections error:error];
  if (listeningSocket6 <= 0) {
    close(listeningSocket4);
    return NO;
  }

  _serverName = [_GetOption(_options, GCDWebServerOption_ServerName, NSStringFromClass([self class])) copy];
  NSString* authenticationMethod = _GetOption(_options, GCDWebServerOption_AuthenticationMethod, nil);
  if ([authenticationMethod isEqualToString:GCDWebServerAuthenticationMethod_Basic]) {
    _authenticationRealm = [_GetOption(_options, GCDWebServerOption_AuthenticationRealm, _serverName) copy];
    _authenticationBasicAccounts = [[NSMutableDictionary alloc] init];
    NSDictionary* accounts = _GetOption(_options, GCDWebServerOption_AuthenticationAccounts, @{});
    [accounts enumerateKeysAndObjectsUsingBlock:^(NSString* username, NSString* password, BOOL* stop) {
      [_authenticationBasicAccounts setObject:_EncodeBase64([NSString stringWithFormat:@"%@:%@", username, password]) forKey:username];
    }];
  } else if ([authenticationMethod isEqualToString:GCDWebServerAuthenticationMethod_DigestAccess]) {
    _authenticationRealm = [_GetOption(_options, GCDWebServerOption_AuthenticationRealm, _serverName) copy];
    _authenticationDigestAccounts = [[NSMutableDictionary alloc] init];
    NSDictionary* accounts = _GetOption(_options, GCDWebServerOption_AuthenticationAccounts, @{});
    [accounts enumerateKeysAndObjectsUsingBlock:^(NSString* username, NSString* password, BOOL* stop) {
      [_authenticationDigestAccounts setObject:GCDWebServerComputeMD5Digest(@"%@:%@:%@", username, _authenticationRealm, password) forKey:username];
    }];
  }
  _connectionClass = _GetOption(_options, GCDWebServerOption_ConnectionClass, [GCDWebServerConnection class]);
  _mapHEADToGET = [_GetOption(_options, GCDWebServerOption_AutomaticallyMapHEADToGET, @YES) boolValue];
  _disconnectDelay = [_GetOption(_options, GCDWebServerOption_ConnectedStateCoalescingInterval, @1.0) doubleValue];
  _dispatchQueuePriority = [_GetOption(_options, GCDWebServerOption_DispatchQueuePriority, @(DISPATCH_QUEUE_PRIORITY_DEFAULT)) longValue];

  _source4 = [self _createDispatchSourceWithListeningSocket:listeningSocket4 isIPv6:NO];
  _source6 = [self _createDispatchSourceWithListeningSocket:listeningSocket6 isIPv6:YES];
  _port = port;
  _bindToLocalhost = bindToLocalhost;

  NSString* bonjourName = _GetOption(_options, GCDWebServerOption_BonjourName, nil);
  NSString* bonjourType = _GetOption(_options, GCDWebServerOption_BonjourType, @"_http._tcp");
  if (bonjourName) {
    _registrationService = CFNetServiceCreate(kCFAllocatorDefault, CFSTR("local."), (__bridge CFStringRef)bonjourType, (__bridge CFStringRef)(bonjourName.length ? bonjourName : _serverName), (SInt32)_port);
    if (_registrationService) {
      CFNetServiceClientContext context = {0, (__bridge void*)self, NULL, NULL, NULL};

      CFNetServiceSetClient(_registrationService, _NetServiceRegisterCallBack, &context);
      CFNetServiceScheduleWithRunLoop(_registrationService, CFRunLoopGetMain(), kCFRunLoopCommonModes);
      CFStreamError streamError = {0};
      CFNetServiceRegisterWithOptions(_registrationService, 0, &streamError);

      _resolutionService = CFNetServiceCreateCopy(kCFAllocatorDefault, _registrationService);
      if (_resolutionService) {
        CFNetServiceSetClient(_resolutionService, _NetServiceResolveCallBack, &context);
        CFNetServiceScheduleWithRunLoop(_resolutionService, CFRunLoopGetMain(), kCFRunLoopCommonModes);
      } else {
        GWS_LOG_ERROR(@"Failed creating CFNetService for resolution");
      }
    } else {
      GWS_LOG_ERROR(@"Failed creating CFNetService for registration");
    }
  }

  if ([_GetOption(_options, GCDWebServerOption_RequestNATPortMapping, @NO) boolValue]) {
    DNSServiceErrorType status = DNSServiceNATPortMappingCreate(&_dnsService, 0, 0, kDNSServiceProtocol_TCP, htons(port), htons(port), 0, _DNSServiceCallBack, (__bridge void*)self);
    if (status == kDNSServiceErr_NoError) {
      CFSocketContext context = {0, (__bridge void*)self, NULL, NULL, NULL};
      _dnsSocket = CFSocketCreateWithNative(kCFAllocatorDefault, DNSServiceRefSockFD(_dnsService), kCFSocketReadCallBack, _SocketCallBack, &context);
      if (_dnsSocket) {
        CFSocketSetSocketFlags(_dnsSocket, CFSocketGetSocketFlags(_dnsSocket) & ~kCFSocketCloseOnInvalidate);
        _dnsSource = CFSocketCreateRunLoopSource(kCFAllocatorDefault, _dnsSocket, 0);
        if (_dnsSource) {
          CFRunLoopAddSource(CFRunLoopGetMain(), _dnsSource, kCFRunLoopCommonModes);
        } else {
          GWS_LOG_ERROR(@"Failed creating CFRunLoopSource");
          GWS_DNOT_REACHED();
        }
      } else {
        GWS_LOG_ERROR(@"Failed creating CFSocket");
        GWS_DNOT_REACHED();
      }
    } else {
      GWS_LOG_ERROR(@"Failed creating NAT port mapping (%i)", status);
    }
  }

  dispatch_resume(_source4);
  dispatch_resume(_source6);
  GWS_LOG_INFO(@"%@ started on port %i and reachable at %@", [self class], (int)_port, self.serverURL);
  if ([_delegate respondsToSelector:@selector(webServerDidStart:)]) {
    dispatch_async(dispatch_get_main_queue(), ^{
      [_delegate webServerDidStart:self];
    });
  }

  return YES;
}

- (void)_stop {
  GWS_DCHECK(_source4 != NULL);

  if (_dnsService) {
    _dnsAddress = nil;
    _dnsPort = 0;
    if (_dnsSource) {
      CFRunLoopSourceInvalidate(_dnsSource);
      CFRelease(_dnsSource);
      _dnsSource = NULL;
    }
    if (_dnsSocket) {
      CFRelease(_dnsSocket);
      _dnsSocket = NULL;
    }
    DNSServiceRefDeallocate(_dnsService);
    _dnsService = NULL;
  }

  if (_registrationService) {
    if (_resolutionService) {
      CFNetServiceUnscheduleFromRunLoop(_resolutionService, CFRunLoopGetMain(), kCFRunLoopCommonModes);
      CFNetServiceSetClient(_resolutionService, NULL, NULL);
      CFNetServiceCancel(_resolutionService);
      CFRelease(_resolutionService);
      _resolutionService = NULL;
    }
    CFNetServiceUnscheduleFromRunLoop(_registrationService, CFRunLoopGetMain(), kCFRunLoopCommonModes);
    CFNetServiceSetClient(_registrationService, NULL, NULL);
    CFNetServiceCancel(_registrationService);
    CFRelease(_registrationService);
    _registrationService = NULL;
  }

  dispatch_source_cancel(_source6);
  dispatch_source_cancel(_source4);
  dispatch_group_wait(_sourceGroup, DISPATCH_TIME_FOREVER);  // Wait until the cancellation handlers have been called which guarantees the listening sockets are closed
#if !OS_OBJECT_USE_OBJC_RETAIN_RELEASE
  dispatch_release(_source6);
#endif
  _source6 = NULL;
#if !OS_OBJECT_USE_OBJC_RETAIN_RELEASE
  dispatch_release(_source4);
#endif
  _source4 = NULL;
  _port = 0;
  _bindToLocalhost = NO;

  _serverName = nil;
  _authenticationRealm = nil;
  _authenticationBasicAccounts = nil;
  _authenticationDigestAccounts = nil;

  dispatch_async(dispatch_get_main_queue(), ^{
    if (_disconnectTimer) {
      CFRunLoopTimerInvalidate(_disconnectTimer);
      CFRelease(_disconnectTimer);
      _disconnectTimer = NULL;
      [self _didDisconnect];
    }
  });

  GWS_LOG_INFO(@"%@ stopped", [self class]);
  if ([_delegate respondsToSelector:@selector(webServerDidStop:)]) {
    dispatch_async(dispatch_get_main_queue(), ^{
      [_delegate webServerDidStop:self];
    });
  }
}

#if TARGET_OS_IPHONE

- (void)_didEnterBackground:(NSNotification*)notification {
  GWS_DCHECK([NSThread isMainThread]);
  GWS_LOG_DEBUG(@"Did enter background");
  if ((_backgroundTask == UIBackgroundTaskInvalid) && _source4) {
    [self _stop];
  }
}

- (void)_willEnterForeground:(NSNotification*)notification {
  GWS_DCHECK([NSThread isMainThread]);
  GWS_LOG_DEBUG(@"Will enter foreground");
  if (!_source4) {
    [self _start:NULL];  // TODO: There's probably nothing we can do on failure
  }
}

#endif

- (BOOL)startWithOptions:(NSDictionary*)options error:(NSError**)error {
  if (_options == nil) {
    _options = options ? [options copy] : @{};
#if TARGET_OS_IPHONE
    _suspendInBackground = [_GetOption(_options, GCDWebServerOption_AutomaticallySuspendInBackground, @YES) boolValue];
    if (((_suspendInBackground == NO) || ([[UIApplication sharedApplication] applicationState] != UIApplicationStateBackground)) && ![self _start:error])
#else
    if (![self _start:error])
#endif
    {
      _options = nil;
      return NO;
    }
#if TARGET_OS_IPHONE
    if (_suspendInBackground) {
      [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_didEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil];
      [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_willEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil];
    }
#endif
    return YES;
  } else {
    GWS_DNOT_REACHED();
  }
  return NO;
}

- (BOOL)isRunning {
  return (_options ? YES : NO);
}

- (void)stop {
  if (_options) {
#if TARGET_OS_IPHONE
    if (_suspendInBackground) {
      [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil];
      [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillEnterForegroundNotification object:nil];
    }
#endif
    if (_source4) {
      [self _stop];
    }
    _options = nil;
  } else {
    GWS_DNOT_REACHED();
  }
}

@end

@implementation GCDWebServer (Extensions)

- (NSURL*)serverURL {
  if (_source4) {
    NSString* ipAddress = _bindToLocalhost ? @"localhost" : GCDWebServerGetPrimaryIPAddress(NO);  // We can't really use IPv6 anyway as it doesn't work great with HTTP URLs in practice
    if (ipAddress) {
      if (_port != 80) {
        return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@:%i/", ipAddress, (int)_port]];
      } else {
        return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@/", ipAddress]];
      }
    }
  }
  return nil;
}

- (NSURL*)bonjourServerURL {
  if (_source4 && _resolutionService) {
    NSString* name = (__bridge NSString*)CFNetServiceGetTargetHost(_resolutionService);
    if (name.length) {
      name = [name substringToIndex:(name.length - 1)];  // Strip trailing period at end of domain
      if (_port != 80) {
        return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@:%i/", name, (int)_port]];
      } else {
        return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@/", name]];
      }
    }
  }
  return nil;
}

- (NSURL*)publicServerURL {
  if (_source4 && _dnsService && _dnsAddress && _dnsPort) {
    if (_dnsPort != 80) {
      return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@:%i/", _dnsAddress, (int)_dnsPort]];
    } else {
      return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@/", _dnsAddress]];
    }
  }
  return nil;
}

- (BOOL)start {
  return [self startWithPort:kDefaultPort bonjourName:@""];
}

- (BOOL)startWithPort:(NSUInteger)port bonjourName:(NSString*)name {
  NSMutableDictionary* options = [NSMutableDictionary dictionary];
  [options setObject:[NSNumber numberWithInteger:port] forKey:GCDWebServerOption_Port];
  [options setValue:name forKey:GCDWebServerOption_BonjourName];
  return [self startWithOptions:options error:NULL];
}

#if !TARGET_OS_IPHONE

- (BOOL)runWithPort:(NSUInteger)port bonjourName:(NSString*)name {
  NSMutableDictionary* options = [NSMutableDictionary dictionary];
  [options setObject:[NSNumber numberWithInteger:port] forKey:GCDWebServerOption_Port];
  [options setValue:name forKey:GCDWebServerOption_BonjourName];
  return [self runWithOptions:options error:NULL];
}

- (BOOL)runWithOptions:(NSDictionary*)options error:(NSError**)error {
  GWS_DCHECK([NSThread isMainThread]);
  BOOL success = NO;
  _run = YES;
  void (*termHandler)(int) = signal(SIGTERM, _SignalHandler);
  void (*intHandler)(int) = signal(SIGINT, _SignalHandler);
  if ((termHandler != SIG_ERR) && (intHandler != SIG_ERR)) {
    if ([self startWithOptions:options error:error]) {
      while (_run) {
        CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0, true);
      }
      [self stop];
      success = YES;
    }
    _ExecuteMainThreadRunLoopSources();
    signal(SIGINT, intHandler);
    signal(SIGTERM, termHandler);
  }
  return success;
}

#endif

@end

@implementation GCDWebServer (Handlers)

- (void)addDefaultHandlerForMethod:(NSString*)method requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block {
  [self addDefaultHandlerForMethod:method
                      requestClass:aClass
                 asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
                   completionBlock(block(request));
                 }];
}

- (void)addDefaultHandlerForMethod:(NSString*)method requestClass:(Class)aClass asyncProcessBlock:(GCDWebServerAsyncProcessBlock)block {
  [self addHandlerWithMatchBlock:^GCDWebServerRequest*(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery) {

    if (![requestMethod isEqualToString:method]) {
      return nil;
    }
    return [[aClass alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery];

  }
               asyncProcessBlock:block];
}

- (void)addHandlerForMethod:(NSString*)method path:(NSString*)path requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block {
  [self addHandlerForMethod:method
                       path:path
               requestClass:aClass
          asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
            completionBlock(block(request));
          }];
}

- (void)addHandlerForMethod:(NSString*)method path:(NSString*)path requestClass:(Class)aClass asyncProcessBlock:(GCDWebServerAsyncProcessBlock)block {
  if ([path hasPrefix:@"/"] && [aClass isSubclassOfClass:[GCDWebServerRequest class]]) {
    [self addHandlerWithMatchBlock:^GCDWebServerRequest*(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery) {

      if (![requestMethod isEqualToString:method]) {
        return nil;
      }
      if ([urlPath caseInsensitiveCompare:path] != NSOrderedSame) {
        return nil;
      }
      return [[aClass alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery];

    }
                 asyncProcessBlock:block];
  } else {
    GWS_DNOT_REACHED();
  }
}

- (void)addHandlerForMethod:(NSString*)method pathRegex:(NSString*)regex requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block {
  [self addHandlerForMethod:method
                  pathRegex:regex
               requestClass:aClass
          asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
            completionBlock(block(request));
          }];
}

- (void)addHandlerForMethod:(NSString*)method pathRegex:(NSString*)regex requestClass:(Class)aClass asyncProcessBlock:(GCDWebServerAsyncProcessBlock)block {
  NSRegularExpression* expression = [NSRegularExpression regularExpressionWithPattern:regex options:NSRegularExpressionCaseInsensitive error:NULL];
  if (expression && [aClass isSubclassOfClass:[GCDWebServerRequest class]]) {
    [self addHandlerWithMatchBlock:^GCDWebServerRequest*(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery) {

      if (![requestMethod isEqualToString:method]) {
        return nil;
      }

      NSArray* matches = [expression matchesInString:urlPath options:0 range:NSMakeRange(0, urlPath.length)];
      if (matches.count == 0) {
        return nil;
      }

      NSMutableArray* captures = [NSMutableArray array];
      for (NSTextCheckingResult* result in matches) {
        // Start at 1; index 0 is the whole string
        for (NSUInteger i = 1; i < result.numberOfRanges; i++) {
          NSRange range = [result rangeAtIndex:i];
          // range is {NSNotFound, 0} "if one of the capture groups did not participate in this particular match"
          // see discussion in -[NSRegularExpression firstMatchInString:options:range:]
          if (range.location != NSNotFound) {
            [captures addObject:[urlPath substringWithRange:range]];
          }
        }
      }

      GCDWebServerRequest* request = [[aClass alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery];
      [request setAttribute:captures forKey:GCDWebServerRequestAttribute_RegexCaptures];
      return request;

    }
                 asyncProcessBlock:block];
  } else {
    GWS_DNOT_REACHED();
  }
}

@end

@implementation GCDWebServer (GETHandlers)

- (void)addGETHandlerForPath:(NSString*)path staticData:(NSData*)staticData contentType:(NSString*)contentType cacheAge:(NSUInteger)cacheAge {
  [self addHandlerForMethod:@"GET"
                       path:path
               requestClass:[GCDWebServerRequest class]
               processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {

                 GCDWebServerResponse* response = [GCDWebServerDataResponse responseWithData:staticData contentType:contentType];
                 response.cacheControlMaxAge = cacheAge;
                 return response;

               }];
}

- (void)addGETHandlerForPath:(NSString*)path filePath:(NSString*)filePath isAttachment:(BOOL)isAttachment cacheAge:(NSUInteger)cacheAge allowRangeRequests:(BOOL)allowRangeRequests {
  [self addHandlerForMethod:@"GET"
                       path:path
               requestClass:[GCDWebServerRequest class]
               processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {

                 GCDWebServerResponse* response = nil;
                 if (allowRangeRequests) {
                   response = [GCDWebServerFileResponse responseWithFile:filePath byteRange:request.byteRange isAttachment:isAttachment];
                   [response setValue:@"bytes" forAdditionalHeader:@"Accept-Ranges"];
                 } else {
                   response = [GCDWebServerFileResponse responseWithFile:filePath isAttachment:isAttachment];
                 }
                 response.cacheControlMaxAge = cacheAge;
                 return response;

               }];
}

- (GCDWebServerResponse*)_responseWithContentsOfDirectory:(NSString*)path {
  NSDirectoryEnumerator* enumerator = [[NSFileManager defaultManager] enumeratorAtPath:path];
  if (enumerator == nil) {
    return nil;
  }
  NSMutableString* html = [NSMutableString string];
  [html appendString:@"<!DOCTYPE html>\n"];
  [html appendString:@"<html><head><meta charset=\"utf-8\"></head><body>\n"];
  [html appendString:@"</body></html>\n"];
  return [GCDWebServerDataResponse responseWithHTML:html];
}

- (void)addGETHandlerForBasePath:(NSString*)basePath directoryPath:(NSString*)directoryPath indexFilename:(NSString*)indexFilename cacheAge:(NSUInteger)cacheAge allowRangeRequests:(BOOL)allowRangeRequests {
  if ([basePath hasPrefix:@"/"] && [basePath hasSuffix:@"/"]) {
    GCDWebServer* __unsafe_unretained server = self;
    [self addHandlerWithMatchBlock:^GCDWebServerRequest*(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery) {

      if (![requestMethod isEqualToString:@"GET"]) {
        return nil;
      }
      if (![urlPath hasPrefix:basePath]) {
        return nil;
      }
      return [[GCDWebServerRequest alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery];

    }
        processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {

          GCDWebServerResponse* response = nil;
          NSString* filePath = [directoryPath stringByAppendingPathComponent:[request.path substringFromIndex:basePath.length]];
          NSString* fileType = [[[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:NULL] fileType];
          if (fileType) {
            if ([fileType isEqualToString:NSFileTypeDirectory]) {
              if (indexFilename) {
                NSString* indexPath = [filePath stringByAppendingPathComponent:indexFilename];
                NSString* indexType = [[[NSFileManager defaultManager] attributesOfItemAtPath:indexPath error:NULL] fileType];
                if ([indexType isEqualToString:NSFileTypeRegular]) {
                  return [GCDWebServerFileResponse responseWithFile:indexPath];
                }
              }
              response = [server _responseWithContentsOfDirectory:filePath];
            } else if ([fileType isEqualToString:NSFileTypeRegular]) {
              if (allowRangeRequests) {
                response = [GCDWebServerFileResponse responseWithFile:filePath byteRange:request.byteRange];
                [response setValue:@"bytes" forAdditionalHeader:@"Accept-Ranges"];
              } else {
                response = [GCDWebServerFileResponse responseWithFile:filePath];
              }
            }
          }
          if (response) {
            response.cacheControlMaxAge = cacheAge;
          } else {
            response = [GCDWebServerResponse responseWithStatusCode:kGCDWebServerHTTPStatusCode_NotFound];
          }
          [response setValue:@"*" forAdditionalHeader:@"Access-Control-Allow-Origin"];
          return response;

        }];
  } else {
    GWS_DNOT_REACHED();
  }
}

@end

@implementation GCDWebServer (Logging)

+ (void)setLogLevel:(int)level {
#if defined(__GCDWEBSERVER_LOGGING_FACILITY_XLFACILITY__)
  [XLSharedFacility setMinLogLevel:level];
#elif defined(__GCDWEBSERVER_LOGGING_FACILITY_COCOALUMBERJACK__)
  GCDWebServerLogLevel = level;
#elif defined(__GCDWEBSERVER_LOGGING_FACILITY_BUILTIN__)
  GCDWebServerLogLevel = level;
#endif
}

- (void)logVerbose:(NSString*)format, ... {
  va_list arguments;
  va_start(arguments, format);
  GWS_LOG_VERBOSE(@"%@", [[NSString alloc] initWithFormat:format arguments:arguments]);
  va_end(arguments);
}

- (void)logInfo:(NSString*)format, ... {
  va_list arguments;
  va_start(arguments, format);
  GWS_LOG_INFO(@"%@", [[NSString alloc] initWithFormat:format arguments:arguments]);
  va_end(arguments);
}

- (void)logWarning:(NSString*)format, ... {
  va_list arguments;
  va_start(arguments, format);
  GWS_LOG_WARNING(@"%@", [[NSString alloc] initWithFormat:format arguments:arguments]);
  va_end(arguments);
}

- (void)logError:(NSString*)format, ... {
  va_list arguments;
  va_start(arguments, format);
  GWS_LOG_ERROR(@"%@", [[NSString alloc] initWithFormat:format arguments:arguments]);
  va_end(arguments);
}

@end

#ifdef __GCDWEBSERVER_ENABLE_TESTING__

@implementation GCDWebServer (Testing)

- (void)setRecordingEnabled:(BOOL)flag {
  _recording = flag;
}

- (BOOL)isRecordingEnabled {
  return _recording;
}

static CFHTTPMessageRef _CreateHTTPMessageFromData(NSData* data, BOOL isRequest) {
  CFHTTPMessageRef message = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, isRequest);
  if (CFHTTPMessageAppendBytes(message, data.bytes, data.length)) {
    return message;
  }
  CFRelease(message);
  return NULL;
}

static CFHTTPMessageRef _CreateHTTPMessageFromPerformingRequest(NSData* inData, NSUInteger port) {
  CFHTTPMessageRef response = NULL;
  int httpSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
  if (httpSocket > 0) {
    struct sockaddr_in addr4;
    bzero(&addr4, sizeof(addr4));
    addr4.sin_len = sizeof(port);
    addr4.sin_family = AF_INET;
    addr4.sin_port = htons(8080);
    addr4.sin_addr.s_addr = htonl(INADDR_ANY);
    if (connect(httpSocket, (void*)&addr4, sizeof(addr4)) == 0) {
      if (write(httpSocket, inData.bytes, inData.length) == (ssize_t)inData.length) {
        NSMutableData* outData = [[NSMutableData alloc] initWithLength:(256 * 1024)];
        NSUInteger length = 0;
        while (1) {
          ssize_t result = read(httpSocket, (char*)outData.mutableBytes + length, outData.length - length);
          if (result < 0) {
            length = NSUIntegerMax;
            break;
          } else if (result == 0) {
            break;
          }
          length += result;
          if (length >= outData.length) {
            outData.length = 2 * outData.length;
          }
        }
        if (length != NSUIntegerMax) {
          outData.length = length;
          response = _CreateHTTPMessageFromData(outData, NO);
        } else {
          GWS_DNOT_REACHED();
        }
      }
    }
    close(httpSocket);
  }
  return response;
}

static void _LogResult(NSString* format, ...) {
  va_list arguments;
  va_start(arguments, format);
  NSString* message = [[NSString alloc] initWithFormat:format arguments:arguments];
  va_end(arguments);
  fprintf(stdout, "%s\n", [message UTF8String]);
}

- (NSInteger)runTestsWithOptions:(NSDictionary*)options inDirectory:(NSString*)path {
  GWS_DCHECK([NSThread isMainThread]);
  NSArray* ignoredHeaders = @[ @"Date", @"Etag" ];  // Dates are always different by definition and ETags depend on file system node IDs
  NSInteger result = -1;
  if ([self startWithOptions:options error:NULL]) {
    _ExecuteMainThreadRunLoopSources();

    result = 0;
    NSArray* files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:NULL];
    for (NSString* requestFile in files) {
      if (![requestFile hasSuffix:@".request"]) {
        continue;
      }
      @autoreleasepool {
        NSString* index = [[requestFile componentsSeparatedByString:@"-"] firstObject];
        BOOL success = NO;
        NSData* requestData = [NSData dataWithContentsOfFile:[path stringByAppendingPathComponent:requestFile]];
        if (requestData) {
          CFHTTPMessageRef request = _CreateHTTPMessageFromData(requestData, YES);
          if (request) {
            NSString* requestMethod = CFBridgingRelease(CFHTTPMessageCopyRequestMethod(request));
            NSURL* requestURL = CFBridgingRelease(CFHTTPMessageCopyRequestURL(request));
            _LogResult(@"[%i] %@ %@", (int)[index integerValue], requestMethod, requestURL.path);
            NSString* prefix = [index stringByAppendingString:@"-"];
            for (NSString* responseFile in files) {
              if ([responseFile hasPrefix:prefix] && [responseFile hasSuffix:@".response"]) {
                NSData* responseData = [NSData dataWithContentsOfFile:[path stringByAppendingPathComponent:responseFile]];
                if (responseData) {
                  CFHTTPMessageRef expectedResponse = _CreateHTTPMessageFromData(responseData, NO);
                  if (expectedResponse) {
                    CFHTTPMessageRef actualResponse = _CreateHTTPMessageFromPerformingRequest(requestData, self.port);
                    if (actualResponse) {
                      success = YES;

                      CFIndex expectedStatusCode = CFHTTPMessageGetResponseStatusCode(expectedResponse);
                      CFIndex actualStatusCode = CFHTTPMessageGetResponseStatusCode(actualResponse);
                      if (actualStatusCode != expectedStatusCode) {
                        _LogResult(@"  Status code not matching:\n    Expected: %i\n      Actual: %i", (int)expectedStatusCode, (int)actualStatusCode);
                        success = NO;
                      }

                      NSDictionary* expectedHeaders = CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(expectedResponse));
                      NSDictionary* actualHeaders = CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(actualResponse));
                      for (NSString* expectedHeader in expectedHeaders) {
                        if ([ignoredHeaders containsObject:expectedHeader]) {
                          continue;
                        }
                        NSString* expectedValue = [expectedHeaders objectForKey:expectedHeader];
                        NSString* actualValue = [actualHeaders objectForKey:expectedHeader];
                        if (![actualValue isEqualToString:expectedValue]) {
                          _LogResult(@"  Header '%@' not matching:\n    Expected: \"%@\"\n      Actual: \"%@\"", expectedHeader, expectedValue, actualValue);
                          success = NO;
                        }
                      }
                      for (NSString* actualHeader in actualHeaders) {
                        if (![expectedHeaders objectForKey:actualHeader]) {
                          _LogResult(@"  Header '%@' not matching:\n    Expected: \"%@\"\n      Actual: \"%@\"", actualHeader, nil, [actualHeaders objectForKey:actualHeader]);
                          success = NO;
                        }
                      }

                      NSString* expectedContentLength = CFBridgingRelease(CFHTTPMessageCopyHeaderFieldValue(expectedResponse, CFSTR("Content-Length")));
                      NSData* expectedBody = CFBridgingRelease(CFHTTPMessageCopyBody(expectedResponse));
                      NSString* actualContentLength = CFBridgingRelease(CFHTTPMessageCopyHeaderFieldValue(actualResponse, CFSTR("Content-Length")));
                      NSData* actualBody = CFBridgingRelease(CFHTTPMessageCopyBody(actualResponse));
                      if ([actualContentLength isEqualToString:expectedContentLength] && (actualBody.length > expectedBody.length)) {  // Handle web browser closing connection before retrieving entire body (e.g. when playing a video file)
                        actualBody = [actualBody subdataWithRange:NSMakeRange(0, expectedBody.length)];
                      }
                      if (![actualBody isEqualToData:expectedBody]) {
                        _LogResult(@"  Bodies not matching:\n    Expected: %lu bytes\n      Actual: %lu bytes", (unsigned long)expectedBody.length, (unsigned long)actualBody.length);
                        success = NO;
#if !TARGET_OS_IPHONE
#if DEBUG
                        if (GCDWebServerIsTextContentType([expectedHeaders objectForKey:@"Content-Type"])) {
                          NSString* expectedPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[[NSProcessInfo processInfo] globallyUniqueString] stringByAppendingPathExtension:@"txt"]];
                          NSString* actualPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[[NSProcessInfo processInfo] globallyUniqueString] stringByAppendingPathExtension:@"txt"]];
                          if ([expectedBody writeToFile:expectedPath atomically:YES] && [actualBody writeToFile:actualPath atomically:YES]) {
                            NSTask* task = [[NSTask alloc] init];
                            [task setLaunchPath:@"/usr/bin/opendiff"];
                            [task setArguments:@[ expectedPath, actualPath ]];
                            [task launch];
                          }
                        }
#endif
#endif
                      }

                      CFRelease(actualResponse);
                    }
                    CFRelease(expectedResponse);
                  }
                } else {
                  GWS_DNOT_REACHED();
                }
                break;
              }
            }
            CFRelease(request);
          }
        } else {
          GWS_DNOT_REACHED();
        }
        _LogResult(@"");
        if (!success) {
          ++result;
        }
      }
      _ExecuteMainThreadRunLoopSources();
    }

    [self stop];

    _ExecuteMainThreadRunLoopSources();
  }
  return result;
}

@end

#endif


================================================
FILE: src/ios/GCDWebServer/Core/GCDWebServerConnection.h
================================================
/*
 Copyright (c) 2012-2015, Pierre-Olivier Latour
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are met:
 * Redistributions of source code must retain the above copyright
 notice, this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright
 notice, this list of conditions and the following disclaimer in the
 documentation and/or other materials provided with the distribution.
 * The name of Pierre-Olivier Latour may not be used to endorse
 or promote products derived from this software without specific
 prior written permission.
 
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
 DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#import "GCDWebServer.h"

@class GCDWebServerHandler;

/**
 *  The GCDWebServerConnection class is instantiated by GCDWebServer to handle
 *  each new HTTP connection. Each instance stays alive until the connection is
 *  closed.
 *
 *  You cannot use this class directly, but it is made public so you can
 *  subclass it to override some hooks. Use the GCDWebServerOption_ConnectionClass
 *  option for GCDWebServer to install your custom subclass.
 *
 *  @warning The GCDWebServerConnection retains the GCDWebServer until the
 *  connection is closed.
 */
@interface GCDWebServerConnection : NSObject

/**
 *  Returns the GCDWebServer that owns the connection.
 */
@property(nonatomic, readonly) GCDWebServer* server;

/**
 *  Returns YES if the connection is using IPv6.
 */
@property(nonatomic, readonly, getter=isUsingIPv6) BOOL usingIPv6;

/**
 *  Returns the address of the local peer (i.e. server) of the connection
 *  as a raw "struct sockaddr".
 */
@property(nonatomic, readonly) NSData* localAddressData;

/**
 *  Returns the address of the local peer (i.e. server) of the connection
 *  as a string.
 */
@property(nonatomic, readonly) NSString* localAddressString;

/**
 *  Returns the address of the remote peer (i.e. client) of the connection
 *  as a raw "struct sockaddr".
 */
@property(nonatomic, readonly) NSData* remoteAddressData;

/**
 *  Returns the address of the remote peer (i.e. client) of the connection
 *  as a string.
 */
@property(nonatomic, readonly) NSString* remoteAddressString;

/**
 *  Returns the total number of bytes received from the remote peer (i.e. client)
 *  so far.
 */
@property(nonatomic, readonly) NSUInteger totalBytesRead;

/**
 *  Returns the total number of bytes sent to the remote peer (i.e. client) so far.
 */
@property(nonatomic, readonly) NSUInteger totalBytesWritten;

@end

/**
 *  Hooks to customize the behavior of GCDWebServer HTTP connections.
 *
 *  @warning These methods can be called on any GCD thread.
 *  Be sure to also call "super" when overriding them.
 */
@interface GCDWebServerConnection (Subclassing)

/**
 *  This method is called when the connection is opened.
 *
 *  Return NO to reject the connection e.g. after validating the local
 *  or remote address.
 */
- (BOOL)open;

/**
 *  This method is called whenever data has been received
 *  from the remote peer (i.e. client).
 *
 *  @warning Do not attempt to modify this data.
 */
- (void)didReadBytes:(const void*)bytes length:(NSUInteger)length;

/**
 *  This method is called whenever data has been sent
 *  to the remote peer (i.e. client).
 *
 *  @warning Do not attempt to modify this data.
 */
- (void)didWriteBytes:(const void*)bytes length:(NSUInteger)length;

/**
 *  This method is called after the HTTP headers have been received to
 *  allow replacing the request URL by another one.
 *
 *  The default implementation returns the original URL.
 */
- (NSURL*)rewriteRequestURL:(NSURL*)url withMethod:(NSString*)method headers:(NSDictionary*)headers;

/**
 *  Assuming a valid HTTP request was received, this method is called before
 *  the request is processed.
 *
 *  Return a non-nil GCDWebServerResponse to bypass the request processing entirely.
 *
 *  The default implementation checks for HTTP authentication if applicable
 *  and returns a barebone 401 status code response if authentication failed.
 */
- (GCDWebServerResponse*)preflightRequest:(GCDWebServerRequest*)request;

/**
 *  Assuming a valid HTTP request was received and -preflightRequest: returned nil,
 *  this method is called to process the request by executing the handler's
 *  process block.
 */
- (void)processRequest:(GCDWebServerRequest*)request completion:(GCDWebServerCompletionBlock)completion;

/**
 *  Assuming a valid HTTP request was received and either -preflightRequest:
 *  or -processRequest:completion: returned a non-nil GCDWebServerResponse,
 *  this method is called to override the response.
 *
 *  You can either modify the current response and return it, or return a
 *  completely new one.
 *
 *  The default implementation replaces any response matching the "ETag" or
 *  "Last-Modified-Date" header of the request by a barebone "Not-Modified" (304)
 *  one.
 */
- (GCDWebServerResponse*)overrideResponse:(GCDWebServerResponse*)response forRequest:(GCDWebServerRequest*)request;

/**
 *  This method is called if any error happens while validing or processing
 *  the request or if no GCDWebServerResponse was generated during processing.
 *
 *  @warning If the request was invalid (e.g. the HTTP headers were malformed),
 *  the "request" argument will be nil.
 */
- (void)abortRequest:(GCDWebServerRequest*)request withStatusCode:(NSInteger)statusCode;

/**
 *  Called when the connection is closed.
 */
- (void)close;

@end


================================================
FILE: src/ios/GCDWebServer/Core/GCDWebServerConnection.m
================================================
/*
 Copyright (c) 2012-2015, Pierre-Olivier Latour
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are met:
 * Redistributions of source code must retain the above copyright
 notice, this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright
 notice, this list of conditions and the following disclaimer in the
 documentation and/or other materials provided with the distribution.
 * The name of Pierre-Olivier Latour may not be used to endorse
 or promote products derived from this software without specific
 prior written permission.
 
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
 DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#if !__has_feature(objc_arc)
#error GCDWebServer requires ARC
#endif

#import <TargetConditionals.h>
#import <netdb.h>
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
#import <libkern/OSAtomic.h>
#endif

#import "GCDWebServerPrivate.h"

#define kHeadersReadCapacity (1 * 1024)
#define kBodyReadCapacity (256 * 1024)

typedef void (^ReadDataCompletionBlock)(BOOL success);
typedef void (^ReadHeadersCompletionBlock)(NSData* extraData);
typedef void (^ReadBodyCompletionBlock)(BOOL success);

typedef void (^WriteDataCompletionBlock)(BOOL success);
typedef void (^WriteHeadersCompletionBlock)(BOOL success);
typedef void (^WriteBodyCompletionBlock)(BOOL success);

static NSData* _CRLFData = nil;
static NSData* _CRLFCRLFData = nil;
static NSData* _continueData = nil;
static NSData* _lastChunkData = nil;
static NSString* _digestAuthenticationNonce = nil;
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
static int32_t _connectionCounter = 0;
#endif

@interface GCDWebServerConnection () {
@private
  GCDWebServer* _server;
  NSData* _localAddress;
  NSData* _remoteAddress;
  CFSocketNativeHandle _socket;
  NSUInteger _bytesRead;
  NSUInteger _bytesWritten;
  BOOL _virtualHEAD;

  CFHTTPMessageRef _requestMessage;
  GCDWebServerRequest* _request;
  GCDWebServerHandler* _handler;
  CFHTTPMessageRef _responseMessage;
  GCDWebServerResponse* _response;
  NSInteger _statusCode;

  BOOL _opened;
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
  NSUInteger _connectionIndex;
  NSString* _requestPath;
  int _requestFD;
  NSString* _responsePath;
  int _responseFD;
#endif
}
@end

@implementation GCDWebServerConnection (Read)

- (void)_readData:(NSMutableData*)data withLength:(NSUInteger)length completionBlock:(ReadDataCompletionBlock)block {
  dispatch_read(_socket, length, dispatch_get_global_queue(_server.dispatchQueuePriority, 0), ^(dispatch_data_t buffer, int error) {

    @autoreleasepool {
      if (error == 0) {
        size_t size = dispatch_data_get_size(buffer);
        if (size > 0) {
          NSUInteger originalLength = data.length;
          dispatch_data_apply(buffer, ^bool(dispatch_data_t region, size_t chunkOffset, const void* chunkBytes, size_t chunkSize) {
            [data appendBytes:chunkBytes length:chunkSize];
            return true;
          });
          [self didReadBytes:((char*)data.bytes + originalLength) length:(data.length - originalLength)];
          block(YES);
        } else {
          if (_bytesRead > 0) {
            GWS_LOG_ERROR(@"No more data available on socket %i", _socket);
          } else {
            GWS_LOG_WARNING(@"No data received from socket %i", _socket);
          }
          block(NO);
        }
      } else {
        GWS_LOG_ERROR(@"Error while reading from socket %i: %s (%i)", _socket, strerror(error), error);
        block(NO);
      }
    }

  });
}

- (void)_readHeaders:(NSMutableData*)headersData withCompletionBlock:(ReadHeadersCompletionBlock)block {
  GWS_DCHECK(_requestMessage);
  [self _readData:headersData
           withLength:NSUIntegerMax
      completionBlock:^(BOOL success) {

        if (success) {
          NSRange range = [headersData rangeOfData:_CRLFCRLFData options:0 range:NSMakeRange(0, headersData.length)];
          if (range.location == NSNotFound) {
            [self _readHeaders:headersData withCompletionBlock:block];
          } else {
            NSUInteger length = range.location + range.length;
            if (CFHTTPMessageAppendBytes(_requestMessage, headersData.bytes, length)) {
              if (CFHTTPMessageIsHeaderComplete(_requestMessage)) {
                block([headersData subdataWithRange:NSMakeRange(length, headersData.length - length)]);
              } else {
                GWS_LOG_ERROR(@"Failed parsing request headers from socket %i", _socket);
                block(nil);
              }
            } else {
              GWS_LOG_ERROR(@"Failed appending request headers data from socket %i", _socket);
              block(nil);
            }
          }
        } else {
          block(nil);
        }

      }];
}

- (void)_readBodyWithRemainingLength:(NSUInteger)length completionBlock:(ReadBodyCompletionBlock)block {
  GWS_DCHECK([_request hasBody] && ![_request usesChunkedTransferEncoding]);
  NSMutableData* bodyData = [[NSMutableData alloc] initWithCapacity:kBodyReadCapacity];
  [self _readData:bodyData
           withLength:length
      completionBlock:^(BOOL success) {

        if (success) {
          if (bodyData.length <= length) {
            NSError* error = nil;
            if ([_request performWriteData:bodyData error:&error]) {
              NSUInteger remainingLength = length - bodyData.length;
              if (remainingLength) {
                [self _readBodyWithRemainingLength:remainingLength completionBlock:block];
              } else {
                block(YES);
              }
            } else {
              GWS_LOG_ERROR(@"Failed writing request body on socket %i: %@", _socket, error);
              block(NO);
            }
          } else {
            GWS_LOG_ERROR(@"Unexpected extra content reading request body on socket %i", _socket);
            block(NO);
            GWS_DNOT_REACHED();
          }
        } else {
          block(NO);
        }

      }];
}

static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
  char buffer[size + 1];
  bcopy(bytes, buffer, size);
  buffer[size] = 0;
  char* end = NULL;
  long result = strtol(buffer, &end, 16);
  return ((end != NULL) && (*end == 0) && (result >= 0) ? result : NSNotFound);
}

- (void)_readNextBodyChunk:(NSMutableData*)chunkData completionBlock:(ReadBodyCompletionBlock)block {
  GWS_DCHECK([_request hasBody] && [_request usesChunkedTransferEncoding]);

  while (1) {
    NSRange range = [chunkData rangeOfData:_CRLFData options:0 range:NSMakeRange(0, chunkData.length)];
    if (range.location == NSNotFound) {
      break;
    }
    NSRange extensionRange = [chunkData rangeOfData:[NSData dataWithBytes:";" length:1] options:0 range:NSMakeRange(0, range.location)];  // Ignore chunk extensions
    NSUInteger length = _ScanHexNumber((char*)chunkData.bytes, extensionRange.location != NSNotFound ? extensionRange.location : range.location);
    if (length != NSNotFound) {
      if (length) {
        if (chunkData.length < range.location + range.length + length + 2) {
          break;
        }
        const char* ptr = (char*)chunkData.bytes + range.location + range.length + length;
        if ((*ptr == '\r') && (*(ptr + 1) == '\n')) {
          NSError* error = nil;
          if ([_request performWriteData:[chunkData subdataWithRange:NSMakeRange(range.location + range.length, length)] error:&error]) {
            [chunkData replaceBytesInRange:NSMakeRange(0, range.location + range.length + length + 2) withBytes:NULL length:0];
          } else {
            GWS_LOG_ERROR(@"Failed writing request body on socket %i: %@", _socket, error);
            block(NO);
            return;
          }
        } else {
          GWS_LOG_ERROR(@"Missing terminating CRLF sequence for chunk reading request body on socket %i", _socket);
          block(NO);
          return;
        }
      } else {
        NSRange trailerRange = [chunkData rangeOfData:_CRLFCRLFData options:0 range:NSMakeRange(range.location, chunkData.length - range.location)];  // Ignore trailers
        if (trailerRange.location != NSNotFound) {
          block(YES);
          return;
        }
      }
    } else {
      GWS_LOG_ERROR(@"Invalid chunk length reading request body on socket %i", _socket);
      block(NO);
      return;
    }
  }

  [self _readData:chunkData
           withLength:NSUIntegerMax
      completionBlock:^(BOOL success) {

        if (success) {
          [self _readNextBodyChunk:chunkData completionBlock:block];
        } else {
          block(NO);
        }

      }];
}

@end

@implementation GCDWebServerConnection (Write)

- (void)_writeData:(NSData*)data withCompletionBlock:(WriteDataCompletionBlock)block {
  dispatch_data_t buffer = dispatch_data_create(data.bytes, data.length, dispatch_get_global_queue(_server.dispatchQueuePriority, 0), ^{
    [data self];  // Keeps ARC from releasing data too early
  });
  dispatch_write(_socket, buffer, dispatch_get_global_queue(_server.dispatchQueuePriority, 0), ^(dispatch_data_t remainingData, int error) {

    @autoreleasepool {
      if (error == 0) {
        GWS_DCHECK(remainingData == NULL);
        [self didWriteBytes:data.bytes length:data.length];
        block(YES);
      } else {
        GWS_LOG_ERROR(@"Error while writing to socket %i: %s (%i)", _socket, strerror(error), error);
        block(NO);
      }
    }

  });
#if !OS_OBJECT_USE_OBJC_RETAIN_RELEASE
  dispatch_release(buffer);
#endif
}

- (void)_writeHeadersWithCompletionBlock:(WriteHeadersCompletionBlock)block {
  GWS_DCHECK(_responseMessage);
  CFDataRef data = CFHTTPMessageCopySerializedMessage(_responseMessage);
  [self _writeData:(__bridge NSData*)data withCompletionBlock:block];
  CFRelease(data);
}

- (void)_writeBodyWithCompletionBlock:(WriteBodyCompletionBlock)block {
  GWS_DCHECK([_response hasBody]);
  [_response performReadDataWithCompletion:^(NSData* data, NSError* error) {

    if (data) {
      if (data.length) {
        if (_response.usesChunkedTransferEncoding) {
          const char* hexString = [[NSString stringWithFormat:@"%lx", (unsigned long)data.length] UTF8String];
          size_t hexLength = strlen(hexString);
          NSData* chunk = [NSMutableData dataWithLength:(hexLength + 2 + data.length + 2)];
          if (chunk == nil) {
            GWS_LOG_ERROR(@"Failed allocating memory for response body chunk for socket %i: %@", _socket, error);
            block(NO);
            return;
          }
          char* ptr = (char*)[(NSMutableData*)chunk mutableBytes];
          bcopy(hexString, ptr, hexLength);
          ptr += hexLength;
          *ptr++ = '\r';
          *ptr++ = '\n';
          bcopy(data.bytes, ptr, data.length);
          ptr += data.length;
          *ptr++ = '\r';
          *ptr = '\n';
          data = chunk;
        }
        [self _writeData:data
            withCompletionBlock:^(BOOL success) {

              if (success) {
                [self _writeBodyWithCompletionBlock:block];
              } else {
                block(NO);
              }

            }];
      } else {
        if (_response.usesChunkedTransferEncoding) {
          [self _writeData:_lastChunkData
              withCompletionBlock:^(BOOL success) {

                block(success);

              }];
        } else {
          block(YES);
        }
      }
    } else {
      GWS_LOG_ERROR(@"Failed reading response body for socket %i: %@", _socket, error);
      block(NO);
    }

  }];
}

@end

@implementation GCDWebServerConnection

@synthesize server = _server, localAddressData = _localAddress, remoteAddressData = _remoteAddress, totalBytesRead = _bytesRead, totalBytesWritten = _bytesWritten;

+ (void)initialize {
  if (_CRLFData == nil) {
    _CRLFData = [[NSData alloc] initWithBytes:"\r\n" length:2];
    GWS_DCHECK(_CRLFData);
  }
  if (_CRLFCRLFData == nil) {
    _CRLFCRLFData = [[NSData alloc] initWithBytes:"\r\n\r\n" length:4];
    GWS_DCHECK(_CRLFCRLFData);
  }
  if (_continueData == nil) {
    CFHTTPMessageRef message = CFHTTPMessageCreateResponse(kCFAllocatorDefault, 100, NULL, kCFHTTPVersion1_1);
    _continueData = CFBridgingRelease(CFHTTPMessageCopySerializedMessage(message));
    CFRelease(message);
    GWS_DCHECK(_continueData);
  }
  if (_lastChunkData == nil) {
    _lastChunkData = [[NSData alloc] initWithBytes:"0\r\n\r\n" length:5];
  }
  if (_digestAuthenticationNonce == nil) {
    CFUUIDRef uuid = CFUUIDCreate(kCFAllocatorDefault);
    _digestAuthenticationNonce = GCDWebServerComputeMD5Digest(@"%@", CFBridgingRelease(CFUUIDCreateString(kCFAllocatorDefault, uuid)));
    CFRelease(uuid);
  }
}

- (BOOL)isUsingIPv6 {
  const struct sockaddr* localSockAddr = _localAddress.bytes;
  return (localSockAddr->sa_family == AF_INET6);
}

- (void)_initializeResponseHeadersWithStatusCode:(NSInteger)statusCode {
  _statusCode = statusCode;
  _responseMessage = CFHTTPMessageCreateResponse(kCFAllocatorDefault, statusCode, NULL, kCFHTTPVersion1_1);
  CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Connection"), CFSTR("Close"));
  CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Server"), (__bridge CFStringRef)_server.serverName);
  CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Date"), (__bridge CFStringRef)GCDWebServerFormatRFC822([NSDate date]));
}

- (void)_startProcessingRequest {
  GWS_DCHECK(_responseMessage == NULL);

  GCDWebServerResponse* preflightResponse = [self preflightRequest:_request];
  if (preflightResponse) {
    [self _finishProcessingRequest:preflightResponse];
  } else {
    [self processRequest:_request
              completion:^(GCDWebServerResponse* processResponse) {
                [self _finishProcessingRequest:processResponse];
              }];
  }
}

// http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
- (void)_finishProcessingRequest:(GCDWebServerResponse*)response {
  GWS_DCHECK(_responseMessage == NULL);
  BOOL hasBody = NO;

  if (response) {
    response = [self overrideResponse:response forRequest:_request];
  }
  if (response) {
    if ([response hasBody]) {
      [response prepareForReading];
      hasBody = !_virtualHEAD;
    }
    NSError* error = nil;
    if (hasBody && ![response performOpen:&error]) {
      GWS_LOG_ERROR(@"Failed opening response body for socket %i: %@", _socket, error);
    } else {
      _response = response;
    }
  }

  if (_response) {
    [self _initializeResponseHeadersWithStatusCode:_response.statusCode];
    if (_response.lastModifiedDate) {
      CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Last-Modified"), (__bridge CFStringRef)GCDWebServerFormatRFC822(_response.lastModifiedDate));
    }
    if (_response.eTag) {
      CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("ETag"), (__bridge CFStringRef)_response.eTag);
    }
    if ((_response.statusCode >= 200) && (_response.statusCode < 300)) {
      if (_response.cacheControlMaxAge > 0) {
        CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Cache-Control"), (__bridge CFStringRef)[NSString stringWithFormat:@"max-age=%i, public", (int)_response.cacheControlMaxAge]);
      } else {
        CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Cache-Control"), CFSTR("no-cache"));
      }
    }
    if (_response.contentType != nil) {
      CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Content-Type"), (__bridge CFStringRef)GCDWebServerNormalizeHeaderValue(_response.contentType));
    }
    if (_response.contentLength != NSUIntegerMax) {
      CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Content-Length"), (__bridge CFStringRef)[NSString stringWithFormat:@"%lu", (unsigned long)_response.contentLength]);
    }
    if (_response.usesChunkedTransferEncoding) {
      CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Transfer-Encoding"), CFSTR("chunked"));
    }
    [_response.additionalHeaders enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL* stop) {
      CFHTTPMessageSetHeaderFieldValue(_responseMessage, (__bridge CFStringRef)key, (__bridge CFStringRef)obj);
    }];
    [self _writeHeadersWithCompletionBlock:^(BOOL success) {

      if (success) {
        if (hasBody) {
          [self _writeBodyWithCompletionBlock:^(BOOL successInner) {

            [_response performClose];  // TODO: There's nothing we can do on failure as headers have already been sent

          }];
        }
      } else if (hasBody) {
        [_response performClose];
      }

    }];
  } else {
    [self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
  }
}

- (void)_readBodyWithLength:(NSUInteger)length initialData:(NSData*)initialData {
  NSError* error = nil;
  if (![_request performOpen:&error]) {
    GWS_LOG_ERROR(@"Failed opening request body for socket %i: %@", _socket, error);
    [self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
    return;
  }

  if (initialData.length) {
    if (![_request performWriteData:initialData error:&error]) {
      GWS_LOG_ERROR(@"Failed writing request body on socket %i: %@", _socket, error);
      if (![_request performClose:&error]) {
        GWS_LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error);
      }
      [self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
      return;
    }
    length -= initialData.length;
  }

  if (length) {
    [self _readBodyWithRemainingLength:length
                       completionBlock:^(BOOL success) {

                         NSError* localError = nil;
                         if ([_request performClose:&localError]) {
                           [self _startProcessingRequest];
                         } else {
                           GWS_LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error);
                           [self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
                         }

                       }];
  } else {
    if ([_request performClose:&error]) {
      [self _startProcessingRequest];
    } else {
      GWS_LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error);
      [self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
    }
  }
}

- (void)_readChunkedBodyWithInitialData:(NSData*)initialData {
  NSError* error = nil;
  if (![_request performOpen:&error]) {
    GWS_LOG_ERROR(@"Failed opening request body for socket %i: %@", _socket, error);
    [self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
    return;
  }

  NSMutableData* chunkData = [[NSMutableData alloc] initWithData:initialData];
  [self _readNextBodyChunk:chunkData
           completionBlock:^(BOOL success) {

             NSError* localError = nil;
             if ([_request performClose:&localError]) {
               [self _startProcessingRequest];
             } else {
               GWS_LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error);
               [self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
             }

           }];
}

- (void)_readRequestHeaders {
  _requestMessage = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, true);
  NSMutableData* headersData = [[NSMutableData alloc] initWithCapacity:kHeadersReadCapacity];
  [self _readHeaders:headersData
      withCompletionBlock:^(NSData* extraData) {

        if (extraData) {
          NSString* requestMethod = CFBridgingRelease(CFHTTPMessageCopyRequestMethod(_requestMessage));  // Method verbs are case-sensitive and uppercase
          if (_server.shouldAutomaticallyMapHEADToGET && [requestMethod isEqualToString:@"HEAD"]) {
            requestMethod = @"GET";
            _virtualHEAD = YES;
          }
          NSDictionary* requestHeaders = CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(_requestMessage));  // Header names are case-insensitive but CFHTTPMessageCopyAllHeaderFields() will standardize the common ones
          NSURL* requestURL = CFBridgingRelease(CFHTTPMessageCopyRequestURL(_requestMessage));
          if (requestURL) {
            requestURL = [self rewriteRequestURL:requestURL withMethod:requestMethod headers:requestHeaders];
            GWS_DCHECK(requestURL);
          }
          NSString* requestPath = requestURL ? GCDWebServerUnescapeURLString(CFBridgingRelease(CFURLCopyPath((CFURLRef)requestURL))) : nil;  // Don't use -[NSURL path] which strips the ending slash
          NSString* queryString = requestURL ? CFBridgingRelease(CFURLCopyQueryString((CFURLRef)requestURL, NULL)) : nil;  // Don't use -[NSURL query] to make sure query is not unescaped;
          NSDictionary* requestQuery = queryString ? GCDWebServerParseURLEncodedForm(queryString) : @{};
          if (requestMethod && requestURL && requestHeaders && requestPath && requestQuery) {
            for (_handler in _server.handlers) {
              _request = _handler.matchBlock(requestMethod, requestURL, requestHeaders, requestPath, requestQuery);
              if (_request) {
                break;
              }
            }
            if (_request) {
              _request.localAddressData = self.localAddressData;
              _request.remoteAddressData = self.remoteAddressData;
              if ([_request hasBody]) {
                [_request prepareForWriting];
                if (_request.usesChunkedTransferEncoding || (extraData.length <= _request.contentLength)) {
                  NSString* expectHeader = [requestHeaders objectForKey:@"Expect"];
                  if (expectHeader) {
                    if ([expectHeader caseInsensitiveCompare:@"100-continue"] == NSOrderedSame) {  // TODO: Actually validate request before continuing
                      [self _writeData:_continueData
                          withCompletionBlock:^(BOOL success) {

                            if (success) {
                              if (_request.usesChunkedTransferEncoding) {
                                [self _readChunkedBodyWithInitialData:extraData];
                              } else {
                                [self _readBodyWithLength:_request.contentLength initialData:extraData];
                              }
                            }

                          }];
                    } else {
                      GWS_LOG_ERROR(@"Unsupported 'Expect' / 'Content-Length' header combination on socket %i", _socket);
                      [self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_ExpectationFailed];
                    }
                  } else {
                    if (_request.usesChunkedTransferEncoding) {
                      [self _readChunkedBodyWithInitialData:extraData];
                    } else {
                      [self _readBodyWithLength:_request.contentLength initialData:extraData];
                    }
                  }
                } else {
                  GWS_LOG_ERROR(@"Unexpected 'Content-Length' header value on socket %i", _socket);
                  [self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_BadRequest];
                }
              } else {
                [self _startProcessingRequest];
              }
            } else {
              _request = [[GCDWebServerRequest alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:requestPath query:requestQuery];
              GWS_DCHECK(_request);
              [self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_MethodNotAllowed];
            }
          } else {
            [self abortRequest:nil withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
            GWS_DNOT_REACHED();
          }
        } else {
          [self abortRequest:nil withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
        }

      }];
}

- (id)initWithServer:(GCDWebServer*)server localAddress:(NSData*)localAddress remoteAddress:(NSData*)remoteAddress socket:(CFSocketNativeHandle)socket {
  if ((self = [super init])) {
    _server = server;
    _localAddress = localAddress;
    _remoteAddress = remoteAddress;
    _socket = socket;
    GWS_LOG_DEBUG(@"Did open connection on socket %i", _socket);

    [_server willStartConnection:self];

    if (![self open]) {
      close(_socket);
      return nil;
    }
    _opened = YES;

    [self _readRequestHeaders];
  }
  return self;
}

- (NSString*)localAddressString {
  return GCDWebServerStringFromSockAddr(_localAddress.bytes, YES);
}

- (NSString*)remoteAddressString {
  return GCDWebServerStringFromSockAddr(_remoteAddress.bytes, YES);
}

- (void)dealloc {
  int result = close(_socket);
  if (result != 0) {
    GWS_LOG_ERROR(@"Failed closing socket %i for connection: %s (%i)", _socket, strerror(errno), errno);
  } else {
    GWS_LOG_DEBUG(@"Did close connection on socket %i", _socket);
  }

  if (_opened) {
    [self close];
  }

  [_server didEndConnection:self];

  if (_requestMessage) {
    CFRelease(_requestMessage);
  }

  if (_responseMessage) {
    CFRelease(_responseMessage);
  }
}

@end

@implementation GCDWebServerConnection (Subclassing)

- (BOOL)open {
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
  if (_server.recordingEnabled) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
    _connectionIndex = OSAtomicIncrement32(&_connectionCounter);
#pragma clang diagnostic pop

    _requestPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]];
    _requestFD = open([_requestPath fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
    GWS_DCHECK(_requestFD > 0);

    _responsePath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]];
    _responseFD = open([_responsePath fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
    GWS_DCHECK(_responseFD > 0);
  }
#endif

  return YES;
}

- (void)didReadBytes:(const void*)bytes length:(NSUInteger)length {
  GWS_LOG_DEBUG(@"Connection received %lu bytes on socket %i", (unsigned long)length, _socket);
  _bytesRead += length;

#ifdef __GCDWEBSERVER_ENABLE_TESTING__
  if ((_requestFD > 0) && (write(_requestFD, bytes, length) != (ssize_t)length)) {
    GWS_LOG_ERROR(@"Failed recording request data: %s (%i)", strerror(errno), errno);
    close(_requestFD);
    _requestFD = 0;
  }
#endif
}

- (void)didWriteBytes:(const void*)bytes length:(NSUInteger)length {
  GWS_LOG_DEBUG(@"Connection sent %lu bytes on socket %i", (unsigned long)length, _socket);
  _bytesWritten += length;

#ifdef __GCDWEBSERVER_ENABLE_TESTING__
  if ((_responseFD > 0) && (write(_responseFD, bytes, length) != (ssize_t)length)) {
    GWS_LOG_ERROR(@"Failed recording response data: %s (%i)", strerror(errno), errno);
    close(_responseFD);
    _responseFD = 0;
  }
#endif
}

- (NSURL*)rewriteRequestURL:(NSURL*)url withMethod:(NSString*)method headers:(NSDictionary*)headers {
  return url;
}

// https://tools.ietf.org/html/rfc2617
- (GCDWebServerResponse*)preflightRequest:(GCDWebServerRequest*)request {
  GWS_LOG_DEBUG(@"Connection on socket %i preflighting request \"%@ %@\" with %lu bytes body", _socket, _virtualHEAD ? @"HEAD" : _request.method, _request.path, (unsigned long)_bytesRead);
  GCDWebServerResponse* response = nil;
  if (_server.authenticationBasicAccounts) {
    __block BOOL authenticated = NO;
    NSString* authorizationHeader = [request.headers objectForKey:@"Authorization"];
    if ([authorizationHeader hasPrefix:@"Basic "]) {
      NSString* basicAccount = [authorizationHeader substringFromIndex:6];
      [_server.authenticationBasicAccounts enumerateKeysAndObjectsUsingBlock:^(NSString* username, NSString* digest, BOOL* stop) {
        if ([basicAccount isEqualToString:digest]) {
          authenticated = YES;
          *stop = YES;
        }
      }];
    }
    if (!authenticated) {
      response = [GCDWebServerResponse responseWithStatusCode:kGCDWebServerHTTPStatusCode_Unauthorized];
      [response setValue:[NSString stringWithFormat:@"Basic realm=\"%@\"", _server.authenticationRealm] forAdditionalHeader:@"WWW-Authenticate"];
    }
  } else if (_server.authenticationDigestAccounts) {
    BOOL authenticated = NO;
    BOOL isStaled = NO;
    NSString* authorizationHeader = [request.headers objectForKey:@"Authorization"];
    if ([authorizationHeader hasPrefix:@"Digest "]) {
      NSString* realm = GCDWebServerExtractHeaderValueParameter(authorizationHeader, @"realm");
      if ([realm isEqualToString:_server.authenticationRealm]) {
        NSString* nonce = GCDWebServerExtractHeaderValueParameter(authorizationHeader, @"nonce");
        if ([nonce isEqualToString:_digestAuthenticationNonce]) {
          NSString* username = GCDWebServerExtractHeaderValueParameter(authorizationHeader, @"username");
          NSString* uri = GCDWebServerExtractHeaderValueParameter(authorizationHeader, @"uri");
          NSString* actualResponse = GCDWebServerExtractHeaderValueParameter(authorizationHeader, @"response");
          NSString* ha1 = [_server.authenticationDigestAccounts objectForKey:username];
          NSString* ha2 = GCDWebServerComputeMD5Digest(@"%@:%@", request.method, uri);  // We cannot use "request.path" as the query string is required
          NSString* expectedResponse = GCDWebServerComputeMD5Digest(@"%@:%@:%@", ha1, _digestAuthenticationNonce, ha2);
          if ([actualResponse isEqualToString:expectedResponse]) {
            authenticated = YES;
          }
        } else if (nonce.length) {
          isStaled = YES;
        }
      }
    }
    if (!authenticated) {
      response = [GCDWebServerResponse responseWithStatusCode:kGCDWebServerHTTPStatusCode_Unauthorized];
      [response setValue:[NSString stringWithFormat:@"Digest realm=\"%@\", nonce=\"%@\"%@", _server.authenticationRealm, _digestAuthenticationNonce, isStaled ? @", stale=TRUE" : @""] forAdditionalHeader:@"WWW-Authenticate"];  // TODO: Support Quality of Protection ("qop")
    }
  }
  return response;
}

- (void)processRequest:(GCDWebServerRequest*)request completion:(GCDWebServerCompletionBlock)completion {
  GWS_LOG_DEBUG(@"Connection on socket %i processing request \"%@ %@\" with %lu bytes body", _socket, _virtualHEAD ? @"HEAD" : _request.method, _request.path, (unsigned long)_bytesRead);
  _handler.asyncProcessBlock(request, [completion copy]);
}

// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.25
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.26
static inline BOOL _CompareResources(NSString* responseETag, NSString* requestETag, NSDate* responseLastModified, NSDate* requestLastModified) {
  if (requestLastModified && responseLastModified) {
    if ([responseLastModified compare:requestLastModified] != NSOrderedDescending) {
      return YES;
    }
  }
  if (requestETag && responseETag) {  // Per the specs "If-None-Match" must be checked after "If-Modified-Since"
    if ([requestETag isEqualToString:@"*"]) {
      return YES;
    }
    if ([responseETag isEqualToString:requestETag]) {
      return YES;
    }
  }
  return NO;
}

- (GCDWebServerResponse*)overrideResponse:(GCDWebServerResponse*)response forRequest:(GCDWebServerRequest*)request {
  if ((response.statusCode >= 200) && (response.statusCode < 300) && _CompareResources(response.eTag, request.ifNoneMatch, response.lastModifiedDate, request.ifModifiedSince)) {
    NSInteger code = [request.method isEqualToString:@"HEAD"] || [request.method isEqualToString:@"GET"] ? kGCDWebServerHTTPStatusCode_NotModified : kGCDWebServerHTTPStatusCode_PreconditionFailed;
    GCDWebServerResponse* newResponse = [GCDWebServerResponse responseWithStatusCode:code];
    newResponse.cacheControlMaxAge = response.cacheControlMaxAge;
    newResponse.lastModifiedDate = response.lastModifiedDate;
    newResponse.eTag = response.eTag;
    GWS_DCHECK(newResponse);
    return newResponse;
  }
  return response;
}

- (void)abortRequest:(GCDWebServerRequest*)request withStatusCode:(NSInteger)statusCode {
  GWS_DCHECK(_responseMessage == NULL);
  GWS_DCHECK((statusCode >= 400) && (statusCode < 600));
  [self _initializeResponseHeadersWithStatusCode:statusCode];
  [self _writeHeadersWithCompletionBlock:^(BOOL success) {
    ;  // Nothing more to do
  }];
  GWS_LOG_DEBUG(@"Connection aborted with status code %i on socket %i", (int)statusCode, _socket);
}

- (void)close {
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
  if (_requestPath) {
    BOOL success = NO;
    NSError* error = nil;
    if (_requestFD > 0) {
      close(_requestFD);
      NSString* name = [NSString stringWithFormat:@"%03lu-%@.request", (unsigned long)_connectionIndex, _virtualHEAD ? @"HEAD" : _request.method];
      success = [[NSFileManager defaultManager] moveItemAtPath:_requestPath toPath:[[[NSFileManager defaultManager] currentDirectoryPath] stringByAppendingPathComponent:name] error:&error];
    }
    if (!success) {
      GWS_LOG_ERROR(@"Failed saving recorded request: %@", error);
      GWS_DNOT_REACHED();
    }
    unlink([_requestPath fileSystemRepresentation]);
  }

  if (_responsePath) {
    BOOL success = NO;
    NSError* error = nil;
    if (_responseFD > 0) {
      close(_responseFD);
      NSString* name = [NSString stringWithFormat:@"%03lu-%i.response", (unsigned long)_connectionIndex, (int)_statusCode];
      success = [[NSFileManager defaultManager] moveItemAtPath:_responsePath toPath:[[[NSFileManager defaultManager] currentDirectoryPath] stringByAppendingPathComponent:name] error:&error];
    }
    if (!success) {
      GWS_LOG_ERROR(@"Failed saving recorded response: %@", error);
      GWS_DNOT_REACHED();
    }
    unlink([_responsePath fileSystemRepresentation]);
  }
#endif

  if (_request) {
    GWS_LOG_VERBOSE(@"[%@] %@ %i \"%@ %@\" (%lu | %lu)", self.localAddressString, self.remoteAddressString, (int)_statusCode, _virtualHEAD ? @"HEAD" : _request.method, _request.path, (unsigned long)_bytesRead, (unsigned long)_bytesWritten);
  } else {
    GWS_LOG_VERBOSE(@"[%@] %@ %i \"(invalid request)\" (%lu | %lu)", self.localAddressString, self.remoteAddressString, (int)_statusCode, (unsigned long)_bytesRead, (unsigned long)_bytesWritten);
  }
}

@end


================================================
FILE: src/ios/GCDWebServer/Core/GCDWebServerFunctions.h
================================================
/*
 Copyright (c) 2012-2015, Pierre-Olivier Latour
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are met:
 * Redistributions of source code must retain the above copyright
 notice, this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright
 notice, this list of conditions and the following disclaimer in the
 documentation and/or other materials provided with the distribution.
 * The name of Pierre-Olivier Latour may not be used to endorse
 or promote products derived from this software without specific
 prior written permission.
 
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
 DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#import <Foundation/Foundation.h>

#ifdef __cplusplus
extern "C" {
#endif

/**
 *  Converts a file extension to the corresponding MIME type.
 *  If there is no match, "application/octet-stream" is returned.
 */
NSString* GCDWebServerGetMimeTypeForExtension(NSString* extension);

/**
 *  Add percent-escapes to a string so it can be used in a URL.
 *  The legal characters ":@/?&=+" are also escaped to ensure compatibility
 *  with URL encoded forms and URL queries.
 */
NSString* GCDWebServerEscapeURLString(NSString* string);

/**
 *  Unescapes a URL percent-encoded string.
 */
NSString* GCDWebServerUnescapeURLString(NSString* string);

/**
 *  Extracts the unescaped names and values from an
 *  "application/x-www-form-urlencoded" form.
 *  http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1
 */
NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form);

/**
 *  On OS X, returns the IPv4 or IPv6 address as a string of the primary
 *  connected service or nil if not available.
 *  
 *  On iOS, returns the IPv4 or IPv6 address as a string of the WiFi
 *  interface if connected or nil otherwise.
 */
NSString* GCDWebServerGetPrimaryIPAddress(BOOL useIPv6);

/**
 *  Converts a date into a string using RFC822 formatting.
 *  https://tools.ietf.org/html/rfc822#section-5
 *  https://tools.ietf.org/html/rfc1123#section-5.2.14
 */
NSString* GCDWebServerFormatRFC822(NSDate* date);

/**
 *  Converts a RFC822 formatted string into a date.
 *  https://tools.ietf.org/html/rfc822#section-5
 *  https://tools.ietf.org/html/rfc1123#section-5.2.14
 *
 *  @warning Timezones other than GMT are not supported by this function.
 */
NSDate* GCDWebServerParseRFC822(NSString* string);

/**
 *  Converts a date into a string using IOS 8601 formatting.
 *  http://tools.ietf.org/html/rfc3339#section-5.6
 */
NSString* GCDWebServerFormatISO8601(NSDate* date);

/**
 *  Converts a ISO 8601 formatted string into a date.
 *  http://tools.ietf.org/html/rfc3339#section-5.6
 *
 *  @warning Only "calendar" variant is supported at this time and timezones
 *  other than GMT are not supported either.
 */
NSDate* GCDWebServerParseISO8601(NSString* string);

#ifdef __cplusplus
}
#endif


================================================
FILE: src/ios/GCDWebServer/Core/GCDWebServerFunctions.m
================================================
/*
 Copyright (c) 2012-2015, Pierre-Olivier Latour
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are met:
 * Redistributions of source code must retain the above copyright
 notice, this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright
 notice, this list of conditions and the following disclaimer in the
 documentation and/or other materials provided with the distribution.
 * The name of Pierre-Olivier Latour may not be used to endorse
 or promote products derived from this software without specific
 prior written permission.
 
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
 DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#if !__has_feature(objc_arc)
#error GCDWebServer requires ARC
#endif

#import <TargetConditionals.h>
#if TARGET_OS_IPHONE
#import <MobileCoreServices/MobileCoreServices.h>
#else
#import <SystemConfiguration/SystemConfiguration.h>
#endif
#import <CommonCrypto/CommonDigest.h>

#import <ifaddrs.h>
#import <net/if.h>
#import <netdb.h>

#import "GCDWebServerPrivate.h"

static NSDateFormatter* _dateFormatterRFC822 = nil;
static NSDateFormatter* _dateFormatterISO8601 = nil;
static dispatch_queue_t _dateFormatterQueue = NULL;

// TODO: Handle RFC 850 and ANSI C's asctime() format
void GCDWebServerInitializeFunctions() {
  GWS_DCHECK([NSThread isMainThread]);  // NSDateFormatter should be initialized on main thread
  if (_dateFormatterRFC822 == nil) {
    _dateFormatterRFC822 = [[NSDateFormatter alloc] init];
    _dateFormatterRFC822.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"];
    _dateFormatterRFC822.dateFormat = @"EEE',' dd MMM yyyy HH':'mm':'ss 'GMT'";
    _dateFormatterRFC822.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"];
    GWS_DCHECK(_dateFormatterRFC822);
  }
  if (_dateFormatterISO8601 == nil) {
    _dateFormatterISO8601 = [[NSDateFormatter alloc] init];
    _dateFormatterISO8601.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"];
    _dateFormatterISO8601.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss'+00:00'";
    _dateFormatterISO8601.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"];
    GWS_DCHECK(_dateFormatterISO8601);
  }
  if (_dateFormatterQueue == NULL) {
    _dateFormatterQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
    GWS_DCHECK(_dateFormatterQueue);
  }
}

NSString* GCDWebServerNormalizeHeaderValue(NSString* value) {
  if (value) {
    NSRange range = [value rangeOfString:@";"];  // Assume part before ";" separator is case-insensitive
    if (range.location != NSNotFound) {
      value = [[[value substringToIndex:range.location] lowercaseString] stringByAppendingString:[value substringFromIndex:range.location]];
    } else {
      value = [value lowercaseString];
    }
  }
  return value;
}

NSString* GCDWebServerTruncateHeaderValue(NSString* value) {
  NSRange range = [value rangeOfString:@";"];
  return range.location != NSNotFound ? [value substringToIndex:range.location] : value;
}

NSString* GCDWebServerExtractHeaderValueParameter(NSString* value, NSString* name) {
  NSString* parameter = nil;
  NSScanner* scanner = [[NSScanner alloc] initWithString:value];
  [scanner setCaseSensitive:NO];  // Assume parameter names are case-insensitive
  NSString* string = [NSString stringWithFormat:@"%@=", name];
  if ([scanner scanUpToString:string intoString:NULL]) {
    [scanner scanString:string intoString:NULL];
    if ([scanner scanString:@"\"" intoString:NULL]) {
      [scanner scanUpToString:@"\"" intoString:&parameter];
    } else {
      [scanner scanUpToCharactersFromSet:[NSCharacterSet whitespaceCharacterSet] intoString:&parameter];
    }
  }
  return parameter;
}

// http://www.w3schools.com/tags/ref_charactersets.asp
NSStringEncoding GCDWebServerStringEncodingFromCharset(NSString* charset) {
  NSStringEncoding encoding = kCFStringEncodingInvalidId;
  if (charset) {
    encoding = CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding((CFStringRef)charset));
  }
  return (encoding != kCFStringEncodingInvalidId ? encoding : NSUTF8StringEncoding);
}

NSString* GCDWebServerFormatRFC822(NSDate* date) {
  __block NSString* string;
  dispatch_sync(_dateFormatterQueue, ^{
    string = [_dateFormatterRFC822 stringFromDate:date];
  });
  return string;
}

NSDate* GCDWebServerParseRFC822(NSString* string) {
  __block NSDate* date;
  dispatch_sync(_dateFormatterQueue, ^{
    date = [_dateFormatterRFC822 dateFromString:string];
  });
  return date;
}

NSString* GCDWebServerFormatISO8601(NSDate* date) {
  __block NSString* string;
  dispatch_sync(_dateFormatterQueue, ^{
    string = [_dateFormatterISO8601 stringFromDate:date];
  });
  return string;
}

NSDate* GCDWebServerParseISO8601(NSString* string) {
  __block NSDate* date;
  dispatch_sync(_dateFormatterQueue, ^{
    date = [_dateFormatterISO8601 dateFromString:string];
  });
  return date;
}

BOOL GCDWebServerIsTextContentType(NSString* type) {
  return ([type hasPrefix:@"text/"] || [type hasPrefix:@"application/json"] || [type hasPrefix:@"application/xml"]);
}

NSString* GCDWebServerDescribeData(NSData* data, NSString* type) {
  if (GCDWebServerIsTextContentType(type)) {
    NSString* charset = GCDWebServerExtractHeaderValueParameter(type, @"charset");
    NSString* string = [[NSString alloc] initWithData:data encoding:GCDWebServerStringEncodingFromCharset(charset)];
    if (string) {
      return string;
    }
  }
  return [NSString stringWithFormat:@"<%lu bytes>", (unsigned long)data.length];
}

NSString* GCDWebServerGetMimeTypeForExtension(NSString* extension) {
  static NSDictionary* _overrides = nil;
  if (_overrides == nil) {
    _overrides = [[NSDictionary alloc] initWithObjectsAndKeys:
                                           @"text/css", @"css",
                                           nil];
  }
  NSString* mimeType = nil;
  extension = [extension lowercaseString];
  if (extension.length) {
    mimeType = [_overrides objectForKey:extension];
    if (mimeType == nil) {
      CFStringRef uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)extension, NULL);
      if (uti) {
        mimeType = CFBridgingRelease(UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType));
        CFRelease(uti);
      }
    }
  }
  return mimeType ? mimeType : kGCDWebServerDefaultMimeType;
}

NSString* GCDWebServerEscapeURLString(NSString* string) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
  return CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)string, NULL, CFSTR(":@/?&=+"), kCFStringEncodingUTF8));
#pragma clang diagnostic pop
}

NSString* GCDWebServerUnescapeURLString(NSString* string) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
  return CFBridgingRelease(CFURLCreateStringByReplacingPercentEscapesUsingEncoding(kCFAllocatorDefault, (CFStringRef)string, CFSTR(""), kCFStringEncodingUTF8));
#pragma clang diagnostic pop
}

NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form) {
  NSMutableDictionary* parameters = [NSMutableDictionary dictionary];
  NSScanner* scanner = [[NSScanner alloc] initWithString:form];
  [scanner setCharactersToBeSkipped:nil];
  while (1) {
    NSString* key = nil;
    if (![scanner scanUpToString:@"=" intoString:&key] || [scanner isAtEnd]) {
      break;
    }
    [scanner setScanLocation:([scanner scanLocation] + 1)];

    NSString* value = nil;
    [scanner scanUpToString:@"&" intoString:&value];
    if (value == nil) {
      value = @"";
    }

    key = [key stringByReplacingOccurrencesOfString:@"+" withString:@" "];
    NSString* unescapedKey = key ? GCDWebServerUnescapeURLString(key) : nil;
    value = [value stringByReplacingOccurrencesOfString:@"+" withString:@" "];
    NSString* unescapedValue = value ? GCDWebServerUnescapeURLString(value) : nil;
    if (unescapedKey && unescapedValue) {
      [parameters setObject:unescapedValue forKey:unescapedKey];
    } else {
      GWS_LOG_WARNING(@"Failed parsing URL encoded form for key \"%@\" and value \"%@\"", key, value);
      GWS_DNOT_REACHED();
    }

    if ([scanner isAtEnd]) {
      break;
    }
    [scanner setScanLocation:([scanner scanLocation] + 1)];
  }
  return parameters;
}

NSString* GCDWebServerStringFromSockAddr(const struct sockaddr* addr, BOOL includeService) {
  NSString* string = nil;
  char hostBuffer[NI_MAXHOST];
  char serviceBuffer[NI_MAXSERV];
  if (getnameinfo(addr, addr->sa_len, hostBuffer, sizeof(hostBuffer), serviceBuffer, sizeof(serviceBuffer), NI_NUMERICHOST | NI_NUMERICSERV | NI_NOFQDN) >= 0) {
    string = includeService ? [NSString stringWithFormat:@"%s:%s", hostBuffer, serviceBuffer] : [NSString stringWithUTF8String:hostBuffer];
  } else {
    GWS_DNOT_REACHED();
  }
  return string;
}

NSString* GCDWebServerGetPrimaryIPAddress(BOOL useIPv6) {
  NSString* address = nil;
#if TARGET_OS_IPHONE
#if !TARGET_IPHONE_SIMULATOR && !TARGET_OS_TV
  const char* primaryInterface = "en0";  // WiFi interface on iOS
#endif
#else
  const char* primaryInterface = NULL;
  SCDynamicStoreRef store = SCDynamicStoreCreate(kCFAllocatorDefault, CFSTR("GCDWebServer"), NULL, NULL);
  if (store) {
    CFPropertyListRef info = SCDynamicStoreCopyValue(store, CFSTR("State:/Network/Global/IPv4"));  // There is no equivalent for IPv6 but the primary interface should be the same
    if (info) {
      primaryInterface = [[NSString stringWithString:[(__bridge NSDictionary*)info objectForKey:@"PrimaryInterface"]] UTF8String];
      CFRelease(info);
    }
    CFRelease(store);
  }
  if (primaryInterface == NULL) {
    primaryInterface = "lo0";
  }
#endif
  struct ifaddrs* list;
  if (getifaddrs(&list) >= 0) {
    for (struct ifaddrs* ifap = list; ifap; ifap = ifap->ifa_next) {
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_TV
      // Assume en0 is Ethernet and en1 is WiFi since there is no way to use SystemConfiguration framework in iOS Simulator
      // Assumption holds for Apple TV running tvOS
      if (strcmp(ifap->ifa_name, "en0") && strcmp(ifap->ifa_name, "en1"))
#else
      if (strcmp(ifap->ifa_name, primaryInterface))
#endif
      {
        continue;
      }
      if ((ifap->ifa_flags & IFF_UP) && ((!useIPv6 && (ifap->ifa_addr->sa_family == AF_INET)) || (useIPv6 && (ifap->ifa_addr->sa_family == AF_INET6)))) {
        address = GCDWebServerStringFromSockAddr(ifap->ifa_addr, NO);
        break;
      }
    }
    freeifaddrs(list);
  }
  return address;
}

NSString* GCDWebServerComputeMD5Digest(NSString* format, ...) {
  va_list arguments;
  va_start(arguments, format);
  const char* string = [[[NSString alloc] initWithFormat:format arguments:arguments] UTF8String];
  va_end(arguments);
  unsigned char md5[CC_MD5_DIGEST_LENGTH];
  CC_MD5(string, (CC_LONG)strlen(string), md5);
  char buffer[2 * CC_MD5_DIGEST_LENGTH + 1];
  for (int i = 0; i < CC_MD5_DIGEST_LENGTH; ++i) {
    unsigned char byte = md5[i];
    unsigned char byteHi = (byte & 0xF0) >> 4;
    buffer[2 * i + 0] = byteHi >= 10 ? 'a' + byteHi - 10 : '0' + byteHi;
    unsigned char byteLo = byte & 0x0F;
    buffer[2 * i + 1] = byteLo >= 10 ? 'a' + byteLo - 10 : '0' + byteLo;
  }
  buffer[2 * CC_MD5_DIGEST_LENGTH] = 0;
  return [NSString stringWithUTF8String:buffer];
}


================================================
FILE: src/ios/GCDWebServer/Core/GCDWebServerHTTPStatusCodes.h
================================================
/*
 Copyright (c) 2012-2015, Pierre-Olivier Latour
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are met:
 * Redistributions of source code must retain the above copyright
 notice, this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright
 notice, this list of conditions and the following disclaimer in the
 documentation and/or other materials provided with the distribution.
 * The name of Pierre-Olivier Latour may not be used to endorse
 or promote products derived from this software without specific
 prior written permission.
 
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
 DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

// http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
// http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml

#import <Foundation/Foundation.h>

/**
 *  Convenience constants for "informational" HTTP status codes.
 */
typedef NS_ENUM(NSInteger, GCDWebServerInformationalHTTPStatusCode) {
  kGCDWebServerHTTPStatusCode_Continue = 100,
  kGCDWebServerHTTPStatusCode_SwitchingProtocols = 101,
  kGCDWebServerHTTPStatusCode_Processing = 102
};

/**
 *  Convenience constants for "successful" HTTP status codes.
 */
typedef NS_ENUM(NSInteger, GCDWebServerSuccessfulHTTPStatusCode) {
  kGCDWebServerHTTPStatusCode_OK = 200,
  kGCDWebServerHTTPStatusCode_Created = 201,
  kGCDWebServerHTTPStatusCode_Accepted = 202,
  kGCDWebServerHTTPStatusCode_NonAuthoritativeInformation = 203,
  kGCDWebServerHTTPStatusCode_NoContent = 204,
  kGCDWebServerHTTPStatusCode_ResetContent = 205,
  kGCDWebServerHTTPStatusCode_PartialContent = 206,
  kGCDWebServerHTTPStatusCode_MultiStatus = 207,
  kGCDWebServerHTTPStatusCode_AlreadyReported = 208
};

/**
 *  Convenience constants for "redirection" HTTP status codes.
 */
typedef NS_ENUM(NSInteger, GCDWebServerRedirectionHTTPStatusCode) {
  kGCDWebServerHTTPStatusCode_MultipleChoices = 300,
  kGCDWebServerHTTPStatusCode_MovedPermanently = 301,
  kGCDWebServerHTTPStatusCode_Found = 302,
  kGCDWebServerHTTPStatusCode_SeeOther = 303,
  kGCDWebServerHTTPStatusCode_NotModified = 304,
  kGCDWebServerHTTPStatusCode_UseProxy = 305,
  kGCDWebServerHTTPStatusCode_TemporaryRedirect = 307,
  kGCDWebServerHTTPStatusCode_PermanentRedirect = 308
};

/**
 *  Convenience constants for "client error" HTTP status codes.
 */
typedef NS_ENUM(NSInteger, GCDWebServerClientErrorHTTPStatusCode) {
  kGCDWebServerHTTPStatusCode_BadRequest = 400,
  kGCDWebServerHTTPStatusCode_Unauthorized = 401,
  kGCDWebServerHTTPStatusCode_PaymentRequired = 402,
  kGCDWebServerHTTPStatusCode_Forbidden = 403,
  kGCDWebServerHTTPStatusCode_NotFound = 404,
  kGCDWebServerHTTPStatusCode_MethodNotAllowed = 405,
  kGCDWebServerHTTPStatusCode_NotAcceptable = 406,
  kGCDWebServerHTTPStatusCode_ProxyAuthenticationRequired = 407,
  kGCDWebServerHTTPStatusCode_RequestTimeout = 408,
  kGCDWebServerHTTPStatusCode_Conflict = 409,
  kGCDWebServerHTTPStatusCode_Gone = 410,
  kGCDWebServerHTTPStatusCode_LengthRequired = 411,
  kGCDWebServerHTTPStatusCode_PreconditionFailed = 412,
  kGCDWebServerHTTPStatusCode_RequestEntityTooLarge = 413,
  kGCDWebServerHTTPStatusCode_R
Download .txt
gitextract_ak8tiifl/

├── .appveyor.yml
├── .eslintrc.yml
├── .gitignore
├── .jshintrc
├── .travis.yml
├── LICENSE
├── README.md
├── RELEASENOTES.md
├── package.json
├── plugin.xml
├── src/
│   ├── ios/
│   │   ├── CDVWKProcessPoolFactory.h
│   │   ├── CDVWKProcessPoolFactory.m
│   │   ├── CDVWKWebViewEngine.h
│   │   ├── CDVWKWebViewEngine.m
│   │   ├── CDVWKWebViewUIDelegate.h
│   │   ├── CDVWKWebViewUIDelegate.m
│   │   ├── GCDWebServer/
│   │   │   ├── Core/
│   │   │   │   ├── GCDWebServer.h
│   │   │   │   ├── GCDWebServer.m
│   │   │   │   ├── GCDWebServerConnection.h
│   │   │   │   ├── GCDWebServerConnection.m
│   │   │   │   ├── GCDWebServerFunctions.h
│   │   │   │   ├── GCDWebServerFunctions.m
│   │   │   │   ├── GCDWebServerHTTPStatusCodes.h
│   │   │   │   ├── GCDWebServerPrivate.h
│   │   │   │   ├── GCDWebServerRequest.h
│   │   │   │   ├── GCDWebServerRequest.m
│   │   │   │   ├── GCDWebServerResponse.h
│   │   │   │   └── GCDWebServerResponse.m
│   │   │   ├── Requests/
│   │   │   │   ├── GCDWebServerDataRequest.h
│   │   │   │   ├── GCDWebServerDataRequest.m
│   │   │   │   ├── GCDWebServerFileRequest.h
│   │   │   │   ├── GCDWebServerFileRequest.m
│   │   │   │   ├── GCDWebServerMultiPartFormRequest.h
│   │   │   │   ├── GCDWebServerMultiPartFormRequest.m
│   │   │   │   ├── GCDWebServerURLEncodedFormRequest.h
│   │   │   │   └── GCDWebServerURLEncodedFormRequest.m
│   │   │   └── Responses/
│   │   │       ├── GCDWebServerDataResponse.h
│   │   │       ├── GCDWebServerDataResponse.m
│   │   │       ├── GCDWebServerErrorResponse.h
│   │   │       ├── GCDWebServerErrorResponse.m
│   │   │       ├── GCDWebServerFileResponse.h
│   │   │       ├── GCDWebServerFileResponse.m
│   │   │       ├── GCDWebServerStreamedResponse.h
│   │   │       └── GCDWebServerStreamedResponse.m
│   │   ├── LICENSE
│   │   └── wk-plugin.js
│   └── www/
│       └── ios/
│           └── ios-wkwebview-exec.js
└── tests/
    ├── ios/
    │   ├── CDVWKWebViewEngineTest/
    │   │   ├── .gitignore
    │   │   ├── CDVWKWebViewEngineLibTests/
    │   │   │   ├── CDVWKWebViewEngineTest.m
    │   │   │   └── Info.plist
    │   │   └── CDVWKWebViewEngineTest.xcodeproj/
    │   │       ├── project.pbxproj
    │   │       ├── project.xcworkspace/
    │   │       │   ├── contents.xcworkspacedata
    │   │       │   └── xcshareddata/
    │   │       │       └── CDVWKWebViewEngineTest.xccheckout
    │   │       └── xcshareddata/
    │   │           └── xcschemes/
    │   │               ├── CDVWKWebViewEngineLib.xcscheme
    │   │               └── CDVWKWebViewEngineLibTests.xcscheme
    │   ├── CDVWKWebViewEngineTest.xcworkspace/
    │   │   ├── contents.xcworkspacedata
    │   │   └── xcshareddata/
    │   │       ├── CDVWKWebViewEngineTest.xccheckout
    │   │       └── xcschemes/
    │   │           └── CordovaLib.xcscheme
    │   ├── README.md
    │   ├── package.json
    │   └── test.xcconfig
    ├── package.json
    ├── plugin.xml
    └── tests.js
Download .txt
SYMBOL INDEX (11 symbols across 5 files)

FILE: src/ios/GCDWebServer/Core/GCDWebServer.h
  type GCDWebServerRequest (line 43) | typedef GCDWebServerRequest* (^GCDWebServerMatchBlock)(NSString* request...
  type GCDWebServerResponse (line 55) | typedef GCDWebServerResponse* (^GCDWebServerProcessBlock)(__kindof GCDWe...

FILE: src/ios/GCDWebServer/Core/GCDWebServerPrivate.h
  function BOOL (line 184) | static inline BOOL GCDWebServerIsValidByteRange(NSRange range) {
  type sockaddr (line 200) | struct sockaddr

FILE: src/ios/GCDWebServer/Responses/GCDWebServerStreamedResponse.h
  type NSData (line 35) | typedef NSData* (^GCDWebServerStreamBlock)(NSError** error);

FILE: src/ios/wk-plugin.js
  function normalizeURL (line 11) | function normalizeURL(url) {

FILE: src/www/ios/ios-wkwebview-exec.js
  function massageArgsJsToNative (line 30) | function massageArgsJsToNative (args) {
  function massageMessageNativeToJs (line 48) | function massageMessageNativeToJs (message) {
  function convertMessageToArgsNativeToJs (line 65) | function convertMessageToArgsNativeToJs (message) {
  function cordovaExec (line 143) | function cordovaExec () {
  function execProxy (line 149) | function execProxy () {
Condensed preview — 64 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (396K chars).
[
  {
    "path": ".appveyor.yml",
    "chars": 512,
    "preview": "# appveyor file\n# http://www.appveyor.com/docs/appveyor-yml\n\nmax_jobs: 1\n\nshallow_clone: true\n\ninit:\n  - git config --gl"
  },
  {
    "path": ".eslintrc.yml",
    "chars": 157,
    "preview": "root: true\nextends: semistandard\nrules:\n  indent:\n    - error\n    - 4\n  camelcase: off\n  padded-blocks: off\n  operator-l"
  },
  {
    "path": ".gitignore",
    "chars": 177,
    "preview": "#If ignorance is bliss, then somebody knock the smile off my face\n\n*.csproj.user\n*.suo\n*.cache\nThumbs.db\n*.DS_Store\n\n*."
  },
  {
    "path": ".jshintrc",
    "chars": 283,
    "preview": "{\n    \"browser\": true\n  , \"devel\": true\n  , \"bitwise\": true\n  , \"undef\": true\n  , \"trailing\": true\n  , \"quotmark\": false"
  },
  {
    "path": ".travis.yml",
    "chars": 247,
    "preview": "osx_image: xcode8\nlanguage: objective-c\nsudo: false\nbefore_install:\n    - rvm get head\n    - npm cache clean -f\n    - np"
  },
  {
    "path": "LICENSE",
    "chars": 11357,
    "preview": "\n                                 Apache License\n                           Version 2.0, January 2004\n                  "
  },
  {
    "path": "README.md",
    "chars": 150,
    "preview": "# DEPRECATED\n\nRepo moved to: [https://github.com/ionic-team/cordova-plugin-ionic-webview](https://github.com/ionic-team/"
  },
  {
    "path": "RELEASENOTES.md",
    "chars": 6107,
    "preview": "<!--\n#\n# Licensed to the Apache Software Foundation (ASF) under one\n# or more contributor license agreements.  See the N"
  },
  {
    "path": "package.json",
    "chars": 995,
    "preview": "{\n  \"name\": \"cordova-plugin-wkwebview-engine\",\n  \"version\": \"1.1.6\",\n  \"description\": \"The official Apache Cordova WKWeb"
  },
  {
    "path": "plugin.xml",
    "chars": 5110,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<!--\n  Licensed to the Apache Software Foundation (ASF) under one\n  or more cont"
  },
  {
    "path": "src/ios/CDVWKProcessPoolFactory.h",
    "chars": 981,
    "preview": "/*\n Licensed to the Apache Software Foundation (ASF) under one\n or more contributor license agreements.  See the NOTICE "
  },
  {
    "path": "src/ios/CDVWKProcessPoolFactory.m",
    "chars": 1362,
    "preview": "/*\n Licensed to the Apache Software Foundation (ASF) under one\n or more contributor license agreements.  See the NOTICE "
  },
  {
    "path": "src/ios/CDVWKWebViewEngine.h",
    "chars": 1018,
    "preview": "/*\n Licensed to the Apache Software Foundation (ASF) under one\n or more contributor license agreements.  See the NOTICE "
  },
  {
    "path": "src/ios/CDVWKWebViewEngine.m",
    "chars": 25213,
    "preview": "/*\n Licensed to the Apache Software Foundation (ASF) under one\n or more contributor license agreements.  See the NOTICE "
  },
  {
    "path": "src/ios/CDVWKWebViewUIDelegate.h",
    "chars": 963,
    "preview": "/*\n Licensed to the Apache Software Foundation (ASF) under one\n or more contributor license agreements.  See the NOTICE "
  },
  {
    "path": "src/ios/CDVWKWebViewUIDelegate.m",
    "chars": 5259,
    "preview": "/*\n Licensed to the Apache Software Foundation (ASF) under one\n or more contributor license agreements.  See the NOTICE "
  },
  {
    "path": "src/ios/GCDWebServer/Core/GCDWebServer.h",
    "chars": 22556,
    "preview": "/*\n Copyright (c) 2012-2015, Pierre-Olivier Latour\n All rights reserved.\n \n Redistribution and use in source and binary "
  },
  {
    "path": "src/ios/GCDWebServer/Core/GCDWebServer.m",
    "chars": 53384,
    "preview": "/*\n Copyright (c) 2012-2015, Pierre-Olivier Latour\n All rights reserved.\n \n Redistribution and use in source and binary "
  },
  {
    "path": "src/ios/GCDWebServer/Core/GCDWebServerConnection.h",
    "chars": 6245,
    "preview": "/*\n Copyright (c) 2012-2015, Pierre-Olivier Latour\n All rights reserved.\n \n Redistribution and use in source and binary "
  },
  {
    "path": "src/ios/GCDWebServer/Core/GCDWebServerConnection.m",
    "chars": 35126,
    "preview": "/*\n Copyright (c) 2012-2015, Pierre-Olivier Latour\n All rights reserved.\n \n Redistribution and use in source and binary "
  },
  {
    "path": "src/ios/GCDWebServer/Core/GCDWebServerFunctions.h",
    "chars": 3659,
    "preview": "/*\n Copyright (c) 2012-2015, Pierre-Olivier Latour\n All rights reserved.\n \n Redistribution and use in source and binary "
  },
  {
    "path": "src/ios/GCDWebServer/Core/GCDWebServerFunctions.m",
    "chars": 12092,
    "preview": "/*\n Copyright (c) 2012-2015, Pierre-Olivier Latour\n All rights reserved.\n \n Redistribution and use in source and binary "
  },
  {
    "path": "src/ios/GCDWebServer/Core/GCDWebServerHTTPStatusCodes.h",
    "chars": 5241,
    "preview": "/*\n Copyright (c) 2012-2015, Pierre-Olivier Latour\n All rights reserved.\n \n Redistribution and use in source and binary "
  },
  {
    "path": "src/ios/GCDWebServer/Core/GCDWebServerPrivate.h",
    "chars": 9815,
    "preview": "/*\n Copyright (c) 2012-2015, Pierre-Olivier Latour\n All rights reserved.\n \n Redistribution and use in source and binary "
  },
  {
    "path": "src/ios/GCDWebServer/Core/GCDWebServerRequest.h",
    "chars": 7030,
    "preview": "/*\n Copyright (c) 2012-2015, Pierre-Olivier Latour\n All rights reserved.\n \n Redistribution and use in source and binary "
  },
  {
    "path": "src/ios/GCDWebServer/Core/GCDWebServerRequest.m",
    "chars": 10994,
    "preview": "/*\n Copyright (c) 2012-2015, Pierre-Olivier Latour\n All rights reserved.\n \n Redistribution and use in source and binary "
  },
  {
    "path": "src/ios/GCDWebServer/Core/GCDWebServerResponse.h",
    "chars": 6889,
    "preview": "/*\n Copyright (c) 2012-2015, Pierre-Olivier Latour\n All rights reserved.\n \n Redistribution and use in source and binary "
  },
  {
    "path": "src/ios/GCDWebServer/Core/GCDWebServerResponse.m",
    "chars": 8957,
    "preview": "/*\n Copyright (c) 2012-2015, Pierre-Olivier Latour\n All rights reserved.\n \n Redistribution and use in source and binary "
  },
  {
    "path": "src/ios/GCDWebServer/Requests/GCDWebServerDataRequest.h",
    "chars": 2394,
    "preview": "/*\n Copyright (c) 2012-2015, Pierre-Olivier Latour\n All rights reserved.\n \n Redistribution and use in source and binary "
  },
  {
    "path": "src/ios/GCDWebServer/Requests/GCDWebServerDataRequest.m",
    "chars": 3497,
    "preview": "/*\n Copyright (c) 2012-2015, Pierre-Olivier Latour\n All rights reserved.\n \n Redistribution and use in source and binary "
  },
  {
    "path": "src/ios/GCDWebServer/Requests/GCDWebServerFileRequest.h",
    "chars": 2046,
    "preview": "/*\n Copyright (c) 2012-2015, Pierre-Olivier Latour\n All rights reserved.\n \n Redistribution and use in source and binary "
  },
  {
    "path": "src/ios/GCDWebServer/Requests/GCDWebServerFileRequest.m",
    "chars": 3874,
    "preview": "/*\n Copyright (c) 2012-2015, Pierre-Olivier Latour\n All rights reserved.\n \n Redistribution and use in source and binary "
  },
  {
    "path": "src/ios/GCDWebServer/Requests/GCDWebServerMultiPartFormRequest.h",
    "chars": 4379,
    "preview": "/*\n Copyright (c) 2012-2015, Pierre-Olivier Latour\n All rights reserved.\n \n Redistribution and use in source and binary "
  },
  {
    "path": "src/ios/GCDWebServer/Requests/GCDWebServerMultiPartFormRequest.m",
    "chars": 15614,
    "preview": "/*\n Copyright (c) 2012-2015, Pierre-Olivier Latour\n All rights reserved.\n \n Redistribution and use in source and binary "
  },
  {
    "path": "src/ios/GCDWebServer/Requests/GCDWebServerURLEncodedFormRequest.h",
    "chars": 2164,
    "preview": "/*\n Copyright (c) 2012-2015, Pierre-Olivier Latour\n All rights reserved.\n \n Redistribution and use in source and binary "
  },
  {
    "path": "src/ios/GCDWebServer/Requests/GCDWebServerURLEncodedFormRequest.m",
    "chars": 2604,
    "preview": "/*\n Copyright (c) 2012-2015, Pierre-Olivier Latour\n All rights reserved.\n \n Redistribution and use in source and binary "
  },
  {
    "path": "src/ios/GCDWebServer/Responses/GCDWebServerDataResponse.h",
    "chars": 3730,
    "preview": "/*\n Copyright (c) 2012-2015, Pierre-Olivier Latour\n All rights reserved.\n \n Redistribution and use in source and binary "
  },
  {
    "path": "src/ios/GCDWebServer/Responses/GCDWebServerDataResponse.m",
    "chars": 4653,
    "preview": "/*\n Copyright (c) 2012-2015, Pierre-Olivier Latour\n All rights reserved.\n \n Redistribution and use in source and binary "
  },
  {
    "path": "src/ios/GCDWebServer/Responses/GCDWebServerErrorResponse.h",
    "chars": 3915,
    "preview": "/*\n Copyright (c) 2012-2015, Pierre-Olivier Latour\n All rights reserved.\n \n Redistribution and use in source and binary "
  },
  {
    "path": "src/ios/GCDWebServer/Responses/GCDWebServerErrorResponse.m",
    "chars": 6432,
    "preview": "/*\n Copyright (c) 2012-2015, Pierre-Olivier Latour\n All rights reserved.\n \n Redistribution and use in source and binary "
  },
  {
    "path": "src/ios/GCDWebServer/Responses/GCDWebServerFileResponse.h",
    "chars": 3906,
    "preview": "/*\n Copyright (c) 2012-2015, Pierre-Olivier Latour\n All rights reserved.\n \n Redistribution and use in source and binary "
  },
  {
    "path": "src/ios/GCDWebServer/Responses/GCDWebServerFileResponse.m",
    "chars": 6840,
    "preview": "/*\n Copyright (c) 2012-2015, Pierre-Olivier Latour\n All rights reserved.\n \n Redistribution and use in source and binary "
  },
  {
    "path": "src/ios/GCDWebServer/Responses/GCDWebServerStreamedResponse.h",
    "chars": 3287,
    "preview": "/*\n Copyright (c) 2012-2015, Pierre-Olivier Latour\n All rights reserved.\n \n Redistribution and use in source and binary "
  },
  {
    "path": "src/ios/GCDWebServer/Responses/GCDWebServerStreamedResponse.m",
    "chars": 3008,
    "preview": "/*\n Copyright (c) 2012-2015, Pierre-Olivier Latour\n All rights reserved.\n \n Redistribution and use in source and binary "
  },
  {
    "path": "src/ios/LICENSE",
    "chars": 1061,
    "preview": "Copyright (c) 2012 Niklas von Hertzen\n\nPermission is hereby granted, free of charge, to any person\nobtaining a copy of t"
  },
  {
    "path": "src/ios/wk-plugin.js",
    "chars": 1779,
    "preview": "\n(function _wk_plugin() {\n  // Check if we are running in WKWebView\n  if (!window.webkit || !window.webkit.messageHandle"
  },
  {
    "path": "src/www/ios/ios-wkwebview-exec.js",
    "chars": 6059,
    "preview": "/*\n *\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements.  See the "
  },
  {
    "path": "tests/ios/CDVWKWebViewEngineTest/.gitignore",
    "chars": 5,
    "preview": "build"
  },
  {
    "path": "tests/ios/CDVWKWebViewEngineTest/CDVWKWebViewEngineLibTests/CDVWKWebViewEngineTest.m",
    "chars": 11559,
    "preview": "/*\n Licensed to the Apache Software Foundation (ASF) under one\n or more contributor license agreements.  See the NOTICE "
  },
  {
    "path": "tests/ios/CDVWKWebViewEngineTest/CDVWKWebViewEngineLibTests/Info.plist",
    "chars": 733,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "tests/ios/CDVWKWebViewEngineTest/CDVWKWebViewEngineTest.xcodeproj/project.pbxproj",
    "chars": 22939,
    "preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 46;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
  },
  {
    "path": "tests/ios/CDVWKWebViewEngineTest/CDVWKWebViewEngineTest.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
    "chars": 167,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"self:CDVWKWebViewEng"
  },
  {
    "path": "tests/ios/CDVWKWebViewEngineTest/CDVWKWebViewEngineTest.xcodeproj/project.xcworkspace/xcshareddata/CDVWKWebViewEngineTest.xccheckout",
    "chars": 1644,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "tests/ios/CDVWKWebViewEngineTest/CDVWKWebViewEngineTest.xcodeproj/xcshareddata/xcschemes/CDVWKWebViewEngineLib.xcscheme",
    "chars": 2964,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"0830\"\n   version = \"1.3\">\n   <BuildAction\n      "
  },
  {
    "path": "tests/ios/CDVWKWebViewEngineTest/CDVWKWebViewEngineTest.xcodeproj/xcshareddata/xcschemes/CDVWKWebViewEngineLibTests.xcscheme",
    "chars": 4088,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"0830\"\n   version = \"1.3\">\n   <BuildAction\n      "
  },
  {
    "path": "tests/ios/CDVWKWebViewEngineTest.xcworkspace/contents.xcworkspacedata",
    "chars": 195,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"container:CDVWKWebVi"
  },
  {
    "path": "tests/ios/CDVWKWebViewEngineTest.xcworkspace/xcshareddata/CDVWKWebViewEngineTest.xccheckout",
    "chars": 1644,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "tests/ios/CDVWKWebViewEngineTest.xcworkspace/xcshareddata/xcschemes/CordovaLib.xcscheme",
    "chars": 2961,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"0830\"\n   version = \"1.3\">\n   <BuildAction\n      "
  },
  {
    "path": "tests/ios/README.md",
    "chars": 1358,
    "preview": "<!--\n# license: Licensed to the Apache Software Foundation (ASF) under one\n#         or more contributor license agreeme"
  },
  {
    "path": "tests/ios/package.json",
    "chars": 515,
    "preview": "{\n    \"name\": \"cordova-plugin-wkwebview-engine-test-ios\",\n    \"version\": \"1.0.0\",\n    \"description\": \"iOS Unit Tests for"
  },
  {
    "path": "tests/ios/test.xcconfig",
    "chars": 857,
    "preview": "//\n// Licensed to the Apache Software Foundation (ASF) under one\n// or more contributor license agreements.  See the NOT"
  },
  {
    "path": "tests/package.json",
    "chars": 280,
    "preview": "{\n  \"name\": \"cordova-plugin-wkwebview-engine-tests\",\n  \"version\": \"1.1.3-dev\",\n  \"description\": \"\",\n  \"cordova\": {\n    \""
  },
  {
    "path": "tests/plugin.xml",
    "chars": 1122,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  Licensed to the Apache Software Foundation (ASF) under one\n  or more contr"
  },
  {
    "path": "tests/tests.js",
    "chars": 1453,
    "preview": "/*\n *\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements.  See the "
  }
]

About this extraction

This page contains the full source code of the driftyco/cordova-plugin-wkwebview-engine GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 64 files (368.8 KB), approximately 94.0k tokens, and a symbol index with 11 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!