0

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 the server reporting a padding error.

I'm attempting to read a file (on iPad simulator) as data, then convert to a base64 encoded string, and then using this as part of my httpbody. I've tried this with a couple of different file types, but for the purpose of this example i'm just using a local settings file.

The Frappe instance is (sometimes depending on the data) returning the following error:

File \"/usr/lib/python3.7/base64.py\", line 87, in b64decode\n return binascii.a2b_base64(s)\nbinascii.Error: Incorrect padding

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)


    // Session and dataTask send request below but not relevant to example
    }

After a bit of googling, I discovered I can resolve the error by appending "==" to the file string, but this feels nasty/wrong.

let fileAsString = (attachment.base64EncodedString().replacingOccurrences(of: "+", with: "%2B") + "==")

Can someone please point out where I might be going wrong and how to do this properly?

Andy P
  • 667
  • 7
  • 14
  • You used `base64EncodedString()`, but in fact there is a `options` parameter, see what value you should put there. In fact the "==" might be added, because a base64 value should be of a special length (modulo a value). So adding "==" works only if the previous data has a certain length, sometimes, you won't need the "=="... See the padding of https://en.wikipedia.org/wiki/Base64 – Larme Aug 18 '21 at 09:25
  • @Larme yes, i'm aware of the options, but don't as yet understand well enough what each one does. Padding with "==" always seems to work as from what I understand the python method server side strips any extra characters. But i'm sure there has to be an option that gets the padding correct without me manipulating it afterwards – Andy P Aug 18 '21 at 09:28
  • I don't think there is an option to produce a URL-safe base64 encoding. See [this answer](https://stackoverflow.com/a/26212148/5133585). You can just replace `/` with `_` and `+` with `-`. – Sweeper Aug 18 '21 at 09:31
  • @Sweeper You might be on to something there (which also might resolve the other question i posted). I didn't even know there was such a thing as url safe base64. A quick search found this question: https://stackoverflow.com/questions/43499651/decode-base64url-to-base64-swift - i'll give it a try – Andy P Aug 18 '21 at 09:45
  • I made a Data extension based on the answer to the question above - didn't help. Given that the server seems to be expecting the padding, but the the 'URL Safe' method removes '=' characters I guess this is perhaps not surprising. – Andy P Aug 18 '21 at 11:01

1 Answers1

1

I'm not particularly happy with this answer, but its the one I used, and from the small number of xml and pdf files I have tried is working for me.

From what can work out, there is no Swift method that automatically generates the padding and it needs to be done manually.

I based my solution on the answers to this question and wrote an extension to Data to provide the padding:

extension Data {
    
func base64EncodedStringWithPadding() -> String {
    let base64String = self.base64EncodedString()
    let remainder = base64String.count % 4
    if remainder == 0 {
        return base64String
    } else {
        let paddedLength = base64String.count + 4 - remainder
        return base64String.padding(toLength: paddedLength, withPad: "=", startingAt: 0)
    }
    
}
}

Which I then used in my function like this:

let fileAsString = attachment.base64EncodedStringWithPadding()
Andy P
  • 667
  • 7
  • 14