Upload Torrent with multipart/form-data to a Server return Bad Request

94 Views Asked by At

I'm trying to upload a torrent file to a server like this:

func add( _ torrents: [String: Data]) async -> Result<Void, API.Error> {
    do {
        let url = URL(string: "http://192.168.1.1:9080/api/v2/torrents/add")!
        let boundary: String = UUID().uuidString
        var body = Data()
        
        for (torrentName, torrentData) in torrents {
            body.addField("--\(boundary)")
            body.addField("Content-Disposition: form-data; name=\"torrents\"; filename=\"\(torrentName)\"")
            body.addField("Content-Type: application/x-bittorrent")
            body.addField(torrentData)
        }
        
        body.addField("--\(boundary)--")
        
        var urlRequest = URLRequest(url: url)
        urlRequest.httpMethod = "post".uppercased()
        urlRequest.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
        urlRequest.setValue("\(body.count)", forHTTPHeaderField: "Content-Length")
        urlRequest.httpBody = body
         
        let apiResponse = try await urlSession.data(for: urlRequest)
        print(apiResponse)
        print(String(data: apiResponse.0, encoding: .utf8) ?? "noup")
        
        guard (apiResponse.1 as? HTTPURLResponse)?.statusCode == 415 else { return .success(()) }
        return .failure(Error(TorrentFileError.invalideFile))
    } catch {
        let apiError = handleError(error)
        return .failure(apiError)
    }
}

extension Data {
    private mutating func append(_ string: String) {
        guard let data = string.data(using: .utf8) else { return }
        append(data)
    }

    mutating func addField(_ string: String) {
        append(string)
        append(.httpFieldDelimiter)
    }

    mutating func addField(_ data: Data) {
        append(data)
        append(.httpFieldDelimiter)
    }
}

extension String {
    static let httpFieldDelimiter = "\r\n"
}

Loading torrent file (SwiftUI):

.fileImporter(isPresented: $showTorrentFileImporter, allowedContentTypes: [.torrent], allowsMultipleSelection: true) { result in
    switch result {
    case .success(let urls):
        let torrents: [String: Data] = urls.reduce(into: [:]) { partialResult, url in
            let name = url.lastPathComponent
            let data = try? Data(contentsOf: url)
            partialResult[name] = data == nil ? Data() : data
        }
        
        appContext.add(torrents)
        
    case .failure(let error):
        print(error)
    }
}

API description here.

Please help me understand what I'm doing wrong. Response from server is:

(0 bytes, <NSHTTPURLResponse: 0x600000cf8160> { URL: http://192.168.1.1:9080/api/v2/torrents/add } { Status Code: 400, Headers {
Connection =     (
    close
);
"Content-Length" =     (
    0
);
Date =     (
    "Mon, 26 Feb 2024 08:13:11 GMT"
);
} })

UPD: If i set the urlReguest Content Type as part of body (not header field):

 body.addField("Content-Type: multipart/form-data; boundary=\(boundary)")

response from server is:

(6 bytes, <NSHTTPURLResponse: 0x600000681a80> { URL: http://192.168.1.1:9080/api/v2/torrents/add } { Status Code: 200, Headers {
    Connection =     (
        "keep-alive"
    );
    "Content-Length" =     (
        6
    );
    "Content-Type" =     (
        "text/plain; charset=UTF-8"
    );
    Date =     (
        "Mon, 26 Feb 2024 13:19:21 GMT"
    );
    "content-security-policy" =     (
        "frame-ancestors 'self';"
    );
    "x-content-type-options" =     (
        nosniff
    );
    "x-frame-options" =     (
        SAMEORIGIN
    );
    "x-xss-protection" =     (
        "1; mode=block"
    );
} })
Fails.

In general, some strange APIs. For some reason, query items also need to be sent in the body of the request and not as part of the URL.

0

There are 0 best solutions below