I have been tasked with building the API for an existing mobile app. App is sending multipart data and files in the same PUT request. As an example, there is /api/employee/personal-info PUT endpoint (notice no ID in the URI) which is a multipart - JSON data and 2 images.
PHP does not have native support for PUT, it doesn't nicely put everything in $_FILES and $_POST so I have to decode the input manually.
I first have to do file_get_contents("php://input") which gives me the raw data. I have to use regex to extract the boundary string, then I need to split the input into blocks using that boundary (and regex again) and then decide whether the block is JSON or a file, by looking at Content-Disposition on each of the blocks. If it is a file, I have to regex out the filename and extension, and populate $_FILES array manually.
Json bloks have those headers (inside the body, just before the actual data)
Content-Type: text/plain; charset=utf-8
Content-Disposition: form-data; name=model
and file blocks only have this:
Content-Disposition: form-data; name=file; filename=IMG_20180208_1.jpg; filename*=utf-8’’IMG_20180208_1.jpg
So the Content-Disposition of the whole input is multipart/form-data but then each of the blocks have their own headers, depending whether it is a file or JSON data.
Is this really the only way to do it in PHP?
Should the PUT endpoints NOT be multipart as a rule when it comes to PHP?
Am I missing something?
Related
In my iOS app, I'm building an NSData to use as the body for an NSMutableURLRequest so that I can upload multiple files in one HTTP POST.
The contents of my post body look like this (with the file data removed and just replaced with the byte count):
multipart/form-data; charset=utf-8; boundary=0xKhTmLbOuNdArY
--0xKhTmLbOuNdArY
Content-Disposition: form-data; name="email"
myemailaddress#gmail.com
--0xKhTmLbOuNdArY
Content-Disposition: form-data; name="sqlite"; filename="backup.MyApp.v1.1.3-to-v1.1.3.1443578420.sqlite"
Content-Type: application/octet-stream
// ... data length: 880640
--0xKhTmLbOuNdArY--
Content-Disposition: form-data; name="sqliteshm"; filename="backup.MyApp.v1.1.3-to-v1.1.3.1443578420.sqlite-shm"
Content-Type: application/octet-stream
// ... data length: 32768
--0xKhTmLbOuNdArY--
Content-Disposition: form-data; name="sqlitewal"; filename="backup.MyApp.v1.1.3-to-v1.1.3.1443578420.sqlite-wal"
Content-Type: application/octet-stream
// ... data length: 3901672
--0xKhTmLbOuNdArY--
However, on the PHP side when I receive this post, I'm only seeing the first of the three files. If I put the one named "sqlite" first, then on the PHP side, I only see the "sqlite" file. If I put the one named "sqliteshm" first, then I only see the "sqliteshm" file in the $_FILES array.
array (
'sqliteshm' =>
array (
'name' => 'backup.MyApp.v1.1.3-to-v1.1.3.1443578420.sqlite-shm',
'type' => 'application/octet-stream',
'tmp_name' => '/private/var/tmp/phpk1wyWb',
'error' => 0,
'size' => 32768,
),
)
The file size matches up, regardless of which one I put first, but only the first file ever shows up on the PHP side.
Do I need to do something special in order to receive multiple files on the PHP side?
Or am I sending the multiple files incorrect from iOS?
In short, your request is not valid.
In more detail, notice the difference between --0xKhTmLbOuNdArY-- and --0xKhTmLbOuNdArY. The separator that uses two dashes at the end -- is a terminator. As a result, your request body isn't parsed beyond the first terminator, which happens to be the right after the first file. There should only be one terminator - at the very end of your request.
Generally, you shouldn't reinvent the wheel when it comes to composing multipart form data requests. Just use a proven and time tested library like AFNetworking.
I wanted to upload a file(any format) to an api. If I upload the file using multipart/form-data then the file gets uploaded.
I wanted to upload the file using application/json as the Content-type in the header.
Could you'll tell me if this method is possible/allowed?
Which are Content-type besides multipart/form-data which supports file upload?
Is there a single standard for content type which can be used for get, put, post etc.
Thanks in Advance.
Only multipart/form-data can trigger the population of the PHP $_FILES global, otherwise you're going to need to nest your attachment data in your API request if using something like JSON or XML (base64_encoding comes to mind)
You could also process a PUT request - http://www.php.net/manual/en/features.file-upload.put-method.php
RFC 4627:
The MIME media type for JSON text is application/json
When a file is uploaded via POST, the form data is separated out from the file(s) via the $_POST and $_FILES variables (respectively). On the other hand, when a file is uploaded via PUT, the response must be retrieved from a single source (php://input). Unfortunately, when a file is involved, php://input seems to contain multiple headers, which appear to be divided by a key of some kind (--6OJvloM5owOQsn2b3APr-Ad9dDLvRqBxm in this case).
--6OJvloM5owOQsn2b3APr-Ad9dDLvRqBxm
Content-Disposition: form-data; name="file"; filename="image.jpg"
Content-Type: application/octet-stream
Content-Transfer-Encoding: binary
<<<BINARY DATA>>>
--6OJvloM5owOQsn2b3APr-Ad9dDLvRqBxm
Content-Disposition: form-data; name="description"
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
<<<FILE DESCRIPTION>>>
--6OJvloM5owOQsn2b3APr-Ad9dDLvRqBxm--
Short of iterating over the entire response and trying to pick out the different headers, is there a way to separate the files from the form data?
Note: I'm using a well-known 3rd-party application to make the API requests, so it's unlikely that the problem resides in the requests themselves.
It seems that you are trying to do to much in one PUT request. However, if you really need to handle the raw data, you should be able to parse it as a MIME string using a library such as this one: http://pear.php.net/package/Mail_mimeDecode
this is what i have in process.php
parse_str(file_get_contents("php://input"),$upload_data);
if (isset($upload_data)) {
print_r($upload_data);
exit;
}
this is how i query to server using curl.
weldan#prindu:~$ curl -X PUT -H "X-TOKEN: test123" -F filedata=#/home/weldan/Pictures/Cool-Pictures1.jpg http://host.tld/process.php
Array
(
[------------------------------22031b6e799c
Content-Disposition:_form-data;_name] => "filedata"; filename="Cool-Pictures1.jpg"
Content-Type: image/jpeg
���
)
so that how i know there is uploaded file there.
current problem is, how do I process this file like $_FILES variable?
open for other way to achieve this too.
Thanks
The $_FILES array being populated on PUT requests. That's because a PUT request will specify the file name in the url and the file content in the body. Nothing more.
You'll have to use php://input as you already suggested.
upload.php
<?php
/* PUT data goes to php://input */
echo file_get_contents("php://input");
Then use the following curl command line:
curl --upload -H "X-TOKEN: test123" a.txt http://localhost/upload.php
Update after comments: Using the --upload option should fit your needs. In difference to -X PUT together with -F, the data will be send raw and get not multipart/form-data encoded.. One of the hidden treasures of curl ;)
Short answer
The only way in your situation is to parse raw request manually (look at this gist)
Thoughts
PUT request could be used to upload one file only (not from html upload form, but using custom request). On the server you just get raw file content and writes it into some file.
Use PUT without files (application/x-www-form-urlencoded) and parse raw request using parse_str(file_get_contents("php://input"), $_PUT);
Use POST to upload files (multipart/form-data).
I have an xml file saved and this is what I want to do,
when some one click the link that contains an xml file, I want them to ask the question if they want to save or open the file.
I know there is a way to do this using http header to send and trick your brower into using the application/octet-stream mime type, but I forgot how it was done. ..
it gives me this error
The XML page cannot be displayed
Cannot view XML input using style sheet. Please correct the error and then click the Refresh button, or try again later.
--------------------------------------------------------------------------------
XML document must have a top level element. Error processing resource 'file:///C:/Documents and
Try adding Content-Disposition header:
Content-Disposition: attachment; filename="yourfile.xml"
And of course, the XML Content-Type header also:
Content-type: text/xml