added iOS source code
[wl-app.git] / iOS / Pods / OAuthSwift / Sources / OAuthSwiftHTTPRequest.swift
diff --git a/iOS/Pods/OAuthSwift/Sources/OAuthSwiftHTTPRequest.swift b/iOS/Pods/OAuthSwift/Sources/OAuthSwiftHTTPRequest.swift
new file mode 100644 (file)
index 0000000..417563c
--- /dev/null
@@ -0,0 +1,497 @@
+//
+//  OAuthSwiftHTTPRequest.swift
+//  OAuthSwift
+//
+//  Created by Dongri Jin on 6/21/14.
+//  Copyright (c) 2014 Dongri Jin. All rights reserved.
+//
+
+import Foundation
+
+let kHTTPHeaderContentType = "Content-Type"
+
+open class OAuthSwiftHTTPRequest: NSObject, OAuthSwiftRequestHandle {
+
+    public typealias SuccessHandler = (_ response: OAuthSwiftResponse) -> Void
+    public typealias FailureHandler = (_ error: OAuthSwiftError) -> Void
+
+    /// HTTP request method
+    /// https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_methods
+    public enum Method: String {
+        case GET, POST, PUT, DELETE, PATCH, HEAD //, OPTIONS, TRACE, CONNECT
+
+        var isBody: Bool {
+            return self == .POST || self == .PUT || self == .PATCH
+        }
+    }
+
+    /// Where the additional parameters will be injected
+    @objc public enum ParamsLocation: Int {
+        case authorizationHeader, /*FormEncodedBody,*/ requestURIQuery
+    }
+
+    public var config: Config
+
+    private var request: URLRequest?
+    private var task: URLSessionTask?
+    private var session: URLSession!
+
+    fileprivate var cancelRequested = false
+
+    open static var executionContext: (@escaping () -> Void) -> Void = { block in
+        return DispatchQueue.main.async(execute: block)
+    }
+
+    // MARK: INIT
+
+    convenience init(url: URL, method: Method = .GET, parameters: OAuthSwift.Parameters = [:], paramsLocation: ParamsLocation = .authorizationHeader, httpBody: Data? = nil, headers: OAuthSwift.Headers = [:], sessionFactory: URLSessionFactory = .default) {
+        self.init(config: Config(url: url, httpMethod: method, httpBody: httpBody, headers: headers, parameters: parameters, paramsLocation: paramsLocation, sessionFactory: sessionFactory))
+    }
+
+    convenience init(request: URLRequest, paramsLocation: ParamsLocation = .authorizationHeader, sessionFactory: URLSessionFactory = .default) {
+        self.init(config: Config(urlRequest: request, paramsLocation: paramsLocation, sessionFactory: sessionFactory))
+    }
+
+    init(config: Config) {
+        self.config = config
+    }
+
+    /// START request
+    func start(success: SuccessHandler?, failure: FailureHandler?) {
+        guard request == nil else { return } // Don't start the same request twice!
+
+        let successHandler = success
+        let failureHandler = failure
+
+        do {
+            self.request = try self.makeRequest()
+        } catch let error as NSError {
+            failureHandler?(OAuthSwiftError.requestCreation(message: error.localizedDescription))
+            self.request = nil
+            return
+        }
+
+        OAuthSwiftHTTPRequest.executionContext {
+            // perform lock here to prevent cancel calls on another thread while creating the request
+            objc_sync_enter(self)
+            defer { objc_sync_exit(self) }
+            if self.cancelRequested {
+                return
+            }
+
+            self.session = self.config.sessionFactory.build()
+            let usedRequest = self.request!
+
+            if self.config.sessionFactory.useDataTaskClosure {
+                let completionHandler: (Data?, URLResponse?, Error?) -> Void = { data, resp, error in
+                    OAuthSwiftHTTPRequest.completionHandler(successHandler: success,
+                                                            failureHandler: failure,
+                                                            request: usedRequest,
+                                                            data: data,
+                                                            resp: resp,
+                                                            error: error)
+                }
+                self.task = self.session.dataTask(with: usedRequest, completionHandler: completionHandler)
+            } else {
+                self.task = self.session.dataTask(with: usedRequest)
+            }
+
+            self.task?.resume()
+            self.session.finishTasksAndInvalidate()
+
+            #if os(iOS)
+                #if !OAUTH_APP_EXTENSIONS
+                    UIApplication.shared.isNetworkActivityIndicatorVisible = self.config.sessionFactory.isNetworkActivityIndicatorVisible
+                #endif
+            #endif
+        }
+    }
+
+    /// Function called when receiving data from server.
+    public static func completionHandler(successHandler: SuccessHandler?, failureHandler: FailureHandler?, request: URLRequest, data: Data?, resp: URLResponse?, error: Error?) {
+        #if os(iOS)
+        #if !OAUTH_APP_EXTENSIONS
+        UIApplication.shared.isNetworkActivityIndicatorVisible = false
+        #endif
+        #endif
+
+        // MARK: failure error returned by server
+        if let error = error {
+            var oauthError: OAuthSwiftError = .requestError(error: error, request: request)
+            let nsError = error as NSError
+            if nsError.code == NSURLErrorCancelled {
+                oauthError = .cancelled
+            } else if nsError.isExpiredToken {
+                oauthError = .tokenExpired(error: error)
+            }
+
+            failureHandler?(oauthError)
+            return
+        }
+
+        // MARK: failure no response or data returned by server
+        guard let response = resp as? HTTPURLResponse, let responseData = data else {
+            let badRequestCode = 400
+            let localizedDescription = OAuthSwiftHTTPRequest.descriptionForHTTPStatus(badRequestCode, responseString: "")
+            var userInfo: [String: Any] = [
+                NSLocalizedDescriptionKey: localizedDescription
+            ]
+            if let response = resp { // there is only no data
+                userInfo[OAuthSwiftError.ResponseKey] = response
+            }
+            if let response = resp as? HTTPURLResponse {
+                userInfo["Response-Headers"] = response.allHeaderFields
+            }
+            let error = NSError(domain: OAuthSwiftError.Domain, code: badRequestCode, userInfo: userInfo)
+            failureHandler?(.requestError(error:error, request: request))
+            return
+        }
+
+        // MARK: failure code > 400
+        guard response.statusCode < 400 else {
+            var localizedDescription = String()
+            let responseString = String(data: responseData, encoding: OAuthSwiftDataEncoding)
+
+            // Try to get error information from data as json
+            let responseJSON = try? JSONSerialization.jsonObject(with: responseData, options: .mutableContainers)
+            if let responseJSON = responseJSON as? OAuthSwift.Parameters {
+                if let code = responseJSON["error"] as? String, let description = responseJSON["error_description"] as? String {
+
+                    localizedDescription = NSLocalizedString("\(code) \(description)", comment: "")
+                    if code == "authorization_pending" {
+                        failureHandler?(.authorizationPending)
+                        return
+                    }
+                }
+            } else {
+                localizedDescription = OAuthSwiftHTTPRequest.descriptionForHTTPStatus(response.statusCode, responseString: String(data: responseData, encoding: OAuthSwiftDataEncoding)!)
+            }
+
+            var userInfo: [String: Any] = [
+                NSLocalizedDescriptionKey: localizedDescription,
+                "Response-Headers": response.allHeaderFields,
+                OAuthSwiftError.ResponseKey: response,
+                OAuthSwiftError.ResponseDataKey: responseData
+            ]
+            if let string = responseString {
+                userInfo["Response-Body"] = string
+            }
+            if let urlString = response.url?.absoluteString {
+                userInfo[NSURLErrorFailingURLErrorKey] = urlString
+            }
+
+            let error = NSError(domain: NSURLErrorDomain, code: response.statusCode, userInfo: userInfo)
+            if error.isExpiredToken {
+                failureHandler?(.tokenExpired(error: error))
+            } else {
+                failureHandler?(.requestError(error: error, request: request))
+            }
+            return
+        }
+
+        // MARK: success
+        successHandler?(OAuthSwiftResponse(data: responseData, response: response, request: request))
+    }
+
+    open func cancel() {
+        // perform lock here to prevent cancel calls on another thread while creating the request
+        objc_sync_enter(self)
+        defer { objc_sync_exit(self) }
+        // either cancel the request if it's already running or set the flag to prohibit creation of the request
+        if let task = task {
+            task.cancel()
+        } else {
+            cancelRequested = true
+        }
+    }
+
+    open func makeRequest() throws -> URLRequest {
+        return try OAuthSwiftHTTPRequest.makeRequest(config: self.config)
+    }
+
+    open class func makeRequest(config: Config) throws -> URLRequest {
+        var request = config.urlRequest
+        return try setupRequestForOAuth(request: &request,
+                                        parameters: config.parameters,
+                                        dataEncoding: config.dataEncoding,
+                                        paramsLocation: config.paramsLocation
+        )
+    }
+
+    open class func makeRequest(
+        url: Foundation.URL,
+        method: Method,
+        headers: OAuthSwift.Headers,
+        parameters: OAuthSwift.Parameters,
+        dataEncoding: String.Encoding,
+        body: Data? = nil,
+        paramsLocation: ParamsLocation = .authorizationHeader) throws -> URLRequest {
+
+        var request = URLRequest(url: url)
+        request.httpMethod = method.rawValue
+        for (key, value) in headers {
+            request.setValue(value, forHTTPHeaderField: key)
+        }
+
+        return try setupRequestForOAuth(
+            request: &request,
+            parameters: parameters,
+            dataEncoding: dataEncoding,
+            body: body,
+            paramsLocation: paramsLocation
+        )
+    }
+
+    open class func setupRequestForOAuth(
+        request: inout URLRequest,
+        parameters: OAuthSwift.Parameters,
+        dataEncoding: String.Encoding = OAuthSwiftDataEncoding,
+        body: Data? = nil,
+        paramsLocation: ParamsLocation = .authorizationHeader) throws -> URLRequest {
+
+        let finalParameters: OAuthSwift.Parameters
+        switch paramsLocation {
+        case .authorizationHeader:
+            finalParameters = parameters.filter { key, _ in !key.hasPrefix("oauth_") }
+        case .requestURIQuery:
+            finalParameters = parameters
+        }
+
+        if let b = body {
+            request.httpBody = b
+        } else {
+            if !finalParameters.isEmpty {
+                let charset = dataEncoding.charset
+                let headers = request.allHTTPHeaderFields ?? [:]
+                if request.httpMethod == "GET" || request.httpMethod == "HEAD" || request.httpMethod == "DELETE" {
+                    let queryString = finalParameters.urlEncodedQuery
+                    let url = request.url!
+                    request.url = url.urlByAppending(queryString: queryString)
+                    if headers[kHTTPHeaderContentType] == nil {
+                        request.setValue("application/x-www-form-urlencoded; charset=\(charset)", forHTTPHeaderField: kHTTPHeaderContentType)
+                    }
+                } else {
+                    if let contentType = headers[kHTTPHeaderContentType], contentType.contains("application/json") {
+                        let jsonData = try JSONSerialization.data(withJSONObject: finalParameters, options: [])
+                        request.setValue("application/json; charset=\(charset)", forHTTPHeaderField: kHTTPHeaderContentType)
+                        request.httpBody = jsonData
+                    } else if let contentType = headers[kHTTPHeaderContentType], contentType.contains("multipart/form-data") {
+                    // snip
+                    } else {
+                        request.setValue("application/x-www-form-urlencoded; charset=\(charset)", forHTTPHeaderField: kHTTPHeaderContentType)
+                        let queryString = finalParameters.urlEncodedQuery
+                        request.httpBody = queryString.data(using: dataEncoding, allowLossyConversion: true)
+                    }
+                }
+            }
+        }
+        return request
+    }
+
+}
+
+// MARK: - Request configuraiton
+extension OAuthSwiftHTTPRequest {
+
+    /// Configuration for request
+    public struct Config {
+
+        /// URLRequest (url, method, ...)
+        public var urlRequest: URLRequest
+        /// These parameters are either added to the query string for GET, HEAD and DELETE requests or
+        /// used as the http body in case of POST, PUT or PATCH requests.
+        ///
+        /// If used in the body they are either encoded as JSON or as encoded plaintext based on the Content-Type header field.
+        public var parameters: OAuthSwift.Parameters
+        public let paramsLocation: ParamsLocation
+        public let dataEncoding: String.Encoding
+        public let sessionFactory: URLSessionFactory
+
+        /// Shortcut
+        public var httpMethod: Method {
+            if let requestMethod = urlRequest.httpMethod {
+                return Method(rawValue: requestMethod) ?? .GET
+            }
+            return .GET
+        }
+
+        public var url: Foundation.URL? {
+            return urlRequest.url
+        }
+
+        // MARK: init
+        public init(url: URL, httpMethod: Method = .GET, httpBody: Data? = nil, headers: OAuthSwift.Headers = [:], timeoutInterval: TimeInterval = 60, httpShouldHandleCookies: Bool = false, parameters: OAuthSwift.Parameters, paramsLocation: ParamsLocation = .authorizationHeader, dataEncoding: String.Encoding = OAuthSwiftDataEncoding, sessionFactory: URLSessionFactory = .default) {
+            var urlRequest = URLRequest(url: url)
+            urlRequest.httpMethod = httpMethod.rawValue
+            urlRequest.httpBody = httpBody
+            urlRequest.allHTTPHeaderFields = headers
+            urlRequest.timeoutInterval = timeoutInterval
+            urlRequest.httpShouldHandleCookies = httpShouldHandleCookies
+            self.init(urlRequest: urlRequest, parameters: parameters, paramsLocation: paramsLocation, dataEncoding: dataEncoding, sessionFactory: sessionFactory)
+        }
+
+        public init(urlRequest: URLRequest, parameters: OAuthSwift.Parameters = [:], paramsLocation: ParamsLocation = .authorizationHeader, dataEncoding: String.Encoding = OAuthSwiftDataEncoding, sessionFactory: URLSessionFactory = .default) {
+            self.urlRequest = urlRequest
+            self.parameters = parameters
+            self.paramsLocation = paramsLocation
+            self.dataEncoding = dataEncoding
+            self.sessionFactory = sessionFactory
+        }
+
+        /// Modify request with authentification
+        public mutating func updateRequest(credential: OAuthSwiftCredential) {
+            let method = self.httpMethod
+            let url = self.urlRequest.url!
+            let headers: OAuthSwift.Headers = self.urlRequest.allHTTPHeaderFields ?? [:]
+            let paramsLocation = self.paramsLocation
+            let parameters = self.parameters
+
+            var signatureUrl = url
+            var signatureParameters = parameters
+
+            // Check if body must be hashed (oauth1)
+            let body: Data? = nil
+            if method.isBody {
+                if let contentType = headers[kHTTPHeaderContentType]?.lowercased() {
+
+                    if contentType.contains("application/json") {
+                        // TODO: oauth_body_hash create body before signing if implementing body hashing
+                        /*do {
+                         let jsonData: Data = try JSONSerialization.jsonObject(parameters, options: [])
+                         request.HTTPBody = jsonData
+                         requestHeaders["Content-Length"] = "\(jsonData.length)"
+                         body = jsonData
+                         }
+                         catch {
+                         }*/
+
+                        signatureParameters = [:] // parameters are not used for general signature (could only be used for body hashing
+                    }
+                    // else other type are not supported, see setupRequestForOAuth()
+                }
+            }
+
+            // Need to account for the fact that some consumers will have additional parameters on the
+            // querystring, including in the case of fetching a request token. Especially in the case of
+            // additional parameters on the request, authorize, or access token exchanges, we need to
+            // normalize the URL and add to the parametes collection.
+
+            var queryStringParameters = OAuthSwift.Parameters()
+            var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false )
+            if let queryItems = urlComponents?.queryItems {
+                for queryItem in queryItems {
+                    let value = queryItem.value?.safeStringByRemovingPercentEncoding ?? ""
+                    queryStringParameters.updateValue(value, forKey: queryItem.name)
+                }
+            }
+
+            // According to the OAuth1.0a spec, the url used for signing is ONLY scheme, path, and query
+            if !queryStringParameters.isEmpty {
+                urlComponents?.query = nil
+                // This is safe to unwrap because these just came from an NSURL
+                signatureUrl = urlComponents?.url ?? url
+            }
+            signatureParameters = signatureParameters.join(queryStringParameters)
+
+            var requestHeaders = OAuthSwift.Headers()
+            switch paramsLocation {
+            case .authorizationHeader:
+                //Add oauth parameters in the Authorization header
+                requestHeaders += credential.makeHeaders(signatureUrl, method: method, parameters: signatureParameters, body: body)
+            case .requestURIQuery:
+                //Add oauth parameters as request parameters
+                self.parameters += credential.authorizationParametersWithSignature(method: method, url: signatureUrl, parameters: signatureParameters, body: body)
+            }
+
+            self.urlRequest.allHTTPHeaderFields = requestHeaders + headers
+        }
+
+    }
+}
+
+// MARK: - session configuration
+
+/// configure how URLSession is initialized
+public struct URLSessionFactory {
+
+    public static let `default` = URLSessionFactory()
+
+    public var configuration = URLSessionConfiguration.default
+    public var queue = OperationQueue.main
+    /// An optional delegate for the URLSession
+    public weak var delegate: URLSessionDelegate?
+
+    /// Monitor session: see UIApplication.shared.isNetworkActivityIndicatorVisible
+    public var isNetworkActivityIndicatorVisible = true
+
+    /// By default use a closure to receive data from server.
+    /// If you set to false, you must in `delegate` take care of server response.
+    /// and maybe call in delegate `OAuthSwiftHTTPRequest.completionHandler`
+    public var useDataTaskClosure = true
+
+    /// Create a new URLSession
+    func build() -> URLSession {
+        return URLSession(configuration: self.configuration, delegate: self.delegate, delegateQueue: self.queue)
+    }
+}
+
+// MARK: - status code mapping
+
+extension OAuthSwiftHTTPRequest {
+
+    class func descriptionForHTTPStatus(_ status: Int, responseString: String) -> String {
+
+        var s = "HTTP Status \(status)"
+
+        var description: String?
+        // http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
+        if status == 400 { description = "Bad Request" }
+        if status == 401 { description = "Unauthorized" }
+        if status == 402 { description = "Payment Required" }
+        if status == 403 { description = "Forbidden" }
+        if status == 404 { description = "Not Found" }
+        if status == 405 { description = "Method Not Allowed" }
+        if status == 406 { description = "Not Acceptable" }
+        if status == 407 { description = "Proxy Authentication Required" }
+        if status == 408 { description = "Request Timeout" }
+        if status == 409 { description = "Conflict" }
+        if status == 410 { description = "Gone" }
+        if status == 411 { description = "Length Required" }
+        if status == 412 { description = "Precondition Failed" }
+        if status == 413 { description = "Payload Too Large" }
+        if status == 414 { description = "URI Too Long" }
+        if status == 415 { description = "Unsupported Media Type" }
+        if status == 416 { description = "Requested Range Not Satisfiable" }
+        if status == 417 { description = "Expectation Failed" }
+        if status == 422 { description = "Unprocessable Entity" }
+        if status == 423 { description = "Locked" }
+        if status == 424 { description = "Failed Dependency" }
+        if status == 425 { description = "Unassigned" }
+        if status == 426 { description = "Upgrade Required" }
+        if status == 427 { description = "Unassigned" }
+        if status == 428 { description = "Precondition Required" }
+        if status == 429 { description = "Too Many Requests" }
+        if status == 430 { description = "Unassigned" }
+        if status == 431 { description = "Request Header Fields Too Large" }
+        if status == 432 { description = "Unassigned" }
+        if status == 500 { description = "Internal Server Error" }
+        if status == 501 { description = "Not Implemented" }
+        if status == 502 { description = "Bad Gateway" }
+        if status == 503 { description = "Service Unavailable" }
+        if status == 504 { description = "Gateway Timeout" }
+        if status == 505 { description = "HTTP Version Not Supported" }
+        if status == 506 { description = "Variant Also Negotiates" }
+        if status == 507 { description = "Insufficient Storage" }
+        if status == 508 { description = "Loop Detected" }
+        if status == 509 { description = "Unassigned" }
+        if status == 510 { description = "Not Extended" }
+        if status == 511 { description = "Network Authentication Required" }
+
+        if description != nil {
+            s += ": " + description! + ", Response: " + responseString
+        }
+
+        return s
+    }
+
+}