<commit_before>//
//  HttpHelper.swift
//  Pods
//
//  Created by <NAME> on 05/06/17.
//
//

import UIKit

public typealias HttpCompletionHandler = (_ data: Data?, _ error: Error?) -> Void

public class HttpHelper: NSObject {
    
    // MARK: Variables
    
    static public var builder   : HttpHelper.Builder {
        return HttpHelper.Builder()
    }
    
    //Http Properties
    
    fileprivate var contentType : String!
    fileprivate var timeout     : TimeInterval!
    fileprivate var encoding    : String.Encoding!
    
    fileprivate var header      : [String: String]!
    fileprivate var parameters  : [String: Any]!
    
    //Basic Authorization
    
    fileprivate var username    : String?
    fileprivate var password    : String?
    
    //Certificates
    
    fileprivate var certificateMode     : CertificateMode = .none
    
    fileprivate var certificate         : Data?
    fileprivate var certificatePassword : String?
    
    fileprivate var trustAllSSL         : Bool = false
    
    fileprivate var hostDomain          : String?
    
    //Request Type
    
//    fileprivate var synchronous : Bool = false
    
    //Request
    
    fileprivate var url : String?
    
    //Response
    
    fileprivate var completionHandler   : HttpCompletionHandler?
    
    // MARK: Inits
    
    fileprivate override init() {
        super.init()
        
        contentType = "application/json"
        timeout = 60
        encoding = .utf8
        
        header = [:]
        parameters = [:]
    }
    
    // MARK: Conversions
    
    private func convertBasicAuthToBase64(username: String, password: String) throws -> String {
        let credentials = "\(username):\(password)"
        
        guard let data = credentials.data(using: encoding) else {
            throw MochaException.domainException(message: "Error formatting the basic authentication provided.")
        }
        
        let base64Credential = data.base64EncodedString(options: .lineLength64Characters)
        let authValue = "Basic \(base64Credential)"
        return authValue
    }
    
    private func string(fromDictionary dictionary: [String: Any]) -> String {
        var resource = ""
        
        for (key, value) in dictionary {
            resource = resource.isEmpty ? "" : resource + "&"
            resource += key + "=" + "\(value)"
        }
        
        return resource
    }
    
    // MARK: Requests
    
    public func get() {
        send(httpMethod: "get")
    }
    
    public func delete() {
        send(httpMethod: "delete")
    }
    
    public func post() {
        send(httpMethod: "post")
    }
    
    public func update() {
        send(httpMethod: "update")
    }
    
    private func handleDomainException(_ message: String) {
        completionHandler?(nil, MochaException.domainException(message: message))
    }
    
    private func send(httpMethod: String) {
        
        guard let url = self.url else {
            handleDomainException("URL cannot be `nil`")
            return
        }
        
        guard let nsurl = URL(string: url) else {
            handleDomainException("Invalid URL.")
            return
        }
        
        var request = URLRequest(url: nsurl, cachePolicy: .useProtocolCachePolicy)
        request.httpMethod = httpMethod
        
        if let username = self.username, username.isNotEmpty, let password = <PASSWORD>, <PASSWORD> {
            do {
                let encodedBasicAuth = try convertBasicAuthToBase64(username: username, password: password)
                request.setValue(encodedBasicAuth, forHTTPHeaderField: "Authorization")
            } catch MochaException.domainException(let message) {
                MochaLogger.log(message)
            } catch {}
        }
        
        if header.count > 0 {
            for (key, value) in header {
                request.setValue(value, forHTTPHeaderField: key)
            }
        }
        
        if httpMethod.equalsIgnoreCase("post") || httpMethod.equalsIgnoreCase("update") {
            if parameters.isEmpty {
                handleDomainException("Http request (\(httpMethod.lowercased()) without parameters.")
                return
            }
            
            if request.value(forHTTPHeaderField: "Content-Type") == nil {
                request.setValue(contentType, forHTTPHeaderField: "Content-Type")
            }
            
            if contentType == "application/x-www-form-urlencoded" {
                let formString = string(fromDictionary: parameters)
                let length = "\(formString.length)"
                
                request.setValue(length, forHTTPHeaderField: "Content-Length")
                request.httpBody = formString.data(using: encoding)
            } else {
                do {
                    let data = try JSONSerialization.data(withJSONObject: parameters, options: [])
                    request.httpBody = data
                } catch {
                    handleDomainException("Error formatting the data to be sent.")
                }
            }
        }
        
        let configuration = URLSessionConfiguration.default
        configuration.timeoutIntervalForRequest = timeout
        
        let session = URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
        
        let dataTask = session.dataTask(with: request, completionHandler: {
            (data: Data?, response: URLResponse?, error: Error?) in
            
            if let httpResponse = response as? HTTPURLResponse {
                if httpResponse.statusCode == -1022 {
                    self.completionHandler?(nil, MochaException.appSecurityTransportException)
                } else if httpResponse.statusCode != 200 {
                    self.completionHandler?(nil, error)
//                    throw Exception.ioException
                }
            }
            
            if let error = error {
                MochaLogger.log("Http error: \(error.localizedDescription)")
                self.completionHandler?(nil, error)
            }
            
            if let data = data {
                self.completionHandler?(data, nil)
            }
        })
        
        dataTask.resume()
        
        session.finishTasksAndInvalidate()
    }
    
