diff --git a/README.md b/README.md
index 19355761..58c6d14b 100644
--- a/README.md
+++ b/README.md
@@ -111,6 +111,60 @@ the plugin now restricts access to only the app itself.
Whether to use a dark styled keyboard on iOS
+#### Proxy requests to avoid CORS errors (iOS only) (BETA)
+
+```xml
+
+```
+
+All requests which starts with `/api/` will be forwarder to proxyUrl
+(eg. https://www.domain.com/api/)
+
+* `path` - path which will be proxied (all starts with that path)
+* `proxyUrl` - where to forward
+* `sslCheck` - (optional) mode of ssl `nocheck`, `default`, `pinned`
+* `sslCheckChain` - (optional) if "yes" then it will compare all certificates in the chain in order to pin your intermediate certificate
+* `useCertificates` - (optional) comma separated `.cer` files to use for the pining
+* `clearCookies` - (optional) default `no`. If `yes` all cookies will be removed on app start
+
+##### Proxy SSL Pining
+
+There are 3 modes which can be set for the sslCheck
+
+* `default` - (used by default). Check will be by iOS
+* `nocheck` - will accept all certificates, even self-signed
+* `pinned` - will accept connections only with provided DER certificates with `.cer` extension
+
+The certificate files should be provided in the `www/certificates` directory
+
+It is also possible to explicitly specify which certificates will be used by providing
+there's names (comma separated) in the `useCertificates` attribute of `wkproxy`. If the `useCertificates`
+is not provided, it will use all `.cer` files.
+
+How to get DER certificate from my server?
+
+```bash
+# Leaf certificate
+openssl s_client -connect my-server.com:443 -showcerts < /dev/null | openssl x509 -outform DER > mycert.cer
+```
+
+```bash
+# List all certificate chain
+openssl s_client -connect my-server.com:443 -showcerts
+
+# Take (copy) one needed incliding "-----BEGIN CERTIFICATE-----" and "-----END CERTIFICATE-----"
+# Create file "certificate.pem" and add all into the file
+# Generate DER certificate from PEM with command
+openssl x509 -in certificate.pem -outform der -out certificate.cer
+```
+
+
+##### Cookies
+
+Cookies are stored on the native system and are not forwarded to the WKWebView.
+
+
+
## Plugin Requirements
* **iOS**: iOS 10+ and `cordova-ios` 4+
diff --git a/plugin.xml b/plugin.xml
index f0b9e13e..5af01c36 100644
--- a/plugin.xml
+++ b/plugin.xml
@@ -36,6 +36,8 @@
+
+
@@ -77,6 +79,9 @@
+
+
+
diff --git a/src/ios/CDVWKCorsProxy.h b/src/ios/CDVWKCorsProxy.h
new file mode 100644
index 00000000..ecd1590a
--- /dev/null
+++ b/src/ios/CDVWKCorsProxy.h
@@ -0,0 +1,201 @@
+// Generated by Apple Swift version 4.2.1 effective-3.4.1 (swiftlang-1000.11.42 clang-1000.11.45.1)
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wgcc-compat"
+
+#if !defined(__has_include)
+# define __has_include(x) 0
+#endif
+#if !defined(__has_attribute)
+# define __has_attribute(x) 0
+#endif
+#if !defined(__has_feature)
+# define __has_feature(x) 0
+#endif
+#if !defined(__has_warning)
+# define __has_warning(x) 0
+#endif
+
+#if __has_include()
+# include
+#endif
+
+#pragma clang diagnostic ignored "-Wauto-import"
+#include
+#include
+#include
+#include
+
+#if !defined(SWIFT_TYPEDEFS)
+# define SWIFT_TYPEDEFS 1
+# if __has_include()
+# include
+# elif !defined(__cplusplus)
+typedef uint_least16_t char16_t;
+typedef uint_least32_t char32_t;
+# endif
+typedef float swift_float2 __attribute__((__ext_vector_type__(2)));
+typedef float swift_float3 __attribute__((__ext_vector_type__(3)));
+typedef float swift_float4 __attribute__((__ext_vector_type__(4)));
+typedef double swift_double2 __attribute__((__ext_vector_type__(2)));
+typedef double swift_double3 __attribute__((__ext_vector_type__(3)));
+typedef double swift_double4 __attribute__((__ext_vector_type__(4)));
+typedef int swift_int2 __attribute__((__ext_vector_type__(2)));
+typedef int swift_int3 __attribute__((__ext_vector_type__(3)));
+typedef int swift_int4 __attribute__((__ext_vector_type__(4)));
+typedef unsigned int swift_uint2 __attribute__((__ext_vector_type__(2)));
+typedef unsigned int swift_uint3 __attribute__((__ext_vector_type__(3)));
+typedef unsigned int swift_uint4 __attribute__((__ext_vector_type__(4)));
+#endif
+
+#if !defined(SWIFT_PASTE)
+# define SWIFT_PASTE_HELPER(x, y) x##y
+# define SWIFT_PASTE(x, y) SWIFT_PASTE_HELPER(x, y)
+#endif
+#if !defined(SWIFT_METATYPE)
+# define SWIFT_METATYPE(X) Class
+#endif
+#if !defined(SWIFT_CLASS_PROPERTY)
+# if __has_feature(objc_class_property)
+# define SWIFT_CLASS_PROPERTY(...) __VA_ARGS__
+# else
+# define SWIFT_CLASS_PROPERTY(...)
+# endif
+#endif
+
+#if __has_attribute(objc_runtime_name)
+# define SWIFT_RUNTIME_NAME(X) __attribute__((objc_runtime_name(X)))
+#else
+# define SWIFT_RUNTIME_NAME(X)
+#endif
+#if __has_attribute(swift_name)
+# define SWIFT_COMPILE_NAME(X) __attribute__((swift_name(X)))
+#else
+# define SWIFT_COMPILE_NAME(X)
+#endif
+#if __has_attribute(objc_method_family)
+# define SWIFT_METHOD_FAMILY(X) __attribute__((objc_method_family(X)))
+#else
+# define SWIFT_METHOD_FAMILY(X)
+#endif
+#if __has_attribute(noescape)
+# define SWIFT_NOESCAPE __attribute__((noescape))
+#else
+# define SWIFT_NOESCAPE
+#endif
+#if __has_attribute(warn_unused_result)
+# define SWIFT_WARN_UNUSED_RESULT __attribute__((warn_unused_result))
+#else
+# define SWIFT_WARN_UNUSED_RESULT
+#endif
+#if __has_attribute(noreturn)
+# define SWIFT_NORETURN __attribute__((noreturn))
+#else
+# define SWIFT_NORETURN
+#endif
+#if !defined(SWIFT_CLASS_EXTRA)
+# define SWIFT_CLASS_EXTRA
+#endif
+#if !defined(SWIFT_PROTOCOL_EXTRA)
+# define SWIFT_PROTOCOL_EXTRA
+#endif
+#if !defined(SWIFT_ENUM_EXTRA)
+# define SWIFT_ENUM_EXTRA
+#endif
+#if !defined(SWIFT_CLASS)
+# if __has_attribute(objc_subclassing_restricted)
+# define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_CLASS_EXTRA
+# define SWIFT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA
+# else
+# define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA
+# define SWIFT_CLASS_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA
+# endif
+#endif
+
+#if !defined(SWIFT_PROTOCOL)
+# define SWIFT_PROTOCOL(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA
+# define SWIFT_PROTOCOL_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA
+#endif
+
+#if !defined(SWIFT_EXTENSION)
+# define SWIFT_EXTENSION(M) SWIFT_PASTE(M##_Swift_, __LINE__)
+#endif
+
+#if !defined(OBJC_DESIGNATED_INITIALIZER)
+# if __has_attribute(objc_designated_initializer)
+# define OBJC_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer))
+# else
+# define OBJC_DESIGNATED_INITIALIZER
+# endif
+#endif
+#if !defined(SWIFT_ENUM_ATTR)
+# if defined(__has_attribute) && __has_attribute(enum_extensibility)
+# define SWIFT_ENUM_ATTR(_extensibility) __attribute__((enum_extensibility(_extensibility)))
+# else
+# define SWIFT_ENUM_ATTR(_extensibility)
+# endif
+#endif
+#if !defined(SWIFT_ENUM)
+# define SWIFT_ENUM(_type, _name, _extensibility) enum _name : _type _name; enum SWIFT_ENUM_ATTR(_extensibility) SWIFT_ENUM_EXTRA _name : _type
+# if __has_feature(generalized_swift_name)
+# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) enum _name : _type _name SWIFT_COMPILE_NAME(SWIFT_NAME); enum SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_ENUM_ATTR(_extensibility) SWIFT_ENUM_EXTRA _name : _type
+# else
+# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) SWIFT_ENUM(_type, _name, _extensibility)
+# endif
+#endif
+#if !defined(SWIFT_UNAVAILABLE)
+# define SWIFT_UNAVAILABLE __attribute__((unavailable))
+#endif
+#if !defined(SWIFT_UNAVAILABLE_MSG)
+# define SWIFT_UNAVAILABLE_MSG(msg) __attribute__((unavailable(msg)))
+#endif
+#if !defined(SWIFT_AVAILABILITY)
+# define SWIFT_AVAILABILITY(plat, ...) __attribute__((availability(plat, __VA_ARGS__)))
+#endif
+#if !defined(SWIFT_DEPRECATED)
+# define SWIFT_DEPRECATED __attribute__((deprecated))
+#endif
+#if !defined(SWIFT_DEPRECATED_MSG)
+# define SWIFT_DEPRECATED_MSG(...) __attribute__((deprecated(__VA_ARGS__)))
+#endif
+#if __has_feature(attribute_diagnose_if_objc)
+# define SWIFT_DEPRECATED_OBJC(Msg) __attribute__((diagnose_if(1, Msg, "warning")))
+#else
+# define SWIFT_DEPRECATED_OBJC(Msg) SWIFT_DEPRECATED_MSG(Msg)
+#endif
+#if __has_feature(modules)
+@import Foundation;
+@import ObjectiveC;
+#endif
+
+#pragma clang diagnostic ignored "-Wproperty-attribute-mismatch"
+#pragma clang diagnostic ignored "-Wduplicate-method-arg"
+#if __has_warning("-Wpragma-clang-attribute")
+# pragma clang diagnostic ignored "-Wpragma-clang-attribute"
+#endif
+#pragma clang diagnostic ignored "-Wunknown-pragmas"
+#pragma clang diagnostic ignored "-Wnullability"
+
+#if __has_attribute(external_source_symbol)
+# pragma push_macro("any")
+# undef any
+# pragma clang attribute push(__attribute__((external_source_symbol(language="Swift", defined_in="ionicCors",generated_declaration))), apply_to=any(function,enum,objc_interface,objc_category,objc_protocol))
+# pragma pop_macro("any")
+#endif
+
+@class GCDWebServer;
+@class NSXMLParser;
+
+SWIFT_CLASS("_TtC9ionicCors14CDVWKCorsProxy")
+@interface CDVWKCorsProxy : NSObject
+@property (nonatomic, strong) GCDWebServer * _Nonnull webserver;
+- (nonnull instancetype)initWithWebserver:(GCDWebServer * _Nonnull)webserver OBJC_DESIGNATED_INITIALIZER;
+- (void)setHandlersWithUrlPrefix:(NSString * _Nonnull)urlPrefix serverUrl:(NSString * _Nonnull)serverUrl;
+- (void)parser:(NSXMLParser * _Nonnull)parser didStartElement:(NSString * _Nonnull)elementName namespaceURI:(NSString * _Nullable)namespaceURI qualifiedName:(NSString * _Nullable)qName attributes:(NSDictionary * _Nonnull)attributeDict;
+- (nonnull instancetype)init SWIFT_UNAVAILABLE;
++ (nonnull instancetype)new SWIFT_DEPRECATED_MSG("-init is unavailable");
+@end
+
+#if __has_attribute(external_source_symbol)
+# pragma clang attribute pop
+#endif
+#pragma clang diagnostic pop
diff --git a/src/ios/CDVWKCorsProxy.swift b/src/ios/CDVWKCorsProxy.swift
new file mode 100644
index 00000000..2d598ce8
--- /dev/null
+++ b/src/ios/CDVWKCorsProxy.swift
@@ -0,0 +1,281 @@
+//
+// CDVWKCorsProxy.swift
+//
+//
+// Created by Raman Rasliuk on 27.11.18.
+//
+
+import Foundation
+
+@objc class CDVWKCorsProxy : NSObject, XMLParserDelegate {
+
+ var webserver: GCDWebServer
+
+ private var skipHeaders = ["content-encoding", "content-security-policy", "set-cookie"]
+
+ init(webserver: GCDWebServer) {
+ self.webserver = webserver
+
+ super.init()
+
+ self.getConfig();
+
+
+ }
+
+ func setHandlers(urlPrefix: String?, serverUrl: String?, sslCheck: String?, useCertificates: [String]?, clearCookies: String?, checkSSLChain: Bool?) {
+
+ if (urlPrefix == nil || serverUrl == nil) {
+ print("WK PROXY: ERROR SETTING PROXY. missing path or proxyUrl")
+
+ return
+ }
+
+ let pattern = "^" + NSRegularExpression.escapedPattern(for: urlPrefix!) + ".*"
+
+ let sslCheck = sslCheck ?? "default"
+
+ print("WK PROXY: Setting proxy path", urlPrefix!, "to address", serverUrl!, "with ssl check mode", sslCheck)
+
+ var sslTrust: URLSessionDelegate?
+
+ if (sslCheck == "nocheck") {
+ sslTrust = SSLTrustAny()
+ } else if (sslCheck == "pinned") {
+ sslTrust = SSLPinned(useCertificates, checkSSLChain)
+ }
+
+
+ if (clearCookies == "yes") {
+
+ let cookieStore = HTTPCookieStorage.shared
+
+ print("WK PROXY: Clearing cookies")
+
+ for cookie in cookieStore.cookies ?? [] {
+ cookieStore.deleteCookie(cookie)
+ }
+
+ }
+
+
+ for method in ["GET", "POST", "PUT", "PATCH", "DELETE"] {
+ webserver.addHandler(forMethod: method, pathRegex: pattern, request: GCDWebServerDataRequest.self, processBlock: { req in
+ return self.sendProxyResult(urlPrefix!, serverUrl!, req, sslTrust)
+ })
+ }
+
+ }
+
+ private func sendProxyResult(_ prefix: String, _ serverUrl: String, _ req: GCDWebServerRequest, _ sslTrust: URLSessionDelegate?) -> GCDWebServerResponse? {
+
+ let query = req.url.query == nil ? "" : "?" + req.url.query!
+ let url = URL(string: serverUrl + req.path.substring(from: prefix.endIndex) + query)
+
+ if (url == nil) {
+ return self.sendError(error: "Invalid url")
+ }
+
+ let request = NSMutableURLRequest(url: url!, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: 120)
+
+ request.httpMethod = req.method
+ request.allHTTPHeaderFields = req.headers as? [String: String]
+ request.allHTTPHeaderFields?["Host"] = url!.host
+
+ if (req.hasBody()) {
+ request.httpBody = (req as! GCDWebServerDataRequest).data
+ }
+
+ var finalResponse: GCDWebServerDataResponse? = nil
+ var session: URLSession!
+
+ if (sslTrust == nil) {
+ session = URLSession.shared
+ } else {
+ let configuration = URLSessionConfiguration.default
+ session = URLSession(configuration: configuration, delegate: sslTrust, delegateQueue: OperationQueue.main)
+ }
+
+
+ let task = session.dataTask(with: request as URLRequest) { data, urlResp, error in
+ if (error != nil) {
+ finalResponse = self.sendError(error: error?.localizedDescription) as? GCDWebServerDataResponse
+
+ return
+ }
+
+ let httpResponse = urlResp as! HTTPURLResponse
+
+ let resp = GCDWebServerDataResponse(data: data!, contentType: "application/x-unknown")
+
+ resp.statusCode = httpResponse.statusCode
+
+ for key in httpResponse.allHeaderFields {
+
+ let headerKey: String! = self.toString(v: key.0 as AnyObject)
+ let headerValue: String! = self.toString(v: key.1 as AnyObject)
+
+ let headerKeyLower = headerKey.lowercased()
+
+ if (headerKey == "" || self.skipHeaders.contains(headerKeyLower)) {
+ continue
+ }
+
+ resp.setValue(headerValue, forAdditionalHeader: headerKey)
+
+ }
+
+ resp.setValue(String(data!.count), forAdditionalHeader: "Content-Length")
+
+ finalResponse = resp
+
+ }
+
+ task.resume()
+
+ while (finalResponse == nil) {
+ Thread.sleep(forTimeInterval: 0.001)
+ }
+
+ return finalResponse
+
+ }
+
+ private func getConfig() {
+ if let path = Bundle.main.url(forResource: "config", withExtension: "xml") {
+ if let parser = XMLParser(contentsOf: path) {
+ parser.delegate = self
+ parser.parse()
+ }
+ }
+ }
+
+ private func sendError(error: String?) -> GCDWebServerResponse! {
+ let msg = error == nil ? "An error occured" : error!
+ let errorData = msg.data(using: String.Encoding.utf8, allowLossyConversion: true)
+ let resp = GCDWebServerDataResponse(data: errorData!, contentType: "text/plain")
+ resp.statusCode = 500
+
+ return resp
+ }
+
+ private func toString(v: AnyObject?) -> String! {
+ if (v == nil) { return ""; }
+ return String(stringInterpolationSegment: v!)
+ }
+
+
+ func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) {
+ if elementName == "wkproxy" {
+
+ let path = attributeDict["path"]
+ let proxyUrl = attributeDict["proxyUrl"]
+ let sslCheck = attributeDict["sslCheck"]
+ let useCertificates = attributeDict["useCertificates"]
+ let clearCookies = attributeDict["clearCookies"]
+ let checkSSLChain = attributeDict["sslCheckChain"] == "yes"
+
+ if (path != nil && proxyUrl != nil) {
+ self.setHandlers(urlPrefix: path!, serverUrl: proxyUrl!, sslCheck: sslCheck, useCertificates: useCertificates?.components(separatedBy: ","), clearCookies: clearCookies, checkSSLChain: checkSSLChain)
+ }
+
+ }
+ }
+
+}
+
+
+class SSLTrustAny : NSObject, URLSessionDelegate {
+
+ func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
+
+ if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) {
+ completionHandler(.useCredential, URLCredential(trust: challenge.protectionSpace.serverTrust!))
+
+ return
+ }
+
+ completionHandler(URLSession.AuthChallengeDisposition.cancelAuthenticationChallenge, nil)
+ }
+
+}
+
+class SSLPinned : NSObject, URLSessionDelegate {
+
+ private var certificates: [Data] = []
+ private var checkInCertChain = false
+
+ init(_ useCertificates: [String]?, _ checkInCertChain: Bool?) {
+ super.init()
+
+ if (checkInCertChain != nil) {
+ self.checkInCertChain = checkInCertChain!
+ }
+
+ let certsPath = URL(fileURLWithPath: Bundle.main.bundlePath + "/www/certificates", isDirectory: true)
+
+ let fileManager = FileManager.default
+
+ if let enumerator = fileManager.enumerator(atPath: certsPath.path) {
+ for file in enumerator {
+
+ let certFileName = file as! String
+
+ let fileUrl = URL(fileURLWithPath: certFileName, relativeTo: certsPath)
+
+ if (fileUrl.path.hasSuffix(".cer") && (useCertificates == nil || useCertificates!.contains(certFileName))) {
+ do {
+ let certData = try Data(contentsOf: fileUrl)
+ self.certificates.append(certData)
+ } catch {
+ print("WK PROXY: ERROR to load certificate", fileUrl.path)
+ }
+ }
+
+ }
+ }
+
+ }
+
+ func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Swift.Void) {
+
+ // Adapted from OWASP https://www.owasp.org/index.php/Certificate_and_Public_Key_Pinning#iOS
+
+ if (certificates.count > 0 && challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) {
+ if let serverTrust = challenge.protectionSpace.serverTrust {
+ var secresult = SecTrustResultType.invalid
+ let status = SecTrustEvaluate(serverTrust, &secresult)
+
+ let count = self.checkInCertChain ? SecTrustGetCertificateCount(serverTrust) : 1
+
+ if(errSecSuccess == status) {
+
+ for i in 0.. uiDelegate;
@property (nonatomic, weak) id weakScriptMessageHandler;
@property (nonatomic, strong) GCDWebServer *webServer;
+@property (nonatomic, strong) CDVWKCorsProxy *corsProxy;
@property (nonatomic, readwrite) CGRect frame;
@property (nonatomic, strong) NSString *userAgentCreds;
@property (nonatomic, assign) BOOL internalConnectionsOnly;
@@ -158,6 +160,8 @@ - (void)initWebServer
[self updateBindPath];
[self setServerPath:[self getStartPath]];
+ self.corsProxy = [[CDVWKCorsProxy alloc] initWithWebserver: self.webServer];
+
[self startServer];
}
diff --git a/src/ios/GCDWebServer-Bridging-Header.h b/src/ios/GCDWebServer-Bridging-Header.h
new file mode 100644
index 00000000..cb9b7806
--- /dev/null
+++ b/src/ios/GCDWebServer-Bridging-Header.h
@@ -0,0 +1,3 @@
+#import "GCDWebServer.h"
+#import "GCDWebServerDataResponse.h"
+#import "GCDWebServerDataRequest.h"