1

I've got a pretty particular issue today. I've created a multipart/form-data header in iOS, which is then sent to a PHP script on my web server. It works fine with only strings as data, but when I attempt to append an image to the header, it returns 403. Here's the function that attempts to connect:

func uploadImage(image: UIImage) {
    var url: NSURL = NSURL(string: "https://example.com/uploadPicture.php")!
    var request: NSMutableURLRequest = NSMutableURLRequest(URL: url)

    request.HTTPMethod = "POST"

    var boundary = NSString(format: "dsfghjkergsalkj")
    var contentType = NSString(format: "multipart/form-data; boundary=%@", boundary)
    request.addValue(contentType, forHTTPHeaderField: "Content-Type")
    request.timeoutInterval = 60

    var body = NSMutableData()

    body.appendData(NSString(format: "--%@\r\n", boundary).dataUsingEncoding(NSUTF8StringEncoding)!)
    body.appendData(NSString(format: "Content-Disposition: form-data; name=\"image-name\"\r\n\r\n").dataUsingEncoding(NSUTF8StringEncoding)!)
    body.appendData("yay".dataUsingEncoding(NSUTF8StringEncoding)!)

    body.appendData(NSString(format: "--%@\r\n", boundary).dataUsingEncoding(NSUTF8StringEncoding)!)
    body.appendData(NSString(format: "Content-Type: image/png\r\n").dataUsingEncoding(NSUTF8StringEncoding)!)
    body.appendData(NSString(format: "Content-Transfer-Encoding: binary\r\n").dataUsingEncoding(NSUTF8StringEncoding)!)
    body.appendData(NSString(format: "Content-Disposition: form-data; name=\"profile-img\"; filename=\"profile.png\"\r\n\r\n").dataUsingEncoding(NSUTF8StringEncoding)!)
    body.appendData(NSData(data: UIImagePNGRepresentation(image)))

    //println(NSData(data: UIImagePNGRepresentation(image)))

    body.appendData(NSString(format: "--%@\r\n", boundary).dataUsingEncoding(NSUTF8StringEncoding)!)

    request.HTTPBody = body

    let task = NSURLSession(configuration: .defaultSessionConfiguration(), delegate: nil, delegateQueue: NSOperationQueue.mainQueue()).dataTaskWithRequest(request) {
        data, response, error in

        if (error != nil) {
            println("Error Submitting Request: \(error)")
            return
        }

        var err: NSError?
        var userData: [String: String]!
        userData = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &err) as? [String: String]

        if(userData != nil) {
            println(data)
            println(userData)
        } else {
            println(data)
            println("No data recieved.")
        }
    }

    task.resume()

}

It should return a JSON associative array that either has 'true' or 'false' under the key 'data', as shown here.

if(isset($_FILES['profile-img'])) {
    $arr = ['data': 'true'];
} else {
    $arr = ['data': 'false'];
}

echo json_encode($arr);

Instead it returns a long list of hex bytes, which is the 403 forbidden page.

Commenting out body.appendData(NSData(data: UIImagePNGRepresentation(image))) allows the connection to work.

What might be causing this? I've tried everything I could think of.

EDIT: Here's the full PHP page, not much.

<?php

error_reporting(E_ALL);
ini_set('display errors', 1);

if(isset($_FILES['profile-img'])) {
    $arr = ['data': 'true'];
} else {
    $arr = ['data': 'false'];
}

echo json_encode($arr);

/*$uploaddir = './uploads/';
$file = basename($_FILES['profile_img']['name']);
$uploadfile = $uploaddir . $file;

if (move_uploaded_file($_FILES['profile_img']['tmp_name'], $uploadfile)) {
    $arr = ['data': 'true'];
} else {
    $arr = ['data': 'false'];
}

echo json_encode($arr);*/

?>
Jeffrey
  • 1,271
  • 2
  • 15
  • 31

1 Answers1

0

You are likely not constructing a MIME multipart submission that PHP is decoding correctly. The spec for these is here.

  1. Every header line must have a CRLF sequence after it. You can accomplish this by ending your header strings with \r\n. There should be an extra CRLF sequence after the final header line and before the data in each part.
  2. Your binary part (the file) has no encoding specified. By default the spec assumes "7BIT" encoding, which raw binary is not a subset of. Try adding the header Content-Transfer-Encoding: binary to this part.
  3. Not necessarily a problem, but the boundary string is typically a longer string including a bunch of random characters to avoid conflicting with any actual data. Your short sequence of asterisks is not especially strong for this.
Ben Zotto
  • 70,108
  • 23
  • 141
  • 204
  • I'd updated the question to accommodate your answer - it hasn't worked, but I'm also not sure I did it correctly. – Jeffrey Feb 02 '15 at 03:13
  • @jkaufman: You want the `Content-Transfer-Encoding: binary` to be for just the "part" that contains the binary file. You also want 2 CRLFs after the last header in *each* part. (In the current code, this would be the `Content-Disposition` headers) – Ben Zotto Feb 02 '15 at 03:19
  • @jkaufman: I (and anyone else) also can't speak to the actual behavior of the PHP script because you haven't posted it, but I suspect that it's the form data that's the issue. – Ben Zotto Feb 02 '15 at 03:20
  • Thanks a bunch, it works now. Although, the PHP doesn't seem to return anything. Did I do something wrong there? – Jeffrey Feb 02 '15 at 03:53
  • @jkaufman I'm not sure; I'm not a PHP guy. You may want to ask that as a separate question and tag it appropriately. – Ben Zotto Feb 02 '15 at 20:20