POST base64 encoded image string to PHP - ios

I've read through numerous posts and watched some videos.
Problem: I'm unable to POST a base64 string image to PHP server and save to MySQL database without corruption.
What I've attempted: I've attempted passing the base64 encoded string of the image just like I would pass a string or int as a POST parameter. I verified that the POST data is successfully making it to the PHP server and it is getting saved in the database. However, when I extract the base64 encoded string and decode it so the image can be rendered in the browser, it does not work. It only shows a broken image link instead of the image. It seems as though something is happening to corrupt the data in the POST process. I say this because I copied the base64 string directly from a println() statement in Xcode and pasted it into the PHP file where it was decoded and displayed perfectly. I'm able to upload base64 strings of images from my Android app perfectly fine. The problems seems to be with how I'm doing it in Swift.
After some research, I see where I may need to specify a boundary around the image parameter. I'm not positive about this though because I'm not sure if you just need a boundary specified when you send the image file directly or if you also need it when sending a base64 encoded string of the image.
Can anyone tell me if I need to set a boundary around the base64 encoded string parameter before sending it to the PHP server? Any idea what I could be doing wrong?
let image: UIImage = imgProfilePic.image!
let size = CGSizeApplyAffineTransform(image.size, CGAffineTransformMakeScale(0.1, 0.1))
let hasAlpha = false
let scale: CGFloat = 0.0 // Automatically use scale factor of main screen
UIGraphicsBeginImageContextWithOptions(size, !hasAlpha, scale)
image.drawInRect(CGRect(origin: CGPointZero, size: size))
let scaledImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
var imageData = UIImageJPEGRepresentation(scaledImage, 0.9)
let base64String = imageData.base64EncodedStringWithOptions(.allZeros)
var cd = CoreDataUser(pstrContext: "this")
var params = "strUsername=" + cd.getUsername()
params = params + "&strPassword=" + cd.getPassword()
params = params + "&blbProfilePic=" + base64String

Earlier I referred you to https://stackoverflow.com/a/14803292/1271826 which shows one fairly limited and tactical solution to the presence of the + characters in the base-64 string. This consisted of replacing the + characters with %2B.
But I notice that you're also including user name and password in your parameters. This would suggest that you really want a more generalized solution, percent escaping all of the reserved characters. If you don't do that, this wouldn't work if the password included reserved characters (such as + or &).
When submitting parameters like this, you should percent escape the values. For example, you might define a character set to be only the "unreserved" characters (as defined by RFC 3986), namely the alphanumeric characters plus -, ., _, and ~:
extension NSCharacterSet {
class func URLQueryValueAllowedCharacterSet() -> NSCharacterSet {
return NSCharacterSet(charactersInString: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~")
}
}
Then you can do something like:
func percentEscapeString(string: String) -> String {
return string.stringByAddingPercentEncodingWithAllowedCharacters(.URLQueryValueAllowedCharacterSet())!
}
Or, if you like the old CFURLCreateStringByAddingPercentEscapes function, you can do:
func percentEscapeString(string: String) -> String {
return CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault,
string,
nil,
":/?#!$&'()*+,;=",
CFStringBuiltInEncodings.UTF8.rawValue) as! String;
}
The previous examples are more intuitive, but this latter approach is a little more efficient.
But regardless of how you implement it, you can now use this function to percent escape your strings:
let params = "strUsername=" + percentEscapeString(cd.getUsername()) +
"&strPassword=" + percentEscapeString(cd.getPassword()) +
"&blbProfilePic=" + percentEscapeString(base64String)

Related

SHA256 hash of camera image differs after it was saved to photo album