    // MARK: Certificate Handlers
    
    fileprivate func shoultTrustProtectionSpace(_ protectionSpace: URLProtectionSpace) -> Bool {
        
        guard let certificate = certificate else {
            return false
        }
        
        guard let serverTrust = protectionSpace.serverTrust else {
            return false
        }
        
        let cfCertificate = certificate as CFData
        let secCertificate = SecCertificateCreateWithData(kCFAllocatorDefault, cfCertificate)
        
        var certRawPointer : UnsafeRawPointer? = UnsafeRawPointer([secCertificate])
        
        guard let cfArray = CFArrayCreate(kCFAllocatorDefault, &certRawPointer, 1, nil) else {
            return false
        }
        
        SecTrustSetAnchorCertificates(serverTrust, cfArray)
        
        var trustResult: SecTrustResultType = .invalid
        SecTrustEvaluate(serverTrust, &trustResult)
        
        if trustResult == .recoverableTrustFailure {
            let errDataRef = SecTrustCopyExceptions(serverTrust)
            SecTrustSetExceptions(serverTrust, errDataRef)
            
            SecTrustEvaluate(serverTrust, &trustResult)
        }
        
        return trustResult == .unspecified || trustResult == .proceed
    }
    
    fileprivate func loadCredential() -> URLCredential? {
        
        guard let certificate = certificate else {
            return nil
        }
        
        guard let password = certificatePassword else {
            return nil
        }
        
        let keys    : [CFString] = [kSecImportExportPassphrase]
        let values  : [String] = [password]
        
        var keysPointer : UnsafeRawPointer? = UnsafeRawPointer(keys)
        var valuesPointer : UnsafeRawPointer? = UnsafeRawPointer(values)
        
        guard let optionsDictionary = CFDictionaryCreate(kCFAllocatorDefault, &keysPointer, &valuesPointer, 1, nil, nil) else {
            return nil
        }
        
        var p12Items : CFArray?
        
        let result = SecPKCS12Import(certificate as CFData, optionsDictionary, &p12Items)
        
        guard result == noErr else {
            MochaLogger.log("Http error: Invalid certificate or password.")
            return nil
        }
        
        let identityDict = CFArrayGetValueAtIndex(p12Items, 0) as! CFDictionary
        let identityApp = CFDictionaryGetValue(identityDict, Unmanaged.passRetained(kSecImportItemIdentity).toOpaque()) as! SecIdentity
        
        var certRef : SecCertificate?
        
        SecIdentityCopyCertificate(identityApp, &certRef)
        
        guard let newCertRef = certRef else {
            MochaLogger.log("Http error: Invalid certificate or password.")
            return nil
        }
        
        let certArray : [SecCertificate] = [newCertRef]
        var certArrayPointer : UnsafeRawPointer? = UnsafeRawPointer(certArray)
        
        CFArrayCreate(kCFAllocatorDefault, &certArrayPointer, 1, nil)
        
        let credential = URLCredential(identity: identityApp, certificates: nil, persistence: .none)
        return credential
    }
}

// MARK: - Enums

public extension HttpHelper {
    
    public enum CertificateMode {
        case none
        case publicKey
    }
}

// MARK: - Builder

public extension HttpHelper {
    
    public class Builder {
        
        private var helper : HttpHelper
        
        internal init() {
            helper = HttpHelper()
        }
        
        public func url(_ url: String) -> Builder {
            helper.url = url
            return self
        }
        
        public func completionHandler(_ handler: @escaping HttpCompletionHandler) -> Builder {
            helper.completionHandler = handler
            return self
        }
        
//        public func synchronous(_ synchronous: Bool) -> Builder {
//            helper.synchronous = synchronous
//            return self
//        }
        
