I'm trying to upload a file to a remote server using php's fsockopen. If my request looks like:
$httpContent = [
"POST /index.php HTTP/1.1",
"Host: The IP address of my remote server",
"Connection: Close",
"User-Agent: My client"
];
the server responses with 200 OK
. However if I add
"Content-Type: multipart/mixed; boundary=".md5('myBoundary'),
"--".md5('myBoundary'),
"Content-Type: text/plain",
"value",
"--".md5('myBoundary')."--"
The server returns "400 Bad Request". Everything seems fine to me, but somehow it doesn't work. What am I doing wrong?
Any help is greatly appreciated!
Edit (@Rei advised to post a dump of the request):
this is what echoes in client's terminal
POST /index.php HTTP/1.1
Host: The IP address of my remote server
Connection: Close
User-Agent: My client
Content-Type: multipart/mixed; boundary=be0850c82dd4983ddc49a51a797dce49
--be0850c82dd4983ddc49a51a797dce49
Content-Type: text/plain
value
--be0850c82dd4983ddc49a51a797dce49--
And this is what the server gets (caught with tcpdump):
POST /index.php HTTP/1.1
Host: The IP address of my remote server
Connection: Close
User-Agent: My client
Content-Type: multipart/mixed; boundary=be0850c82dd4983ddc49a51a797dce49
--be0850c82dd4983ddc49a51a797dce49
Content-Type: text/plain
value
--be0850c82dd4983ddc49a51a797dce49--
I personally don't see any difference between the two and also don't see anything wrong with the request. Yet, the server returns "400 Bad Request".
*Note: The "The IP address of my remote server" is a real IP address, but I'm not posting it due to security concerns.
**Note: Didn't mention that the PHP script is run in a CLI environment and is not used as a web-application back-end (the requests are NOT prepared by a web browser).
Just as I suspected, the HTTP request you're sending does not follow HTTP specification. See RFC7230 Section 3. The problem: there are missing CRLFs.
There should be an extra CRLF after the last HTTP header, and one after the last MIME header, too.
POST /index.php HTTP/1.1
Host: The IP address of my remote server
Connection: Close
User-Agent: My client
Content-Type: multipart/mixed; boundary=be0850c82dd4983ddc49a51a797dce49
--be0850c82dd4983ddc49a51a797dce49
Content-Type: text/plain
value
--be0850c82dd4983ddc49a51a797dce49--
That will fix the "Bad Request" problem.
Although the cause of the problem is not the Content-Type: multipart/mixed
header, you should know that it is deprecated.
Use Content-Type: multipart/form-data
instead.
See related answer here and RFC7578.
The content type multipart/form-data
can be used to send values and files.
The difference is only the attribute filename
.
Modifying your HTTP request as little as possible, here is an example that sends two fields (one file and one value):
POST /index.php HTTP/1.1
Host: The IP address of my remote server
Connection: Close
User-Agent: My client
Content-Type: multipart/form-data; boundary=be0850c82dd4983ddc49a51a797dce49
Content-Length: 234
--be0850c82dd4983ddc49a51a797dce49
Content-Disposition: form-data; name="one"; filename="example.txt"
foo
--be0850c82dd4983ddc49a51a797dce49
Content-Disposition: form-data; name="two"
bar
--be0850c82dd4983ddc49a51a797dce49--
For each field, you need a Content-Disposition
header to be able to name the field and optionally give it a filename.
Content-Type
is optional although you may want to add one if the value comes garbled.
Modify as necessary if you only need to upload one file.
Here's a neat little script that I use to test HTTP requests:
<?php
header('Content-Type: application/json');
echo json_encode([
'method' => $_SERVER['REQUEST_METHOD'],
'uri' => $_SERVER['REQUEST_URI'],
'body' => file_get_contents('php://input'),
'GET' => $_GET,
'POST' => $_POST,
'FILES' => $_FILES,
'headers' => getallheaders(),
], JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE|JSON_PRETTY_PRINT);
With that script at the receiving end, I sent my example request without modification to my localhost server and I got this result:
HTTP/1.1 200 OK
Date: Tue, 31 Jul 2018 11:33:57 GMT
Server: Apache/2.4.9 (Win32)
Connection: close
Transfer-Encoding: chunked
Content-Type: application/json
254
{
"method": "POST",
"uri": "/index.php",
"body": "",
"GET": [],
"POST": {
"two": "bar"
},
"FILES": {
"one": {
"name": "example.txt",
"type": "",
"tmp_name": "C:\\wamp\\tmp\\phpD6B5.tmp",
"error": 0,
"size": 3
}
},
"headers": {
"Host": "The IP address of my remote server",
"Connection": "Close",
"User-Agent": "My client",
"Content-Type": "multipart/form-data; boundary=be0850c82dd4983ddc49a51a797dce49",
"Content-Length": "234"
}
}
0
As you can see, field "one" goes to $_FILES
and field "two" goes to $_POST
.
First, send the example request without modification. Keep trying until you get the correct result. Then modify as needed, making sure every step of the way that the request still follows HTTP specification.