Swift String and base64Encode Confusion - ios

I have problem with encoding data. My token has 1228 characters and
let data = Data(base64Encoded: tokenString)!
works fine, but when i add to token some information and my token has 1263 characters Data(base64Encoded:) returns nil.
Problem in my opinion is in the string length or Data(base64Encoded:).
Does Data(base64Encoded:) have any restrictions on the length of characters?
Please give me some info about this problem.

Not sure how you encode the modified token, but looks like the encoded Base64 string doesn't include padding characters. Appending = to the modified token seems to fix the decoding issue:
// original token -> 57 bytes
let data1 = Data(base64Encoded: "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyMTF9")
// modified token -> nil
let data2 = Data(base64Encoded: "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyMX0")
// modified token with '=' for padding -> 56 bytes
let data2Fixed = Data(base64Encoded: "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyMX0=")
// decodes correctly: "{"sub":"1234567890","name":"John Doe","iat":15162390221}"
let string = String(data: data2Fixed!, encoding: .utf8)
So to solve it properly you probably need to look into the encoder. Hope that helps!

Related

Decode a JSON object escaped in a String

I get a response from an API (unfortunately, I cannot change it) that looks something like (just an example):
As bytes => "{\"key\":\"value\"}"
The starting and ending quotes and the escaped quotes are all part of the response, I am solving it in a really ugly way that looks like this:
// (...) Receiving response
guard var responseString = String(bytes: data, encoding: .utf8) else {
print("Response wasn't just a string, great!") // unfortunately, this never happens
return
}
responseString = responseString.trimmingCharacters(in: .whitespacesAndNewlines) // make sure it is trimmed
responseString = String(responseString.dropFirst()) // drop the quote at the start
responseString = String(responseString.dropLast()) // drop the quote at the end
responseString = responseString.replacingOccurrences(of: "\\\"", with: "\"")// convert all \" to " (and hope nothing else is escaped <<< this is really bad!!!)
let responseDataToDecode = responseString.data(using: .utf8)!
// (...) decoding with JSONDecoder
Is there a way to automatically unescape the string and use the JSON object that is contained in it?
If it's double-encoded, then you just need to double-decode. If I understand correctly, the incoming data is like this:
let str = #""{\"key\":\"value\"}""#
// "{\\"key\\":\\"value\\"}"
The first byte is ", the second byte is {, the third byte is \, the fourth byte is ".
That's a JSON-encoded string. So decode that as a string (there was a time when this didn't work because it's a "fragment," but it works fine currently, at least in all my tests):
let decoder = JSONDecoder()
let string = try! decoder.decode(String.self, from: Data(str.utf8)) // {"key":"value"}
And then decode that as your type ([String:String] just for example):
let result = try! decoder.decode([String:String].self, from: Data(string.utf8))
// ["key": "value"]
(IMO this kind of double-encoding is fine, BTW, and I'm not sure why there are so many comments against it. Serializing an arbitrary object makes a lot more sense in many cases than forcing the schema to deal with an arbitrary structure. As long as it's cleanly encoded, I don't see any problem here.)
There's a first step: You need an official documented statement what exactly the format of your data is. It looks like someone took some data, turned it into JSON data, interpreted the data as a string, and then converted the string to a JSON fragment. It's not difficult to decode the JSON fragment, getting a string, turning the string into data, and decoding that data as JSON (starting with JSONSerialization and .allowFragments, probably the only time you should use .allowFragments in your life). Doing it without swearing is hard.
But first you want in writing that this is actually the format. Because I would bet that whoever is responsible for that data format will eventually change it without telling you and break your code.

Swift URL.path changes encoding of utf-8 characters

Why does converting a String to an URL in Swift 4.2 and then converting the URL back to a String using url.path change the encoding of special characters like german umlauts (ä, ö, ü), even if I use a utf-8 encoding?
I wrote some sample code to show my problem. I encoded the strings to base64 in order to show that there is a difference.
I also have a similar unsolved problem with special characters and swift here.
Sample Code
let string = "/path/to/file"
let stringUmlauts = "/path/to/file/with/umlauts/testäöü"
let base64 = Data(string.utf8).base64EncodedString()
let base64Umlauts = Data(stringUmlauts.utf8).base64EncodedString()
print(base64, base64Umlauts)
let url = URL(fileURLWithPath: string)
let urlUmlauts = URL(fileURLWithPath: stringUmlauts)
let base64Url = Data(url.path.utf8).base64EncodedString()
let base64UrlUmlauts = Data(urlUmlauts.path.utf8).base64EncodedString()
print(base64Url, base64UrlUmlauts)
Output
The base64 and base64Url string stay the same but the base64Umlauts and the base64UrlUmlauts are different.
"L3BhdGgvdG8vZmlsZQ==" for base64
"L3BhdGgvdG8vZmlsZQ==" for base64Url
"L3BhdGgvdG8vZmlsZS93aXRoL3VtbGF1dHMvdGVzdMOkw7bDvA==" for base64Umlauts
"L3BhdGgvdG8vZmlsZS93aXRoL3VtbGF1dHMvdGVzdGHMiG/MiHXMiA==" for base64UrlUmlauts
When I put the base64Umlauts and base64UrlUmlauts strings into an online Base64 decoder, they both show /path/to/file/with/umlauts/testäöü, but the ä, ö, ü are different (not visually).
stringUmlauts.utf8 uses the Unicode characters äöü.
But urlUmlauts.path.utf8 uses the Unicode characters aou each followed by the combining ¨.
This is why you get different base64 encoding - the characters look the same but are actually encoded differently.
What's really interesting is that Array(stringUmlauts) and Array(urlUmlauts.path) are the same. The difference doesn't appear until you perform the UTF-8 encoding of the otherwise exact same String values.
Since the base64 encoding is irrelevant, here's a more concise test:
let stringUmlauts = "/path/to/file/with/umlauts/testäöü"
let urlUmlauts = URL(fileURLWithPath: stringUmlauts)
print(stringUmlauts, urlUmlauts.path) // Show the same
let rawStr = stringUmlauts
let urlStr = urlUmlauts.path
print(rawStr == urlStr) // true
print(Array(rawStr) == Array(urlStr)) // true
print(Array(rawStr.utf8) == Array(urlStr.utf8)) // false!!!
So how is the UTF-8 encoding of two equal strings different?
One solution to this is to use precomposedStringWithCanonicalMapping on the result of path.
let urlStr = urlUmlauts.path.precomposedStringWithCanonicalMapping
Now you get true from:
print(Array(rawStr.utf8) == Array(urlStr.utf8)) // now true

How to identify UTF-8 encoded text from a string and convert it to smiley\emoticon in Swift

I am doing App which support Smiley/emoticons feature. From the backend I am getting response like this str = "Hferuhggeðððððfjjnjrnjgnejfnsgjen".
This string response has a UTF-8 encoded text in it, for the above str UTF-8 encode text is "ððððð".
Now I need to identify the location of the utf-8 encoded text from the response obtained, and convert that encoded text to an emoticon/smiley.
Finally I found solution if you decode string you will get smiley ,please find the code
let che = descriptionText.cString(using: .isoLatin1)
let decode_string = String(cString: che!, encoding: .utf8)
This worked for me.

RFC2045-MIME variant of Base 64 in swift

I need to convert a encode a string with the RFC2045-MIME variant of base64. However I cant find any options to do this in swift. At the moment i use this method:
var str = "\(test1):\(test2)"
str = str.data(using: .utf8)!.base64EncodedString(options: Data.Base64EncodingOptions(rawValue: UInt(0)))
but this is just the standard base64 encoding not the RFC2045-MIME variant. How can i use the RFC2045-MIME variant?
The only differences between the base64 encodings specified in RFC 2045 and RFC 4686 are that RFC 2045 specifies a maximum line length of 76 characters, with lines separated by CRNL.
The documentation of base64EncodedString(options:) says the default line ending is CRNL, so:
let data = str.data(using: .utf8)!
let b64 = data.base64EncodedString(options: .lineLength76Characters)

Decode Data that is base64String to String utf Swift

A library save a token as Data in UserDefaults
//loading the token
if let token = UserDefaults.standard.object(forKey: "token") as? Data {
let base64Encoded = token.base64EncodedData
let base64EncodedString = token.base64EncodedString()
}
When I print that base64Encoded value in the console I get the value: Y2E2N2Y5NTItNDVkOC00YzZkLWFkZDMtZGRiMjc5NGE3YWI2OjdmZDU1ZTAyLWExMjEtNGQ1ZC05N2MzLWM5OWY4NTg5NTIzNg== as Data
When I copy this value from the output in the console and use this website https://www.base64decode.org/ I got the expected result. ca67f952-45d8-4c6d-add3-ddb2794a7ab6:7fd55e02-a121-4d5d-97c3-c99f85895236
My problem is that I don't get convert that base64Encoded to String in my code.
When I use base64EncodedString I got a string, but I can't figure out which format this is: PFsYdirs21247rRG/XcWOVGUGUcCCTzXz7VFAnunLJU= The method base64EncodedString() This is an method from Apple: https://developer.apple.com/documentation/foundation/nsdata/1413546-base64encodedstring
When I decode this value to utf8 at the website I got: <[v*]Fw9QG
When I try to convert my base64Encoded (Data, not String) to a string like this
if let toString = String(data: base64Encoded, encoding: String.Encoding.utf8) as String {
print("test \(toString)")
}
my compiler threw an error: Cannot convert value of type(Data.Base64EncodingOptions) -> Data to expected argument type Data
Here I found a solution to decode when my Data-value would be a string.
https://stackoverflow.com/a/31859383/4420355
So I'm quite confused about the results. Long story short:
I have base64Encoded Data (Y2E2N2Y5NTItNDVkOC00YzZkLWFkZDMtZGRiMjc5NGE3YWI2OjdmZDU1ZTAyLWExMjEtNGQ1ZC05N2MzLWM5OWY4NTg5NTIzNg==) and want them convert to an utf8 string. I don't get handle this.

Resources