        public func parameters(_ parameters: [String: Any]) -> Builder {
            helper.parameters = parameters
            return self<commit_msg>Change access control of initializer<commit_after>//
//  HttpHelper.swift
//  Pods
//
//  Created by <NAME> on 05/06/17.
//
//

import UIKit

public typealias HttpCompletionHandler = (_ data: Data?, _ error: Error?) -> Void

public class HttpHelper: NSObject {
    
    // MARK: Variables
    
    static public var builder   : HttpHelper.Builder {
        return HttpHelper.Builder()
    }
    
    //Http Properties
    
    fileprivate var contentType : String!
    fileprivate var timeout     : TimeInterval!
    fileprivate var encoding    : String.Encoding!
    
    fileprivate var header      : [String: String]!
    fileprivate var parameters  : [String: Any]!
    
    //Basic Authorization
    
    fileprivate var username    : String?
    fileprivate var password    : String?
    
    //Certificates
    
    fileprivate var certificateMode     : CertificateMode = .none
    
    fileprivate var certificate         : Data?
    fileprivate var certificatePassword : String?
    
    fileprivate var trustAllSSL         : Bool = false
    
    fileprivate var hostDomain          : String?
    
    //Request Type
    
//    fileprivate var synchronous : Bool = false
    
    //Request
    
    fileprivate var url : String?
    
    //Response
    
    fileprivate var completionHandler   : HttpCompletionHandler?
    
    // MARK: Inits
    
    fileprivate override init() {
        super.init()
        
        contentType = "application/json"
        timeout = 60
        encoding = .utf8
        
        header = [:]
        parameters = [:]
    }
    
    // MARK: Conversions
    
    private func convertBasicAuthToBase64(username: String, password: String) throws -> String {
        let credentials = "\(username):\(password)"
        
        guard let data = credentials.data(using: encoding) else {
            throw MochaException.domainException(message: "Error formatting the basic authentication provided.")
        }
        
        let base64Credential = data.base64EncodedString(options: .lineLength64Characters)
        let authValue = "Basic \(base64Credential)"
        return authValue
    }
    
    private func string(fromDictionary dictionary: [String: Any]) -> String {
        var resource = ""
        
        for (key, value) in dictionary {
            resource = resource.isEmpty ? "" : resource + "&"
            resource += key + "=" + "\(value)"
        }
        
        return resource
    }
    
    // MARK: Requests
    
    public func get() {
        send(httpMethod: "get")
    }
    
    public func delete() {
        send(httpMethod: "delete")
    }
    
    public func post() {
        send(httpMethod: "post")
    }
    
    public func update() {
        send(httpMethod: "update")
    }
    
    private func handleDomainException(_ message: String) {
        completionHandler?(nil, MochaException.domainException(message: message))
    }
    
    private func send(httpMethod: String) {
        
        guard let url = self.url else {
            handleDomainException("URL cannot be `nil`")
            return
        }
        
        guard let nsurl = URL(string: url) else {
            handleDomainException("Invalid URL.")
            return
        }
        
        var request = URLRequest(url: nsurl, cachePolicy: .useProtocolCachePolicy)
        request.httpMethod = httpMethod
        
        if let username = self.username, username.isNotEmpty, let password = <PASSWORD>, <PASSWORD> {
            do {
                let encodedBasicAuth = try convertBasicAuthToBase64(username: username, password: password)
                request.setValue(encodedBasicAuth, forHTTPHeaderField: "Authorization")
            } catch MochaException.domainException(let message) {
                MochaLogger.log(message)
            } catch {}
        }
        
        if header.count > 0 {
            for (key, value) in header {
                request.setValue(value, forHTTPHeaderField: key)
            }
        }
        
        if httpMethod.equalsIgnoreCase("post") || httpMethod.equalsIgnoreCase("update") {
            if parameters.isEmpty {
                handleDomainException("Http request (\(httpMethod.lowercased()) without parameters.")
                return
            }
            
            if request.value(forHTTPHeaderField: "Content-Type") == nil {
                request.setValue(contentType, forHTTPHeaderField: "Content-Type")
            }
            
            if contentType == "application/x-www-form-urlencoded" {
                let formString = string(fromDictionary: parameters)
                let length = "\(formString.length)"
                
                request.setValue(length, forHTTPHeaderField: "Content-Length")
                request.httpBody = formString.data(using: encoding)
            } else {
                do {
                    let data = try JSONSerialization.data(withJSONObject: parameters, options: [])
                    request.httpBody = data
                } catch {
                    handleDomainException("Error formatting the data to be sent.")
                }
            }
        }
        
        let configuration = URLSessionConfiguration.default
        configuration.timeoutIntervalForRequest = timeout
        
        let session = URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
        
        let dataTask = session.dataTask(with: request, completionHandler: {
            (data: Data?, response: URLResponse?, error: Error?) in
            
            if let httpResponse = response as? HTTPURLResponse {
                if httpResponse.statusCode == -1022 {
                    self.completionHandler?(nil, MochaException.appSecurityTransportException)
                } else if httpResponse.statusCode != 200 {
                    self.completionHandler?(nil, error)
//                    throw Exception.ioException
                }
            }
            
            if let error = error {
                MochaLogger.log("Http error: \(error.localizedDescription)")
                self.completionHandler?(nil, error)
            }
            
            if let data = data {
                self.completionHandler?(data, nil)
            }
        })
        
        dataTask.resume()
        
        session.finishTasksAndInvalidate()
    }
    
    // MARK: Certificate Handlers
    
    fileprivate func shoultTrustProtectionSpace(_ protectionSpace: URLProtectionSpace) -> Bool {
        
        guard let certificate = certificate else {
            return false
        }
        
        guard let serverTrust = protectionSpace.serverTrust else {
            return false
        }
        
        let cfCertificate = certificate as CFData
        let secCertificate = SecCertificateCreateWithData(kCFAllocatorDefault, cfCertificate)
        
        var certRawPointer : UnsafeRawPointer? = UnsafeRawPointer([secCertificate])
        
        guard let cfArray = CFArrayCreate(kCFAllocatorDefault, &certRawPointer, 1, nil) else {
            return false
        }
        
        SecTrustSetAnchorCertificates(serverTrust, cfArray)
        
        var trustResult: SecTrustResultType = .invalid
        SecTrustEvaluate(serverTrust, &trustResult)
        
        if trustResult == .recoverableTrustFailure {
            let errDataRef = SecTrustCopyExceptions(serverTrust)
            SecTrustSetExceptions(serverTrust, errDataRef)
            
            SecTrustEvaluate(serverTrust, &trustResult)
        }
        
        return trustResult == .unspecified || trustResult == .proceed
    }
    
    fileprivate func loadCredential() -> URLCredential? {
        
        guard let certificate = certificate else {
            return nil
        }
        
        guard let password = certificatePassword else {
            return nil
        }
        
        let keys    : [CFString] = [kSecImportExportPassphrase]
        let values  : [String] = [password]
        
        var keysPointer : UnsafeRawPointer? = UnsafeRawPointer(keys)
        var valuesPointer : UnsafeRawPointer? = UnsafeRawPointer(values)
        
        guard let optionsDictionary = CFDictionaryCreate(kCFAllocatorDefault, &keysPointer, &valuesPointer, 1, nil, nil) else {
            return nil
        }
        
        var p12Items : CFArray?
        
        let result = SecPKCS12Import(certificate as CFData, optionsDictionary, &p12Items)
        
        guard result == noErr else {
            MochaLogger.log("Http error: Invalid certificate or password.")
            return nil
        }
        
        let identityDict = CFArrayGetValueAtIndex(p12Items, 0) as! CFDictionary
        let identityApp = CFDictionaryGetValue(identityDict, Unmanaged.passRetained(kSecImportItemIdentity).toOpaque()) as! SecIdentity
        
        var certRef : SecCertificate?
        
        SecIdentityCopyCertificate(identityApp, &certRef)
        
        guard let newCertRef = certRef else {
            MochaLogger.log("Http error: Invalid certificate or password.")
            return nil
        }
        
        let certArray : [SecCertificate] = [newCertRef]
        var certArrayPointer : UnsafeRawPointer? = UnsafeRawPointer(certArray)
        
        CFArrayCreate(kCFAllocatorDefault, &certArrayPointer, 1, nil)
        
        let credential = URLCredential(identity: identityApp, certificates: nil, persistence: .none)
        return credential
    }
}

// MARK: - Enums

public extension HttpHelper {
    
    public enum CertificateMode {
        case none
        case publicKey
    }
}

// MARK: - Builder

public extension HttpHelper {
    
    public class Builder {
        
        private var helper : HttpHelper
        
        fileprivate init() {
            helper = HttpHelper()
        }
        
        public func url(_ url: String) -> Builder {
            helper.url = url
            return self
        }
        
        public func completionHandler(_ handler: @escaping HttpCompletionHandler) -> Builder {
            helper.completionHandler = handler
            return self
        }
        
//        public func synchronous(_ synchronous: Bool) -> Builder {
//            helper.synchronous = synchronous
//            return self
//        }
        
        public func parameters(_ parameters: [String: Any]) -> Builder {
            helper.parameters = parameters
            return self
{'fraction_non_alphanumeric': 0.07755570431290044, 'fraction_numerical': 0.003920818590417902, 'mean_word_length': 1.7720344599072233, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 29, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1213354', 'n_tokens_mistral': 5273, 'n_tokens_neox': 5066, 'n_words': 1299}