Xcode 10.2 and Swift 5
I pick an image from camera and calculate SHA256 hash of the image as string and save the image to iPhone photo album.
//Save photo to album. Photo comes from UIImagePickerController
UIImageWriteToSavedPhotosAlbum(self.photo, self, #selector(saveimage(_:didFinishSavingWithError:contextInfo:)), nil)
//Calculate hash
let imageData : Data = self.photo!.pngData()!
let imageHash : String = getImageHash(data: imageData)
func getImageHash(data : Data) -> String {
var hashBytes = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
data.withUnsafeBytes {
_ = CC_SHA256($0.baseAddress, CC_LONG(data.count), &hashBytes)
}
var hex = ""
for index in 0..<Int(CC_SHA256_DIGEST_LENGTH) {
hex += String(format: "%02x", hashBytes[index])
}
print(hex)
return hex
}
The code, however, gives me a different SHA256 of the image than after it has been saved. I transferred the photo to my Mac and checked hash by shasum -a 256 as well as uploaded it directly from iPhone to an online hash generator which gave me same hash as Mac did.
So, either my code to calculate the hash is wrong or something is being changed while storing the photo such as name or properties using UIImageWriteToSavedPhotosAlbum(...). Any ideas how to fix this?
I have done some further research on the matter and it appears that the data you have inside the UIImage is indeed different from the data that you open as a file, and I believe that is exactly what causes the problem. Note that I have shortened the base64 data, for readability.
Swift
func getImageHash(data: Data) -> String {
var hashBytes = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH)
data.withUnsafeBytes {
_ = CC_SHA256($0.baseaddress, CC_LONG(data.count), &hashBytes)
}
var hex = ""
for index in 0 ..< Int(CC_SHA256_DIGEST_LENGTH) {
hex += String(format: "%02x", hashbytes[index])
}
return hex
}
The function is fine, I'll use an example using the assets folder of iOS.
let imageData = UIImage(named: "Example")!.pngData()!
print(imageData.base64EncodedString())
// 'iVBORw0KGgoAAAANSUhEUgAAAG8AAACACAQAAACv3v+8AAAM82lD [...] gAAAABJRU5ErkJggg=='
let imageHash = getImageHash(data: imageData)
print(imageHash)
// '145036245c9f675963cc8de2147887f9feded5813b0539d2320d201d9ce63397'
The hash is calculated correctly as far as we are concerned right now. I was interested in the base64 data, so I can use that on other platforms too.
Python
import hashlib
import base64
img_d = base64.b64decode('iVBORw0KGgoAAAANSUhEUgAAAG8AAACACAQAAACv3v+8AAAM82lD [...] gAAAABJRU5ErkJggg==')
m = hashlib.sha256()
m.update(img_d)
m.digest().hex()
# '145036245c9f675963cc8de2147887f9feded5813b0539d2320d201d9ce63397'
So it appears the hash is calculated correctly, but only for the base64 data. So what is the difference? Let's use Python again to investigate. I used PIL to load the image directly.
import hashlib
from PIL import Image
i = Image.open('/path/to/file')
img_d = i.tobytes()
m = hashlib.sha256()
m.update(img_d)
m.digest().hex()
# 'f650b1b95a50c3a2b77da7a0825c3c01066385a12c8fe50b449ffc8c7249e370'
So now we have, indeed, a different hash. Let's try one last thing, openssl dgst in the terminal.
Terminal
echo 'iVBORw0KGgoAAAANSUhEUgAAAG8AAACACAQAAACv3v+8AAAM82lD [...] gAAAABJRU5ErkJggg==' | base64 --decode | openssl dgst -sha256
145036245c9f675963cc8de2147887f9feded5813b0539d2320d201d9ce63397
Alright, so indeed, openssl digest can calculate the hash over the base64 data and it does indeed match.
openssl dgst -sha256 /path/to/file
SHA256(/path/to/file)= d15c2d0c186a46b41c34a312eaf1c7e4b0f7a51bdb9c53a91dc361385ba23e64
So it's very interesting. The base64 data hashed has a 100% success rate, but loading it in Python and in the terminal, resulted in two different hashes. I do not know the reason why even they are different. I do believe what I mentioned earlier in the comments is correct, hashing a file will result in a different hash, as the meta data of the file is hashed too.
In order to provide a solution: try to hash your raw image data, instead of the file. This has the biggest chance of success. Hope this helps.

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

