taler-ios

iOS apps for GNU Taler (wallet)
Log | Files | Refs | README | LICENSE

QuickDataTask.swift (9461B)


      1 /*
      2  * This file is part of GNU Taler, ©2022-25 Taler Systems S.A.
      3  * See LICENSE.md
      4  */
      5 /**
      6  * @author Marc Stibane
      7  */
      8 import Foundation
      9 //import "Foundation/NSURLError.h"
     10 import os.log
     11 
     12 import FTalerWalletcore
     13 
     14 // will be called from wallet-core for networking
     15 func request_create(userdata: Optional<UnsafeMutableRawPointer>,
     16                  requestInfo: Optional<UnsafeMutablePointer<JSHttpRequestInfo>>) -> Int32 {
     17     let quickjs = Unmanaged<Quickjs>.fromOpaque(userdata!).takeUnretainedValue()
     18 
     19     if let requestInfo {
     20         if let url = URL(string: String(cString: requestInfo.pointee.url)) {
     21             let responseCb = requestInfo.pointee.response_cb
     22             let responseCbCls = requestInfo.pointee.response_cb_cls
     23             let method = String(cString: requestInfo.pointee.method)
     24             let requestHeaders = requestInfo.pointee.request_headers
     25                 let redirect = requestInfo.pointee.redirect             // TODO: redirect
     26             let timeoutMs = requestInfo.pointee.timeout_ms
     27                 let debug = requestInfo.pointee.debug                   // TODO: debug
     28             let reqBody = requestInfo.pointee.req_body
     29             let bodyLen = requestInfo.pointee.req_body_len
     30 
     31             var request = URLRequest(url: url)
     32             request.httpMethod = method
     33             request.timeoutInterval = TimeInterval(timeoutMs)
     34             if let reqBody {        // caller will deallocate the req_body after dataTask finish or cancel
     35                 let body = Data(bytesNoCopy: reqBody, count: Int(bodyLen), deallocator: .none)
     36                 request.httpBody = body
     37             }
     38             if var ptr = requestHeaders {
     39                 while let cString = ptr.pointee {
     40                     let string = String(cString: cString)
     41                     if let index = string.firstIndex(of: ":") {
     42                         let headerField = string.prefix(upTo: index)
     43                         let nextIndex = string.index(index, offsetBy: 1)        // skip the ":"
     44                         let value = string.suffix(from: nextIndex)
     45                         request.addValue(String(value), forHTTPHeaderField: String(headerField))
     46                     }
     47                     ptr += 1
     48                 }
     49             }
     50 
     51             return quickjs.reqCreate(request, responseCb, responseCbCls)
     52         }
     53     }
     54     return 0
     55 }
     56 
     57 func request_cancel(userdata: Optional<UnsafeMutableRawPointer>,
     58                    requestID: Int32) -> Int32 {
     59     let quickjs = Unmanaged<Quickjs>.fromOpaque(userdata!).takeUnretainedValue()
     60     return quickjs.reqCancel(requestID)
     61 }
     62 // MARK: -
     63 extension Error {
     64     var errorCode:Int? {
     65         return (self as NSError).code
     66     }
     67 }
     68 // MARK: -
     69 class QuickDataTask: NSObject {
     70     let urlSession: URLSession
     71     let request: URLRequest
     72     let requestID: Int32
     73     var requests: [Int32: QuickDataTask]
     74     let responseCb: JSHttpResponseCb?
     75     let responseCbCls: Optional<UnsafeMutableRawPointer>
     76 
     77     var dataTask: URLSessionDataTask? = nil
     78     var logger: Logger
     79 
     80     init(urlSession: URLSession,
     81             request: URLRequest,
     82           requestID: Int32,
     83            requests: inout [Int32: QuickDataTask],
     84          responseCb: JSHttpResponseCb?,
     85       responseCbCls: Optional<UnsafeMutableRawPointer>
     86     ) {
     87         self.logger = Logger(subsystem: "net.taler.gnu", category: "Networking")
     88         self.urlSession = urlSession
     89         self.request = request
     90         self.requestID = requestID
     91         self.requests = requests
     92         self.responseCb = responseCb
     93         self.responseCbCls = responseCbCls
     94     }
     95 
     96     func run() {
     97         if let responseCb, let responseCbCls {
     98             let method = self.request.httpMethod ?? "Unknown"
     99             let url = self.request.url?.absoluteString ?? EMPTYSTRING
    100             logger.trace("❓\(self.requestID, privacy: .public)  \(method, privacy: .public) \(url, privacy: .public)")
    101             dataTask = urlSession.dataTask(with: request) { [self] data, response, error in
    102                 if let response = response as? HTTPURLResponse {
    103                     var headerArray: [String] = []
    104                     var numHeaders: Int32 = 0
    105                     let status = Int32(response.statusCode)
    106                     let errmsg = HTTPURLResponse.localizedString(forStatusCode: Int(status))
    107                     let errmsg_p0 = UnsafeMutablePointer<CChar>(mutating: errmsg.cString(using: .utf8))
    108               // Initialization of 'UnsafeMutablePointer<CChar>' (aka 'UnsafeMutablePointer<Int8>') results in a dangling pointer
    109                     let headers = response.allHeaderFields
    110                     for (key,value) in headers {
    111                         headerArray.append("\(key): \(value)")
    112                         numHeaders += 1
    113                     }
    114                     let cHeaders = CStringArray(headerArray)
    115 
    116                     if let data {
    117                         let ndata:NSData = data as NSData
    118                         let bodyPtr = UnsafeMutableRawPointer(mutating: ndata.bytes)
    119                         var responseInfo = JSHttpResponseInfo(request_id: self.requestID,
    120                                                               status: status,
    121                                                               errmsg: errmsg_p0,
    122                                                     response_headers: cHeaders.pointer,
    123                                                 num_response_headers: numHeaders,
    124                                                                 body: bodyPtr,
    125                                                             body_len: UInt32(data.count))
    126                         let responseInfoPtr = UnsafeMutablePointer<JSHttpResponseInfo>(&responseInfo)
    127                         // Initialization of 'UnsafeMutablePointer<JSHttpResponseInfo>' results in a dangling pointer
    128                         logger.trace("❗️ \(self.requestID, privacy: .public) \(url, privacy: .public)")
    129                         responseCb(responseCbCls, responseInfoPtr)
    130                         return
    131                     } else { // data == nil
    132                         logger.error("‼️\(self.requestID, privacy: .public)  \(method, privacy: .public) \(response.statusCode, privacy: .public) \(errmsg, privacy: .public)")
    133                         var responseInfo = JSHttpResponseInfo(request_id: self.requestID,
    134                                                                   status: status,
    135                                                                   errmsg: errmsg_p0,
    136                                                         response_headers: cHeaders.pointer,
    137                                                     num_response_headers: numHeaders,
    138                                                                     body: nil,
    139                                                                 body_len: 0)
    140                         let responseInfoPtr = UnsafeMutablePointer<JSHttpResponseInfo>(&responseInfo)
    141                         responseCb(responseCbCls, responseInfoPtr)
    142                     }
    143                 } else {    // pass error to walletCore
    144                     Task.detached {
    145                         self.logger.error("⁉️ \(self.requestID, privacy: .public)  \(method, privacy: .public)  \(error, privacy: .public)")
    146                         Controller.shared.checkInternetConnection()
    147                         if let errmsg = error?.localizedDescription,
    148                            let errmsgC = errmsg.cString(using: .utf8)
    149                         {
    150                             let errmsg_p1 = UnsafeMutablePointer<CChar>(mutating: errmsgC)
    151                             var responseInfo = JSHttpResponseInfo(request_id: self.requestID,
    152                                                                       status: 0,
    153                                                                       errmsg: errmsg_p1,
    154                                                             response_headers: nil,
    155                                                         num_response_headers: 0,
    156                                                                         body: nil,
    157                                                                     body_len: 0)
    158                             let responseInfoPtr = UnsafeMutablePointer<JSHttpResponseInfo>(&responseInfo)
    159                             responseCb(responseCbCls, responseInfoPtr)
    160                         } else {
    161                             // should never happen
    162                             let errmsg_p1 = UnsafeMutablePointer<CChar>(nil)
    163                             var responseInfo = JSHttpResponseInfo(request_id: self.requestID,
    164                                                                       status: 0,
    165                                                                       errmsg: errmsg_p1,
    166                                                             response_headers: nil,
    167                                                         num_response_headers: 0,
    168                                                                         body: nil,
    169                                                                     body_len: 0)
    170                             let responseInfoPtr = UnsafeMutablePointer<JSHttpResponseInfo>(&responseInfo)
    171                             responseCb(responseCbCls, responseInfoPtr)
    172                         }
    173                     }
    174                 }
    175                 requests[requestID] = nil
    176             }
    177             dataTask?.resume()
    178         }
    179     }
    180 }