Search code examples
swiftfrappe

Swift: Files appear to be corrupt after base64 encoding


I'm trying to upload a file as an attachment to my Frappe instance and running into a couple of problems. The first of which is related and a padding error is documented: in this question. I include it here just incase it is in some way part of the mistake i'm making.

My second problem, and the focus of this question is that when I upload my attachments they appear to be corrupt. A small xml file can be opened but is largely garbled. A larger PDF file will not even open. My assumption is i'm making some sort of beginner mistake with the base64 encoding.

Get data from local URL:

let settingsLocation = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent("SettingsList.plist")
let fileData = try Data.init(contentsOf: settingsLocation)

This is then passed to the following function:

    public func attachFileToCloudResource(resourceType: String, resourceName: String, attachment: Data) {
    
    let fileAsString = (attachment.base64EncodedString().replacingOccurrences(of: "+", with: "%2B") + "==")
    
    var request = URLRequest(url: URL(string: FRAPPE_INSTANCE + FRAPPE_METHODS + FRAPPE_UPLOAD_ATTACHMENT)!)

    var components = URLComponents(url: request.url!, resolvingAgainstBaseURL: false)!

    components.queryItems = [
        URLQueryItem(name: FRAPPE_DOCTYPE, value: resourceType),
        URLQueryItem(name: FRAPPE_DOCNAME, value: resourceName),
        URLQueryItem(name: FRAPPE_FILENAME, value: "testFile.xml"),
        URLQueryItem(name: FRAPPE_DATA, value: fileAsString),
        URLQueryItem(name: FRAPPE_PRIVATE, value: "1"),
        URLQueryItem(name: FRAPPE_DECODE_BASE64, value: "1")
    ]

    let query = components.url!.query

    request.httpMethod = "POST"
    request.addValue("token \(API_KEY):\(API_SECRET)", forHTTPHeaderField: "Authorization")
    request.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
    request.httpBody = Data(query!.utf8)


    // url session and dataTask send data to server below this point
    }

This works correctly in that I get a positive response from the server and the file is attached to the specified record - but when the file is downloaded (via web browser) it seems to be corrupt regardless of what type of file I try.

Any help would be much appreciated.


Solution

  • I was able to find a solution that works for me.

    First I used an online base64 encoder to encode a simple xml file. I then used the generated base64 string to re-create my POST using Postman (great tool, only just discovered it).

    This worked and my file was attached to the specified record and not corrupted.

    I then used the feature of Postman that generates code to see what it thought my base64String used for filedata should look like. I found that all the '+' characters were replaced with "%2B".

    A bit of further investigation found that the query item in Swift does some of it's own percent encoding, however this answer gave an explanation why the '+' character is valid and not percent encoded by default.

    The end result was that I removed my manual percent encoding from the original base64String:

    let fileAsString = attachment.base64EncodedString() + "=="
    

    And I added it when generating the httpBody from the query items:

    request.httpBody = Data(query!.replacingOccurrences(of: "+", with: "%2B").utf8)