iOS: Prevent URL from being percent escaped automatically?

I'm fetching some JSON data using URLSession.dataTask. To filter the data, I'm using a parameter that limits by date. The API uses square brackets for this, e.g.
https://foo.com/api/items.json?find[date]=....
However, upon creating a URL with the string and requesting data from it, the square brackets are automatically escaped:
https://foo.com/api/items.json?find%5Bdate%5D=...
With the brackets escaped, the API fails to recognize the request.
I've tried explicitly removing the percent encoding to create a new URL, i.e.
let unsanitized = URL(string: url.absoluteString.removingPercentEncoding)
but the percent sanitization persists.
I've spent a lot of time searching for a solution but haven't had any success—any suggestions?
Per the spec for URI: "A host identified by an Internet Protocol literal address, version 6 [RFC3513] or later, is distinguished by enclosing the IP literal within square brackets ("[" and "]")." rfc3986.
Thus you can't use square brackets for your purpose without escaping them. Your server's REST service is at fault for not handling the escaped characters in the query params. (And I've had situations in recent past where I've had to ask my REST team to fix this sort of problem where they forgot to support escaped query parameter values).
Try use percentEncodedQueryItems of URLComponents instead queryItems.
This is my codes in my app.
let fullUrl = baseURL.appendingPathComponent(path)
guard var components = URLComponents(url: fullUrl, resolvingAgainstBaseURL: false) else {
fatalError("Unable to create URL components")
}
var params = self.parameters // <-- parameters = [String:String]
params["serviceKey"] = Const.OPEN_API_KEY // <-- percent encoded key
components.percentEncodedQueryItems = params.map { // <-- already percent encoded Query Items
URLQueryItem(name: String($0), value: String($1))
}
guard let url = components.url else {
fatalError("Could not get url")
}

Convert string to base64 byte array in swift and java give different value

Incase of android everything is working perfectly. I want to implement same feature in iOS too but getting different values. Please check the description with images below.
In Java/Android Case:
I tried to convert the string to base64 byte array in java like
byte[] data1 = Base64.decode(balance, Base64.DEFAULT);
Output:
In Swift3/iOS Case:
I tried to convert the string to base64 byte array in swift like
let data:Data = Data(base64Encoded: balance, options: NSData.Base64DecodingOptions(rawValue: 0))!
let data1:Array = (data.bytes)
Output:
Finally solved:
This is due to signed and unsigned integer, meaning unsigned vs signed (so 0 to 255 and -127 to 128). Here, we need to convert the UInt8 array to Int8 array and therefore the problem will be solved.
let intArray = data1.map { Int8(bitPattern: $0) }
In no case should you try to compare data on 2 systems the way you just did. That goes for all types but specially for raw data.
Raw data are NOT presentable without additional context which means any system that does present them may choose how to present them (raw data may represent some text in UTF8 or some ASCII, maybe jpeg image or png or raw RGB pixel data, it might be an audio sample or whatever). In your case one system is showing them as a list of signed 8bit integers while the other uses 8bit unsigned integers for the same thing. Another system might for instance show you a hex string which would look completely different.
As #Larme already mentioned these look the same as it is safe to assume that one system uses signed and the other unsigned values. So to convert from signed (Android) to unsigned (iOS) you need to convert negative values as unsigned = 256+signet so for instance -55 => 256 + (-55) = 201.
If you really need to compare data in your case it is the best to save them into some file as raw data. Then transfer that file to another system and compare native raw data to those in file to check there is really a difference.
EDIT (from comment):
Printing raw data as a string is a problem but there are a few ways. The thing is that many bytes are not printable as strings, may be whitespaces or some reserved codes but mostly the problem is that value of 0 means the end of string in most cases which may exist in the middle of your byte sequence.
So you already have 2 ways of printing byte by byte which is showing Int8 or Uint8 corresponding values. As described in comment converting directly to string may not work as easy as
let string = String(data: data, encoding: .utf8) // Will return nil for strange strings
One way of converting data to string may be to convert each byte into a corresponding character. Check this code:
let characterSequence = data.map { UnicodeScalar($0) } // Create an array of characters from bytes
let stringArray = characterSequence.map { String($0) } // Create an array of strings from array of characters
let myString = stringArray.reduce("", { $0 + $1 }) // Convert an array of strings to a single string
let myString2 = data.reduce("", { $0 + String(UnicodeScalar($1)) }) // Same thing in a single line
Then to test it I used:
let data = Data(bytes: Array(0...255)) // Generates with byte values of 0, 1, 2... up to 255
let myString2 = data.reduce("", { $0 + String(UnicodeScalar($1)) })
print(myString2)
The printing result is:
!"#$%&'()*+,-./0123456789:;<=>?#ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþ
Then another popular way is using a hex string. It can be displayed as:
let hexString = data.reduce("", { $0 + String(format: "%02hhx",$1) })
print(hexString)
And with the same data as before the result is:
000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff
I hope this is enough but in general you could do pretty much anything with array of bytes and show them. For instance you could create an image treating bytes as RGB 8-bit per component if it would make sense. It might sound silly but if you are looking for some patterns it might be quite a witty solution.

