Ahmed Zaidan
Ahmed Zaidan

Reputation: 108

Detect encoding (Br, Gzip, Deflate) from NW Connection request

I am using a NWConnection to make a request using an http proxy that requires authorization. The only way I found to make the request with this proxy is with an NWConnection. The request also specifies that it should accept encodings of types: Br, Deflate, and Gzip. I am using 3 different packages to decode each of the three type. How do I know which type the response is encoded with so I can pick the correct decoding type?

NWConnection returns the response headers with the body as Data. Im assuming the encoding type is in those headers, but I am unsure how to separate the headers from the request body.

Function to decode

import Foundation
import Gzip
import SwiftyDeflate
import SwiftBrotli

func decodeRequest(data: Data, encodingType: String?) -> String? {
    var encoding = ""
    
    if let encodingType {
        encoding = encodingType
    }

    switch encoding.lowercased() {
    case "gzip":
        do {
            let decompressedData = try data.gunzipped()
            return String(data: decompressedData, encoding: .utf8) ?? ""
        } catch {
            return nil
        }
    case "deflate":
        do {
            let decompressedData = try data.deflateDecompress()
            return String(data: decompressedData, encoding: .utf8) ?? ""
        } catch {
            return nil
        }
    case "br", "brotli":
        let brotli = Brotli()
        
        switch brotli.decompress(data) {
        case .success(let decompressedData):
            return String(data: decompressedData, encoding: .utf8) ?? ""
        case .failure:
            return nil
        }
    default:
        return String(data: data, encoding: .utf8) ?? ""
    }
}

Request

    func updateOrderStatusProxy(proxy: String, completion: @escaping (Bool) -> Void) {
        let orderLink = "some link"

        guard let url = URL(string: orderLink) else {
            completion(true)
            return
        }

        let proxyDetails = proxy.split(separator: ":").map(String.init)
        guard proxyDetails.count == 4, let port = UInt16(proxyDetails[1]) else {
            completion(false)
            print("Invalid proxy format")
            return
        }

        let proxyEndpoint = NWEndpoint.hostPort(host: .init(proxyDetails[0]),
                                                port: NWEndpoint.Port(integerLiteral: port))
        let proxyConfig = ProxyConfiguration(httpCONNECTProxy: proxyEndpoint, tlsOptions: nil)
        proxyConfig.applyCredential(username: proxyDetails[2], password: proxyDetails[3])

        let parameters = NWParameters.tls
        let privacyContext = NWParameters.PrivacyContext(description: "ProxyConfig")
        privacyContext.proxyConfigurations = [proxyConfig]
        parameters.setPrivacyContext(privacyContext)
    
        let host = url.host ?? ""
        let path = url.path.isEmpty ? "/" : url.path
        let query = url.query ?? ""
        let fullPath = query.isEmpty ? path : "\(path)?\(query)"

        let connection = NWConnection(
            to: .hostPort(
                host: .init(host),
                port: .init(integerLiteral: UInt16(url.port ?? 443))
            ),
            using: parameters
        )

        connection.stateUpdateHandler = { state in
            switch state {
            case .ready:

                let httpRequest = "GET \(fullPath) HTTP/1.1\r\nHost: \(host)\r\nConnection: close\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0.1 Safari/605.1.15\r\nAccept-Language: en-US,en;q=0.9\r\nAccept-Encoding: gzip, deflate, br\r\nSec-Fetch-Dest: document\r\nSec-Fetch-Mode: navigate\r\nSec-Fetch-Site: none\r\nPriority: u=0, i\r\n\r\n"
                
                connection.send(content: httpRequest.data(using: .utf8), completion: .contentProcessed({ error in
                    if let error = error {
                        print("Failed to send request: \(error)")
                        completion(false)
                        return
                    }
                    
                    self.readAllData(connection: connection) { finalData, readError in

                        if let readError = readError {
                            print("Failed to receive response: \(readError)")
                            completion(false)
                            return
                        }

                        guard let data = finalData else {
                            print("No data received or unable to read data.")
                            completion(false)
                            return
                        }

                        if let body = decodeRequest(data: data, encodingType: nil) {
                            completion(true)

                        } else {
                            print("Unable to decode response body.")
                            completion(false)
                        }
                    }
                }))

            case .failed(let error):
                print("Connection failed for proxy \(proxyDetails[0]): \(error)")
                completion(false)

            case .cancelled:
                print("Connection cancelled for proxy \(proxyDetails[0])")
                completion(false)

            case .waiting(let error):
                print("Connection waiting for proxy \(proxyDetails[0]): \(error)")
                completion(false)

            default:
                break
            }
        }

        connection.start(queue: .global())
    }
    private func readAllData(connection: NWConnection,
                             accumulatedData: Data = Data(),
                             completion: @escaping (Data?, Error?) -> Void) {
        
        connection.receive(minimumIncompleteLength: 1, maximumLength: 65536) { data, context, isComplete, error in
            
            if let error = error {
                completion(nil, error)
                return
            }
            
            let newAccumulatedData = accumulatedData + (data ?? Data())

            if isComplete {
                completion(newAccumulatedData, nil)
            } else {
                self.readAllData(connection: connection,
                                 accumulatedData: newAccumulatedData,
                                 completion: completion)
            }
        }
    }

Upvotes: 0

Views: 31

Answers (0)

Related Questions