<commit_before>//
//  BRAPIClient.swift
//  BreadWallet
//
//  Created by <NAME> on 11/4/15.
//  Copyright (c) 2016 breadwallet LLC
//
//  Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
//
//  The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//  THE SOFTWARE.


import Foundation
import BRCore

let BRAPIClientErrorDomain = "BRApiClientErrorDomain"

// these flags map to api feature flag name values
// eg "buy-bitcoin-with-cash" is a persistent name in the /me/features list
@objc public enum BRFeatureFlags : Int, CustomStringConvertible {
    case buyBitcoin
    case earlyAccess
    
    public var description: String {
        switch self {
        case .buyBitcoin: return "buy-bitcoin";
        case .earlyAccess: return "early-access";
        }
    }
}

public enum BRAPIClientError: Error {
    case malformedDataError
    case unknownError
}

public typealias URLSessionTaskHandler = (Data?, HTTPURLResponse?, NSError?) -> Void
public typealias URLSessionChallengeHandler = (URLSession.AuthChallengeDisposition, URLCredential?) -> Void

// an object which implements BRAPIAdaptor can execute API Requests on the current wallet's behalf
public protocol BRAPIAdaptor {
    // execute an API request against the current wallet
    func dataTaskWithRequest(
        _ request: URLRequest, authenticated: Bool, retryCount: Int,
        handler: @escaping URLSessionTaskHandler
    ) -> URLSessionDataTask
    
    func url(_ path: String, args: Dictionary<String, String>?) -> URL
}

open class BRAPIClient : NSObject, URLSessionDelegate, URLSessionTaskDelegate, BRAPIAdaptor {
    private var authenticator: WalletAuthenticator
    
    // whether or not to emit log messages from this instance of the client
    private var logEnabled = true
    
    // proto is the transport protocol to use for talking to the API (either http or https)
    var proto = "https"
    
    // host is the server(s) on which the API is hosted
    #if Testflight || Debug
    var host = "stage.breadwallet.com"
    #else
    var host = "api.breadwallet.com"
    #endif
    
    // isFetchingAuth is set to true when a request is currently trying to renew authentication (the token)
    // it is useful because fetching auth is not idempotent and not reentrant, so at most one auth attempt
    // can take place at any one time
    private var isFetchingAuth = false
    
    // used when requests are waiting for authentication to be fetched
    private var authFetchGroup = DispatchGroup()

    // the NSURLSession on which all NSURLSessionTasks are executed
    lazy private var session: URLSession = URLSession(configuration: .default, delegate: self, delegateQueue: self.queue)

    // the queue on which the NSURLSession operates
    private var queue = OperationQueue()
    
    // convenience getter for the API endpoint
    private var baseUrl: String {
        return "\(proto)://\(host)"
    }
    
    init(authenticator: WalletAuthenticator) {
        self.authenticator = authenticator
    }
    
    // prints whatever you give it if logEnabled is true
    func log(_ s: String) {
        if !logEnabled {
            return
        }
        print("[BRAPIClient] \(s)")
    }
    
    var deviceId: String {
        return UserDefaults.standard.deviceID
    }
    
    var authKey: BRKey? {
        if authenticator.noWallet { return nil }
        guard let keyStr = authenticator.apiAuthKey else { return nil }
        var key = BRKey()
        key.compressed = 1 
        if BRKeySetPrivKey(&key, keyStr) == 0 {
            #if DEBUG
                fatalError("Unable to decode private key")
            #endif
        }
        return key
    }
    
    // MARK: Networking functions
    
    // Constructs a full NSURL for a given path and url parameters
    public func url(_ path: String, args: Dictionary<String, String>? =  nil) -> URL {
        func joinPath(_ k: String...) -> URL {
            return URL(string: ([baseUrl] + k).joined(separator: ""))!
        }
        
        if let args = args {
            return joinPath(path + "?" + args.map({
                "\($0.0.urlEscapedString)=\($0.1.urlEscapedString)"
            }).joined(separator: "&"))
        } else {
            return joinPath(path)
        }
    }
    
    private func signRequest(_ request: URLRequest) -> URLRequest {
        var mutableRequest = request
        let dateHeader = mutableRequest.allHTTPHeaderFields?.get(lowercasedKey: "date")
        if dateHeader == nil {
            // add Date header if necessary
            mutableRequest.setValue(Date().RFC1123String(), forHTTPHeaderField: "Date")
        }
        if let tokenData = authenticator.userAccount,
            let token = tokenData["token"] as? String,
            let authKey = authKey,
            let signingData = mutableRequest.signingString.data(using: .utf8) {
            let sig = signingData.sha256_2.compactSign(key: authKey)
            let hval = "bread \(token):\(sig.base58)"
            mutableRequest.setValue(hval, forHTTPHeaderField: "Authorization")
        }
        return mutableRequest
    }
    
    private func decorateRequest(_ request: URLRequest) -> URLRequest {
        var actualRequest = request
        actualRequest.setValue("\(E.isTestnet ? 1 : 0)", forHTTPHeaderField: "X-Bitcoin-Testnet")
        actualRequest.setValue("\(E.isTestFlight ? 1 : 0)", forHTTPHeaderField: "X-Testflight")
        return actualRequest
    }
    