Body of NSMutableRequest consisting of base64encodeString gets truncated while sending as a string

Recently, I am doing a NSUrlSession task to upload multiple images to the backend . I appended all the images in .png representation in an array. I converted the whole array into a base64 String format and tried to send the whole body as a string.
Conversion of imageArray to String -
let imageArrayData: NSData = NSKeyedArchiver.archivedDataWithRootObject(imageArray)
let imageArrayBase64String = imageArrayData.base64EncodedStringWithOptions(NSDataBase64EncodingOptions(rawValue: 0))
Here printing the Array ,I get -
(String) imageArrayBase64String = "YnBsaXN0MDDUAQIDBAUIIiNUJHRvcFgkb2JqZWN0c1gkdmVyc2lvblkkYXJjaGl2ZXLRBgdUcm9vdIABpQkKEBQcVSRudWxs0gsMDQ5WJGNsYXNzWk5TLm9iamVjdHOABKEPgALSEQsSE1dOUy5kYXRhTxIBe8QxiVBORw0KGgoAAAANSUhEUgAAEMAAAAsgCAIAAABnC3DjAAABGWlDQ1BJQ0MgUHJvZmlsZQAAKJFjYGBSSCwoyGESYGDIzSspCnJ3UoiIjFJgf8DAzCDDwMUgwGCUmFxc4BgQ4MMABDAaFXy7xsAIoi/rgszClMcLuFJSi5OB9B8gzk4uKCphYGDMALKVy0sKQOweIFskKRvMXgBiFwEdCGRvAbHTIewTYDUQ9h2wmpAgZyD7A5DNlwRmM4Hs4kuHsAVAbKi9ICDomJKflKoA8r2GoaWlhSaJfiAISlIrSkC0c35BZVFmekaJgiMwpFIVPPOS9XQUjAwMzRgYQOEOUf05EByejGJnEGIIgBCbI8HA4L+UgYHlD0LMpJeBYYEOAwP/VISYmiEDg4A+A8O+OcmlRWVQYxiZjBkYCPEBNDZKYi1QenUAAAAcaURPVAAAAAIAAAAAAAAFkAAAACgAAAWQAAAFkADAfM/Go8QlAABAAElEQVR4AXS8BXQcV7b3K0Pm3jt3IIlBlmzLFjMzMzMztVhqUasZ1ChmZibLsixmmSmGkB1yzIyhgZubTGJ//+rK6GW97721/muvffbZdaqqq7vqVNX+tUJijjZEKTBIztNNyNZKoeol5WlB8dnqibmaKfk68BOy1JNztVKpOkk5mgiSItPkPjLVU/K14rIOpxboJlO1YzNVE3I0IDJzayiMhmBclhoisEhOK9JHPhZPytNILdCiFGqn07STqKrJVI2UfM24rEPx2YcRzKDppRZoYxWkknM1IGxPSp52cp56epFOar5mClUjmYoN1kor0kVyWpFOejHG18E4KQXqqYUaacUaGSVa2XSdPJY+pUAtk6aVxdB"
Creating the body -
let body = "task=doNotification&select_category=\(selectCategory!)&select_type=\(selectType!)&class=\(classid!)&repliable=\(repliable)&select_students=\(selectedStudents)&select_group=\(selectGroup!)&title=\(SbjctOrTtlTxtFld.text!)&text=\(textVieww.text!)&image=\(imageArrayBase64String)&date=\(dateText!)&time=\(timeText!)"
Here printing the body,I get -
(String) body = "task=doNotification&select_category=exams&select_type=check&class=2&repliable=1&select_students=(\n 26,\n 25\n)&select_group=11&title=self&text=Adam <IMG_0002>&image=YnBsaXN0MDDUAQIDBAUIIiNUJHRvcFgkb2JqZWN0c1gkdmVyc2lvblkkYXJjaGl2ZXLRBgdUcm9vdIABpQkKEBQcVSRudWxs0gsMDQ5WJGNsYXNzWk5TLm9iamVjdHOABKEPgALSEQsSE1dOUy5kYXRhTxIBe8QxiVBORw0KGgoAAAANSUhEUgAAEMAAAAsgCAIAAABnC3DjAAABGWlDQ1BJQ0MgUHJvZmlsZQAAKJFjYGBSSCwoyGESYGDIzSspCnJ3UoiIjFJgf8DAzCDDwMUgwGCUmFxc4BgQ4MMABDAaFXy7xsAIoi/rgszClMcLuFJSi5OB9B8gzk4uKCphYGDMALKVy0sKQOweIFskKRvMXgBiFwEdCGRvAbHTIewTYDUQ9h2wmpAgZyD7A5DNlwRmM4Hs4kuHsAVAbKi9ICDomJKflKoA8r2GoaWlhSaJfiAISlIrSkC0c35BZVFmekaJgiMwpFIVPPOS9XQUjAwMzRgYQOEOUf05EByejGJnEGIIgBCbI8HA4L+UgYHlD0LMpJeBYYEOAwP/VISYmiEDg4A+A8O+OcmlRWVQYxiZjBkYCPEBNDZKYi1QenUAAAAcaURPVAAAAAIAAAAAAAAFkAAAACgAAAWQAAAFkADAfM/Go8QlAABAAElEQVR4AXS8BXQcV7b3K0Pm3jt3IIlBlmzLFjMzMzMztVhqUasZ1ChmZibLsixmmSmGkB1yzIyhgZubTGJ//+rK6GW97721/muvffbZdaqqq7vqVNX+tUJijjZEKTBIztNNyNZKoeol5WlB8dnqibmaKfk68BOy1JNztVKpOkk5mgiSItPkPjLVU/K14rIOpxboJlO1YzN"
As you can notice that the body don't even pass the full encoded string of the array and it also is not passing the last 2 parameters i.e date & time.
So why is it so ?
Calling the web service -
func sendAPIRequest(urlpath:NSString,body: NSString , completion: (result: NSMutableDictionary, error: AnyObject?)-> Void ) -> Void
{
let url:NSURL = NSURL(string: urlpath as String)!
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "POST"
let bodydata = body.dataUsingEncoding(NSUTF8StringEncoding)
request.HTTPBody = bodydata
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let task = session.dataTaskWithRequest(request) { (data, response, error) -> Void in
do
{
let resultdic = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as? NSMutableDictionary
//print(resultdic!)
completion(result: resultdic!,error: nil)
}
catch
{
print("error")
}
}
task.resume()
A couple of observations:
It looks like you're looking at body in the debugger. That will truncate the string (though usually it shows ellipses at the end to indicate that it was truncated). I'd suggest you try print(body) and see if you see the whole string there.
Your selectStudents is an NSArray and when you do string interpolation on that, it includes a newline character in it. The net effect is that this x-www-form-urlencoded request is not well-formed and will likely be rejected by the server. There are a couple of ways of resolving with this
If your server is really expecting a string of the form select_students=(\n 26,\n 25\n) (which I'd be very surprised if that's really what you want), you'd percent escape that string:
let selectedStudentsString = "selectedStudents=\(selectedStudents)".stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryParameterAllowedCharacterSet())!
and that will yield:
selectedStudents=(%0A%20%20%20%20foo,%0A%20%20%20%20bar%0A)
where URLQueryParameterAllowedCharacterSet is defined as:
extension NSCharacterSet {
/// Returns the character set for characters allowed in the individual parameters within a query URL component.
///
/// The query component of a URL is the component immediately following a question mark (?).
/// For example, in the URL `http://www.example.com/index.php?key1=value1#jumpLink`, the query
/// component is `key1=value1`. The individual parameters of that query would be the key `key1`
/// and its associated value `value1`.
///
/// According to RFC 3986, the set of unreserved characters includes
///
/// `ALPHA / DIGIT / "-" / "." / "_" / "~"`
///
/// In section 3.4 of the RFC, it further recommends adding `/` and `?` to the list of unescaped characters
/// for the sake of compatibility with some erroneous implementations, so this routine also allows those
/// to pass unescaped.
class func URLQueryParameterAllowedCharacterSet() -> Self {
return self.init(charactersInString: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~/?")
}
}
See https://stackoverflow.com/a/35912606/1271826 for alternative percent-escaping options. But avoid using stringByAddingPercentEncodingWithAllowedCharacters with any of the existing character sets (e.g. URLQueryAllowedCharacterSet), because they all allow reserved characters to pass unescaped.
If you really want this received as an array in an x-www-form-urlencoded request by your server code, you'd probably want:
var selectedStudentsArray = [String]()
for index in 0 ..< selectedStudents.count {
selectedStudentsArray.append("selectedStudents[]=\(selectedStudents[index])")
}
let selectedStudentsString = selectedStudentsArray.joinWithSeparator("&")
and that will yield:
selectedStudents[]=26&selectedStudents[]=25
That's the right way to send an array of values in an x-www-form-urlencoded request.
Your text value includes reserved characters, too. You should be percent escaping this (and anything else that might include characters outside of a-z, A-Z, 0-9, and -, ., _, ~, /, and ?). Again, when doing x-www-form-urlencoded request like this, all reserved characters must be percent escaped.
You are taking your array of images and building a NSKeyedArchiver. This is going to be one big, opaque, blob object as far as your web service is concerned. If your intent is to treat this as a single blob on the server, that's fine, but if you were hoping to then extract the images out of that, you're making your life unnecessarily complicated.
You can either:
Base-64 encode the images separately and then use the same format I suggested for selectStudents, e.g.
image[]=Bsa...JJH&image[]=5YJ...mVj
Note, you should use the original contents of the data (e.g. get the NSData directly from the original file/asset, avoiding UIImage altogether) or, if you really need to round-trip it through a UIImage (which I'd discourage if you can prevent it), then use UIImagePNGRepresentation or UIImageJPEGRepresentation to create a new NSData from the UIImage.
You might consider using a multipart/form-data request and then the individual images (again, with the aforementioned comment about getting the NSData notwithstanding) can be stored individually. If you do proper multipart/form-data request, it avoids needing to do any percent-escaping anywhere, too, avoids base-64 encoding (which makes the transmission 33% larger than it needs to be), etc.
Bottom line, properly creating these sorts of requests is very, very complicated, and unless you really want to unravel all of these idiosyncrasies, I'd really encourage you to consider Alamofire, which gets you out of these weeds. And if you truly feel compelled to reinvent the wheel, I'd suggest you research these topics individually, as this is far too broad for a single question. Each of my points above probably warrants a separate question.

Resources