Sending POST Data Manually
After surfing around some forums, and reading a few mailing lists, a subject I've noticed time and time again, is "How do I send POST data manually?". Well, it's really not that hard. There are a couple of ways of going about it, but the easiest and most efficient that I've come across is the method I'll speak of here.
Let's get into it. I'm just going to make this into a function, so we can easily include it and call it when we need to. First thing we need to do is plan the variables we want to pass to the function. We'll need a hostname, port, filepath, and an associative array holding the data you wish to post. So, our function definition will be as follows:
function send_post($Host, $Port, $FilePath, $Data) { }
Pretty easy. Now we need to get the data into a format that the web server will recognize. eg, key1=value1&key2=value2&key3=value3. To do this, we'll use a foreach loop:
foreach($Data as $Key=>$Value) {
// We do this to make sure that it doesn't add an & to the beginning of the string
// If it's empty, add nothing, if it's not, append an &
$sPostData .= (empty($sPostData)) ? "" : "&";
// Append the key and value to our string
/* We use the urlencode function to ensure that it fixes up any special characters to hex
and then htmlspecialchars to convert any other characters to something nicer */
$sPostData .= htmlspecialchars(urlencode($Key))."=".htmlspecialchars(urlencode($Value));
}
So, now we've got a function that loops around and creates a string in the format of key1=value1&key2=value2. Yay! Now, we'll need to actually create the HTTP headers. There are a few we'll NEED, and there are a few that are desirable. This is how I assign it, you may want more or less, best way would be to reed the HTTP/1.1 RFC at http://www.w3.org/Protocols/rfc2616/rfc2616 . :
// Set the length of the Post data here, we'll need it later too
$iPostLen = strlen($sPostData);
$headers = "POST $FilePath HTTP/1.1\r\n";
$headers .= "Accept: */*\r\n";
$headers .= "Referer: ".$_SERVER['REQUEST_URI']."\r\n";
$headers .= "Accept-Language: en-us\r\n";
$headers .= "Content-Type: application/x-www-form-urlencoded\r\n";
$headers .= "User-Agent: Mozilla/4.0 (compatible; MSIE 5.5; Windows 98)\r\n";
$headers .= "Content-Length: $iPostLen\r\n";
$headers .= "Host: $Host\r\n";
$headers .= "Cache-Control: no-cache\r\n\r\n";
So, there we have it, that's the important bit. The FilePath tells the server what file you're actually posting the data to. The strlen function is used to get the length for the Content-Length which is required, and the Host header tells the server what host you're sending to (Without this a VirtualHost will kill your HTTP request).
What we need to do now is actually send this to the server. We'll do this by opening a socket, and writing to the file pointer:
// $errno, and $errstr are empty variables, but we'll need them to set the timeout
if(!$fp = fsockopen($Host, $Port, $errno, $errstr, 3)) {
// We use this to create an error message if it fails (it can be suppressed with @ still)
trigger_error("Fatal Error: Could not open socket!", E_USER_ERROR);
return false;
}
// Write the headers to the socket
fwrite($fp, $headers, strlen($headers));
// Write the post data to the socket
fwrite($fp, $sPostData, iPostLen);
Now we've sent our headers and post data! But, it doesn't end there, you're still going to want to see what the page returns! So, we read the response from the server, and output it to our own page. Here's how we do that:
// Loop through each line until we reach EOF
while(!feof($fp)) {
// Read from the file pointer, up to new line, or 4096 bytes, then append it to output
$output .= fgets($fp, 4096);
}
// We close the socket here
fclose($fp);
if($output == "") {
// If the output is empty, then return false with no error message
return false;
} else {
// Otherwise return $output
return $output;
}
That's all there is to it! Now, if we put this masterful work of art together, we end up with THIS:
function send_post($Host, $Port, $FilePath, $Data) {
foreach($Data as $Key=>$Value) {
// We do this to make sure that it doesn't add an & to the beginning of the string
// If it's empty, add nothing, if it's not, append an &
$sPostData .= (empty($sPostData)) ? "" : "&";
// Append the key and value to our string
/* We use the urlencode function to ensure that it fixes up any special characters to hex
and then htmlspecialchars to convert any other characters to something nicer */
$sPostData .= htmlspecialchars(urlencode($Key))."=".htmlspecialchars(urlencode($Value));
}
// Set the length of the Post data here, we'll need it later too
$iPostLen = strlen($sPostData);
$headers = "POST $FilePath HTTP/1.1\r\n";
$headers .= "Accept: */*\r\n";
$headers .= "Referer: ".$_SERVER['REQUEST_URI']."\r\n";
$headers .= "Accept-Language: en-us\r\n";
$headers .= "Content-Type: application/x-www-form-urlencoded\r\n";
$headers .= "User-Agent: Mozilla/4.0 (compatible; MSIE 5.5; Windows 98)\r\n";
$headers .= "Content-Length: $iPostLen\r\n";
$headers .= "Host: $Host\r\n";
$headers .= "Cache-Control: no-cache\r\n\r\n";
// $errno, and $errstr are empty variables, but we'll need them to set the timeout
if(!$fp = fsockopen($Host, $Port, $errno, $errstr, 3)) {
// We use this to create an error message if it fails (it can be suppressed with @ still)
trigger_error("Fatal Error: Could not open socket!", E_USER_ERROR);
return false;
}
// Write the headers to the socket
fwrite($fp, $headers, strlen($headers));
// Write the post data to the socket
fwrite($fp, $sPostData, iPostLen);
// Loop through each line until we reach EOF
while(!feof($fp)) {
// Read from the file pointer, up to new line, or 4096 bytes, then append it to output
$output .= fgets($fp, 4096);
}
// We close the socket here
fclose($fp);
if($output == "") {
// If the output is empty, then return false with no error message
return false;
} else {
// Otherwise return $output
return $output;
}
}
Congratulations! You now have a function that will send POST data for you manually. Makes so many things, so much easier! 🙂