4

EDIT: Ok, I just set the content-type header to multipart/form-data with no difference. My original question is below:


This is my first question on stack overflow, I hope I'm doing it right.

I am only learning Objective-C, having recently completed the online version of the Stanford course. I know virtually nothing about php and html. The php script and the html I am using are mostly copied off a tutorial. Obj-C makes more sense to me.

The problem:

I have a php script. It uploads an image file. It works properly when called from an html file in the same folder on the server. I am trying to get the same script to work when it is called from my obj-c. It seems to run, it returns 200, the obj-c does call the php, but no file appears in the online folder.

There seems to be very little about this on the web since it was only introduced in ios7. No examples I have found deal with file upload, they all deal with download and merely say that upload is similar. What I have done seems to satisfy any tutorials I have found.

What I know is:

  • the php works, and the file is uploaded, when it is called form the html file on the server
  • the obj-c is definitely calling the php script (I wrote some logging (using file_put_contents) into the php which confirms that the script is being called when I run the obj-c)
  • the obj-c is pretty much definitely uploading the image file (if I use a delegate method in the obj-c it shows upload progress)
  • but the php script is not receiving the file (the logging I wrote into the php shows that $_FILES has no value, when called from the obj-c. When called from html it works as expected)
  • I just edited the php to log the headers it receives, and it does get the Content-Length of the image file.

Things that may be important:

  • I am not adding any html headers, no tutorials that I have seen say that I have to (with NSURLSessionUploadTask), I assume NSURLSessionUploadTask sorts this out for you? Or is this my problem?
  • the [response description] returns a 200, quote: { URL: (the PHP script URL) } { status code: 200, headers { Connection = "Keep-Alive"; "Content-Type" = "text/html"; Date = "Thu, 16 Jan 2014 19:58:10 GMT"; "Keep-Alive" = "timeout=5, max=100"; Server = Apache; "Transfer-Encoding" = Identity; } }
  • the html specifies enctype="multipart/form-data", perhaps this has to be worked into my obj-c somewhere?
  • I am only running it off the simulator so far
  • any help will be immensely appreciated! thanks :)
  • edit, I just edited the below code to show [request setHTTPMethod:@"POST"] instead of [request setHTTPMethod:@"PUSH"] that I originally had, but it makes no change.

Here is the objective C

- (void) uploadFile: (NSURL*) localURL toRemoteURL: (NSURL*) phpScriptURL
{
    NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
    NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration: defaultConfigObject delegate: nil delegateQueue: [NSOperationQueue mainQueue]];
    NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:phpScriptURL];
    [request setHTTPMethod:@"POST"];
    NSURLSessionUploadTask* uploadTask = [defaultSession uploadTaskWithRequest:request fromFile:localURL completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){
        if (error == nil)
        {
            NSLog(@"NSURLresponse =%@",  [response description]);
            // do something !!!
        } else
        {
            //handle error
        }
        [defaultSession invalidateAndCancel];
    }];

    self.imageView.image = [UIImage imageWithContentsOfFile:localURL.path]; //to confirm localURL is correct

    [uploadTask resume];
}

and here is the PHP script that is on the server

<?php

        $file = 'log.txt';
        $current = file_get_contents($file);
        $current .= $_FILES["file"]["name"]." is being uploaded. ";  //should write the name of the file to log.txt 
        file_put_contents($file, $current);


    ini_set('display_errors',1);
    error_reporting(E_ALL);

   $allowedExts = array("gif", "jpeg", "jpg", "png");
   $temp = explode(".", $_FILES["file"]["name"]);
   $extension = end($temp);   

    if ((($_FILES["file"]["type"] == "image/gif")
    || ($_FILES["file"]["type"] == "image/jpeg")
    || ($_FILES["file"]["type"] == "image/jpg")
    || ($_FILES["file"]["type"] == "image/pjpeg")
    || ($_FILES["file"]["type"] == "image/x-png")
    || ($_FILES["file"]["type"] == "image/png"))
    //&& ($_FILES["file"]["size"] < 100000) //commented out for error checking
    && in_array($extension, $allowedExts))
      {
      if ($_FILES["file"]["error"] > 0)
        {
        echo "Return Code: " . $_FILES["file"]["error"] . "<br>";
        }
      else
        {
            echo "Upload: " . $_FILES["file"]["name"] . "<br>";
            echo "Type: " . $_FILES["file"]["type"] . "<br>";
            echo "Size: " . ($_FILES["file"]["size"] / 1024) . " kB<br>";
            echo "Temp file: " . $_FILES["file"]["tmp_name"] . "<br>";

            if (file_exists("upload/" . $_FILES["file"]["name"]))
              {
              echo $_FILES["file"]["name"] . " already exists. ";
              }
            else
              {
              if (move_uploaded_file($_FILES["file"]["tmp_name"],
              "upload/" . $_FILES["file"]["name"]))
              {
                echo "Stored in: " . "upload/" . $_FILES["file"]["name"];
              }
              else
              {
                echo "Error saving to: " . "upload/" . $_FILES["file"]["name"];
              }

            }
        }
      }
    else
      {
      echo "Invalid file";
      }

?>

and here is the html file that works as expected when calling the same script

<html>
<body>

    <form action="ios_upload.php" method="post"
    enctype="multipart/form-data">
    <label for="file">Filename:</label>
    <input type="file" name="file" id="file"><br>
    <input type="submit" name="submit" value="Submit">
    </form>

</body>

