|
|
|
@ -700,72 +700,86 @@ public enum OnionRequestAPI: OnionRequestAPIType {
|
|
|
|
|
do {
|
|
|
|
|
let data: Data = try AESGCM.decrypt(responseData, with: destinationSymmetricKey)
|
|
|
|
|
|
|
|
|
|
// The data will be in the form of `l123:jsone` or `l123:json456:bodye` so we need to break the data into
|
|
|
|
|
// parts to properly process it
|
|
|
|
|
guard let responseString: String = String(data: data, encoding: .ascii), responseString.starts(with: "l") else {
|
|
|
|
|
return seal.reject(HTTP.Error.invalidResponse)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let stringParts: [String.SubSequence] = responseString.split(separator: ":")
|
|
|
|
|
|
|
|
|
|
guard stringParts.count > 1, let infoLength: Int = Int(stringParts[0].suffix(from: stringParts[0].index(stringParts[0].startIndex, offsetBy: 1))) else {
|
|
|
|
|
return seal.reject(HTTP.Error.invalidResponse)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let infoStringStartIndex: String.Index = responseString.index(responseString.startIndex, offsetBy: "l\(infoLength):".count)
|
|
|
|
|
let infoStringEndIndex: String.Index = responseString.index(infoStringStartIndex, offsetBy: infoLength)
|
|
|
|
|
let infoString: String = String(responseString[infoStringStartIndex..<infoStringEndIndex])
|
|
|
|
|
|
|
|
|
|
guard let infoStringData: Data = infoString.data(using: .utf8), let responseInfo: ResponseInfo = try? JSONDecoder().decode(ResponseInfo.self, from: infoStringData) else {
|
|
|
|
|
// Process the bencoded response
|
|
|
|
|
guard let processedResponse: (info: ResponseInfo, body: Data?) = process(bencodedData: data) else {
|
|
|
|
|
return seal.reject(HTTP.Error.invalidResponse)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Custom handle a clock out of sync error (v4 returns '425' but included the '406' just in case)
|
|
|
|
|
guard responseInfo.code != 406 && responseInfo.code != 425 else {
|
|
|
|
|
// Custom handle a clock out of sync error (v4 returns '425' but included the '406'
|
|
|
|
|
// just in case)
|
|
|
|
|
guard processedResponse.info.code != 406 && processedResponse.info.code != 425 else {
|
|
|
|
|
SNLog("The user's clock is out of sync with the service node network.")
|
|
|
|
|
return seal.reject(SnodeAPIError.clockOutOfSync)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
guard responseInfo.code != 401 else { // Signature verification failed
|
|
|
|
|
guard processedResponse.info.code != 401 else { // Signature verification failed
|
|
|
|
|
SNLog("Failed to verify the signature.")
|
|
|
|
|
return seal.reject(SnodeAPIError.signatureVerificationFailed)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handle error status codes
|
|
|
|
|
guard 200...299 ~= responseInfo.code else {
|
|
|
|
|
guard 200...299 ~= processedResponse.info.code else {
|
|
|
|
|
return seal.reject(
|
|
|
|
|
OnionRequestAPIError.httpRequestFailedAtDestination(
|
|
|
|
|
statusCode: UInt(responseInfo.code),
|
|
|
|
|
statusCode: UInt(processedResponse.info.code),
|
|
|
|
|
data: data,
|
|
|
|
|
destination: destination
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If there is no data in the response then just return the ResponseInfo
|
|
|
|
|
guard responseString.count > "l\(infoLength)\(infoString)e".count else {
|
|
|
|
|
return seal.fulfill((responseInfo, nil))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Extract the response data as well
|
|
|
|
|
let dataString: String = String(responseString.suffix(from: infoStringEndIndex))
|
|
|
|
|
let dataStringParts: [String.SubSequence] = dataString.split(separator: ":")
|
|
|
|
|
|
|
|
|
|
guard dataStringParts.count > 1, let finalDataLength: Int = Int(dataStringParts[0]), let suffixData: Data = "e".data(using: .utf8) else {
|
|
|
|
|
return seal.reject(HTTP.Error.invalidResponse)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let dataBytes: Array<UInt8> = Array(data)
|
|
|
|
|
let dataEndIndex: Int = (dataBytes.count - suffixData.count)
|
|
|
|
|
let dataStartIndex: Int = (dataEndIndex - finalDataLength)
|
|
|
|
|
let finalDataBytes: ArraySlice<UInt8> = dataBytes[dataStartIndex..<dataEndIndex]
|
|
|
|
|
let finalData: Data = Data(finalDataBytes)
|
|
|
|
|
|
|
|
|
|
return seal.fulfill((responseInfo, finalData))
|
|
|
|
|
return seal.fulfill(processedResponse)
|
|
|
|
|
}
|
|
|
|
|
catch {
|
|
|
|
|
return seal.reject(error)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static func process(bencodedData data: Data) -> (info: ResponseInfo, body: Data?)? {
|
|
|
|
|
// The data will be in the form of `l123:jsone` or `l123:json456:bodye` so we need to break the data
|
|
|
|
|
// into parts to properly process it
|
|
|
|
|
guard let responseString: String = String(data: data, encoding: .ascii), responseString.starts(with: "l") else {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let stringParts: [String.SubSequence] = responseString.split(separator: ":")
|
|
|
|
|
|
|
|
|
|
guard stringParts.count > 1, let infoLength: Int = Int(stringParts[0].suffix(from: stringParts[0].index(stringParts[0].startIndex, offsetBy: 1))) else {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let infoStringStartIndex: String.Index = responseString.index(responseString.startIndex, offsetBy: "l\(infoLength):".count)
|
|
|
|
|
let infoStringEndIndex: String.Index = responseString.index(infoStringStartIndex, offsetBy: infoLength)
|
|
|
|
|
let infoString: String = String(responseString[infoStringStartIndex..<infoStringEndIndex])
|
|
|
|
|
|
|
|
|
|
guard let infoStringData: Data = infoString.data(using: .utf8), let responseInfo: ResponseInfo = try? JSONDecoder().decode(ResponseInfo.self, from: infoStringData) else {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Custom handle a clock out of sync error (v4 returns '425' but included the '406' just in case)
|
|
|
|
|
guard responseInfo.code != 406 && responseInfo.code != 425 else { return nil }
|
|
|
|
|
guard responseInfo.code != 401 else { return nil }
|
|
|
|
|
|
|
|
|
|
// If there is no data in the response then just return the ResponseInfo
|
|
|
|
|
guard responseString.count > "l\(infoLength)\(infoString)e".count else {
|
|
|
|
|
return (responseInfo, nil)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Extract the response data as well
|
|
|
|
|
let dataString: String = String(responseString.suffix(from: infoStringEndIndex))
|
|
|
|
|
let dataStringParts: [String.SubSequence] = dataString.split(separator: ":")
|
|
|
|
|
|
|
|
|
|
guard dataStringParts.count > 1, let finalDataLength: Int = Int(dataStringParts[0]), let suffixData: Data = "e".data(using: .utf8) else {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let dataBytes: Array<UInt8> = Array(data)
|
|
|
|
|
let dataEndIndex: Int = (dataBytes.count - suffixData.count)
|
|
|
|
|
let dataStartIndex: Int = (dataEndIndex - finalDataLength)
|
|
|
|
|
let finalDataBytes: ArraySlice<UInt8> = dataBytes[dataStartIndex..<dataEndIndex]
|
|
|
|
|
let finalData: Data = Data(finalDataBytes)
|
|
|
|
|
|
|
|
|
|
return (responseInfo, finalData)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|