    public func dataTaskWithRequest(_ request: URLRequest, authenticated: Bool = false,
                             retryCount: Int = 0, handler: @escaping URLSessionTaskHandler) -> URLSessionDataTask {
        let start = Date()<commit_msg>set testflight header if in debug mode<commit_after>//
//  BRAPIClient.swift
//  BreadWallet
//
//  Created by <NAME> on 11/4/15.
//  Copyright (c) 2016 breadwallet LLC
//
//  Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
//
//  The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//  THE SOFTWARE.


import Foundation
import BRCore

let BRAPIClientErrorDomain = "BRApiClientErrorDomain"

// these flags map to api feature flag name values
// eg "buy-bitcoin-with-cash" is a persistent name in the /me/features list
@objc public enum BRFeatureFlags : Int, CustomStringConvertible {
    case buyBitcoin
    case earlyAccess
    
    public var description: String {
        switch self {
        case .buyBitcoin: return "buy-bitcoin";
        case .earlyAccess: return "early-access";
        }
    }
}

public enum BRAPIClientError: Error {
    case malformedDataError
    case unknownError
}

public typealias URLSessionTaskHandler = (Data?, HTTPURLResponse?, NSError?) -> Void
public typealias URLSessionChallengeHandler = (URLSession.AuthChallengeDisposition, URLCredential?) -> Void

// an object which implements BRAPIAdaptor can execute API Requests on the current wallet's behalf
public protocol BRAPIAdaptor {
    // execute an API request against the current wallet
    func dataTaskWithRequest(
        _ request: URLRequest, authenticated: Bool, retryCount: Int,
        handler: @escaping URLSessionTaskHandler
    ) -> URLSessionDataTask
    
    func url(_ path: String, args: Dictionary<String, String>?) -> URL
}

open class BRAPIClient : NSObject, URLSessionDelegate, URLSessionTaskDelegate, BRAPIAdaptor {
    private var authenticator: WalletAuthenticator
    
    // whether or not to emit log messages from this instance of the client
    private var logEnabled = true
    
    // proto is the transport protocol to use for talking to the API (either http or https)
    var proto = "https"
    
    // host is the server(s) on which the API is hosted
    #if Testflight || Debug
    var host = "stage.breadwallet.com"
    #else
    var host = "api.breadwallet.com"
    #endif
    
    // isFetchingAuth is set to true when a request is currently trying to renew authentication (the token)
    // it is useful because fetching auth is not idempotent and not reentrant, so at most one auth attempt
    // can take place at any one time
    private var isFetchingAuth = false
    
    // used when requests are waiting for authentication to be fetched
    private var authFetchGroup = DispatchGroup()

    // the NSURLSession on which all NSURLSessionTasks are executed
    lazy private var session: URLSession = URLSession(configuration: .default, delegate: self, delegateQueue: self.queue)

    // the queue on which the NSURLSession operates
    private var queue = OperationQueue()
    
    // convenience getter for the API endpoint
    private var baseUrl: String {
        return "\(proto)://\(host)"
    }
    
    init(authenticator: WalletAuthenticator) {
        self.authenticator = authenticator
    }
    
    // prints whatever you give it if logEnabled is true
    func log(_ s: String) {
        if !logEnabled {
            return
        }
        print("[BRAPIClient] \(s)")
    }
    
    var deviceId: String {
        return UserDefaults.standard.deviceID
    }
    
    var authKey: BRKey? {
        if authenticator.noWallet { return nil }
        guard let keyStr = authenticator.apiAuthKey else { return nil }
        var key = BRKey()
        key.compressed = 1 
        if BRKeySetPrivKey(&key, keyStr) == 0 {
            #if DEBUG
                fatalError("Unable to decode private key")
            #endif
        }
        return key
    }
    
    // MARK: Networking functions
    
    // Constructs a full NSURL for a given path and url parameters
    public func url(_ path: String, args: Dictionary<String, String>? =  nil) -> URL {
        func joinPath(_ k: String...) -> URL {
            return URL(string: ([baseUrl] + k).joined(separator: ""))!
        }
        
        if let args = args {
            return joinPath(path + "?" + args.map({
                "\($0.0.urlEscapedString)=\($0.1.urlEscapedString)"
            }).joined(separator: "&"))
        } else {
            return joinPath(path)
        }
    }
    
    private func signRequest(_ request: URLRequest) -> URLRequest {
        var mutableRequest = request
        let dateHeader = mutableRequest.allHTTPHeaderFields?.get(lowercasedKey: "date")
        if dateHeader == nil {
            // add Date header if necessary
            mutableRequest.setValue(Date().RFC1123String(), forHTTPHeaderField: "Date")
        }
        if let tokenData = authenticator.userAccount,
            let token = tokenData["token"] as? String,
            let authKey = authKey,
            let signingData = mutableRequest.signingString.data(using: .utf8) {
            let sig = signingData.sha256_2.compactSign(key: authKey)
            let hval = "bread \(token):\(sig.base58)"
            mutableRequest.setValue(hval, forHTTPHeaderField: "Authorization")
        }
        return mutableRequest
    }
    
    private func decorateRequest(_ request: URLRequest) -> URLRequest {
        var actualRequest = request
        actualRequest.setValue("\(E.isTestnet ? 1 : 0)", forHTTPHeaderField: "X-Bitcoin-Testnet")
        actualRequest.setValue("\((E.isTestFlight || E.isDebug) ? 1 : 0)", forHTTPHeaderField: "X-Testflight")
        return actualRequest
    }
    
    public func dataTaskWithRequest(_ request: URLRequest, authenticated: Bool = false,
                             retryCount: Int = 0, handler: @escaping URLSessionTaskHandler) -> URLSessionDataTask {
        let start = Date()
{'fraction_non_alphanumeric': 0.08268248723340342, 'fraction_numerical': 0.004656052868729348, 'mean_word_length': 2.642505470459519, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 27, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, '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': '1203046', 'n_tokens_mistral': 3643, 'n_tokens_neox': 3569, 'n_words': 1320}