narco
  • 830
  • 8
  • 21
  • Have you checked out http://www.php.net/move_uploaded_file ? Recieving a file is pretty straight forward with php, combining the $_FILES super global and move_uploaded_file (instead of the file stream you are doing). And also, where do you specify that the input name is "file" in your objective c? – Martin Sommervold Jan 16 '14 at 22:52
  • 1
    Thanks for your reply :) Yes, I had a look at that. I have also tried this, with the same results (sorry I'm not sure how to format code in a reply): $file = $_FILES["file"]["tmp_name"]; $newfile = "upload/" . $_FILES["file"]["name"]; if (!copy($file, $newfile)) { echo "failed to copy $file...\n"; } And I'm not sure what you mean by your second question? – narco Jan 16 '14 at 23:09
  • @Martin Sommervold I was just looking at your comment again, as I've been working on trying to figure out this problem for the best part of a week (and no one else has replied). I may have misunderstood your comment. Are you saying that my php is wrong? Is my obj-c not uploading to $_FILES? Thanks in advance – narco Jan 22 '14 at 20:19
  • @Martin Sommervold ah, over a week later and I've finally gotcha about the "file" comment. Thanks. I ended up writing the code to do everything a different way, so I can't be sure that your solution completely solved the issues, hence why I haven't chosen it as the correct answer. But thanks – narco Jan 24 '14 at 17:57
  • You beat me to it by 37 seconds :) no, the $_FILES super global will always receive the upload, so that's the right place to look. However my question was about where in your obj-c you name your input. For instance, you could do and you would receive that file as $_FILES['holybatman']. I just couldn't spot a 'holybatman' in your obj-c thats all, and was wondering if you just tried to grab the wrong key :) do a var_dump($_FILES); to see what your files post look like :) glad you solved it! – Martin Sommervold Jan 24 '14 at 18:01
  • @narco I am having similar issues. Could you please provide a brief description of your solution to this problem? Thanks! – gberginc Feb 19 '14 at 04:57
  • My solution was mostly based on two parts. Martin Sommervolds answer that my php wasn't correct, and then I had to learn how to build an http POST properly, which I learnt mostly from this article: http://www.faqs.org/rfcs/rfc1867.html You need to make a boundary (described in that article), and then piece the POST request together line by line. The thing I found is that everything has to be perfect in order to work, so it can take a bit of tweaking. I set up the PHP to create a JSON array of results, and returned it a various steps to figure out what was going on. I hope that helps. – narco Feb 21 '14 at 22:09
  • That article isn't correct for uploading multiple files, I just found out. It is actually done using only one boundary. But the article is correct for single files. – narco Mar 14 '14 at 23:39
  • I'm having a similar problem in that `NSURLSessionUploadTask` doesn't seem to be passing data correctly to the server so `$_FILES` shows up blank. Surely fully constructing the POST body piece by piece is (supposed to be) made redundant by the existence of `[NSURLSessionUploadTask uploadTaskWithRequest:fromFile:completionHandler:]`? – GTF Sep 30 '14 at 15:01
  • Have anyone solved it??? I am encountering this problem and trying to solve it since last week.Please help me, post working code here. Thanks – va05 Dec 10 '14 at 07:08

1 Answers1

0

I just answered this same question over here: https://stackoverflow.com/a/28269901/4518324

Basically, the file is uploaded to the server in the request body as binary.

To save that file in PHP you can simply get the request body and save it to file.

Your code should look something like this:

Objective-C Code:

- (void) uploadFile: (NSURL*) localURL toRemoteURL: (NSURL*) phpScriptURL
{
    // Create the Request
     NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:phpScriptURL];
    [request setHTTPMethod:@"POST"];

    // Configure the NSURL Session
     NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"com.upload"];
    [sessionConfig setHTTPMaximumConnectionsPerHost: 1];

     NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration:defaultConfigObject delegate:self delegateQueue:nil];

     NSURLSessionUploadTask* uploadTask = [defaultSession uploadTaskWithRequest:request fromFile:localURL completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){
         if (error == nil)
         {
              NSLog(@"NSURLresponse =%@",  [response description]);
              // do something !!!
         } else
         {
             //handle error
         }
         [defaultSession invalidateAndCancel];
     }];

      self.imageView.image = [UIImage imageWithContentsOfFile:localURL.path]; //to confirm localURL is correct

     [uploadTask resume];
}

PHP Code:

<?php
    // Get the Request body
    $request_body = @file_get_contents('php://input');

    // Get some information on the file
    $file_info = new finfo(FILEINFO_MIME);

    // Extract the mime type
    $mime_type = $file_info->buffer($request_body);

    // Logic to deal with the type returned
    switch($mime_type) 
    {
        case "image/gif; charset=binary":
            // Create filepath
             $file = "upload/image.gif";

            // Write the request body to file
            file_put_contents($file, $request_body);

            break;

        case "image/png; charset=binary":
            // Create filepath
             $file = "upload/image.png";

            // Write the request body to file
            file_put_contents($file, $request_body);

            break;

        default:
            // Handle wrong file type here
            echo $mime_type;
    }
?>

I wrote a code example of recording audio and uploading it to a server here: https://github.com/gingofthesouth/Audio-Recording-Playback-and-Upload

It shows code from saving on the iOS device, to uploading and saving to the server.

I hope that helps.

Community
  • 1
  • 1
  • Been having trouble myself with uploadTaskWithRequest:fromFile:completion: your sample code actually throws an exception. the completion block is not supported if you use a background NSURLSession. I have been working on getting uploadTaskWithRequest to function strictly in the foreground - and the headers go out, but not the file. Starting to suspect the method with the completion block is just broken – Todd Dec 10 '15 at 17:41