
This post is such a big deal for me. After about 10 years of lamenting the limitations of Post Test Server, I’ve finally released a new version, Post Test Server V2. I talk more about it on the site, but the high level story is that the old version had a lot of drawbacks. People couldn’t delete their content, it wasn’t flexible enough, and it did not checking before saving the dumps so once botnets found it the cost to run the service became untenable. This new version has been rewritten in Go and is hosted on Google App Engine. Check it out: http://ptsv2.com.
Over the years a lot of people asked me for the source for Post Test Server. I didn’t share it because I was embarrassed that I wrote something in PHP (even if it was 15 years ago) and it was literally just dumping the values of some post variables so it didn’t seem worthwhile. But, now that I have a real replacement service I feel less bad sharing this:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
# I ran this on a virtualized server, so accessing the filesystem directly wasn't a risk. | |
# I would not recommend this any other way. | |
$basedir = "/home/henryci/posttestserver.com/data/"; | |
$filedir = "/home/henryci/posttestserver.com/files/"; | |
# Enable CORS | |
header('Access-Control-Allow-Origin: *'); | |
# Creates a local directory | |
function makeDir($dir) | |
{ | |
if(file_exists($dir) == false) { | |
mkdir($dir); | |
} | |
} | |
# Creates the path for today's uploads | |
function dir_with_date($startDir) | |
{ | |
$dir = $startDir . date("Y"); | |
makeDir($dir); | |
$dir = "$dir/".date("m"); | |
makeDir($dir); | |
$dir = "$dir/".date("d"); | |
makeDir($dir); | |
return $dir; | |
} | |
# use &status_code to force the server to return a specific status code | |
if (isset($_GET['status_code'])) | |
{ | |
$status = $_GET['status_code']; | |
header("HTTP/1.0 $status Custom Status", true, $status); | |
} | |
# use &sleep to delay the input. Set a max on this to avoid all threads getting tied up | |
if(isset($_GET['sleep'])) | |
{ | |
$sleep_count = $_GET['sleep']; | |
if($sleep_count > 30) { $sleep_count = 30; } | |
sleep($sleep_count); | |
} | |
$output = "Time: " . date(DATE_RFC822) . "\n"; | |
$output .= "Source ip: " . getenv('REMOTE_ADDR') . "\n"; | |
# Grab the headers present in the upload | |
$output .= "\nHeaders (Some may be inserted by server)\n"; | |
foreach ($_SERVER as $name => $content) { | |
# ignore server specific content (confuses people) | |
if(preg_match("/^PATH/", $name) || | |
preg_match("/^RAILS/", $name) || | |
preg_match("/^FCGI/", $name) || | |
preg_match("/^SCRIPT_URL/", $name) || | |
preg_match("/^SCRIPT_URI/", $name) || | |
preg_match("/^dsid/", $name) || | |
preg_match("/^ds_id/", $name) || | |
preg_match("/^DH_USER/", $name) || | |
preg_match("/^DOCUMENT/", $name) || | |
preg_match("/^SERVER/", $name) || | |
preg_match("/^SCRIPT/", $name) || | |
preg_match("/^argv/", $name) || | |
preg_match("/^argc/", $name) || | |
preg_match("/^PHP/", $name) || | |
preg_match("/^SCRIPT/", $name) ) { | |
continue; | |
} | |
} | |
$output .= "\n"; | |
# Avoid writing huge files | |
$totalsize = (int) $_SERVER['CONTENT_LENGTH']; | |
if($totalsize > 537387 ) { # Honestly, I have no idea where I got this magic number from. :) | |
echo "Posted message too large. :("; | |
exit; | |
} | |
# Parse the post parameters | |
if($_POST && count($_POST) > 0 ) | |
{ | |
$output .= "Post Params:\n"; | |
foreach ($_POST as $key => $value) { | |
$output .= "key: '$key' value: '$value'\n"; | |
} | |
} | |
else | |
{ | |
$output .= "No Post Params.\n"; | |
} | |
# If the post contains a raw data block | |
if($HTTP_RAW_POST_DATA) | |
{ | |
$output .= "\n== Begin post body ==\n"; | |
$output .= $HTTP_RAW_POST_DATA; | |
$output .= "\n== End post body ==\n"; | |
} | |
else | |
{ | |
$output .= "Empty post body.\n"; | |
} | |
# Handle multipart/form-data | |
# $_FILES is a hash of hashes, one for each uploaded file | |
if(isset($_SERVER["CONTENT_TYPE"]) && | |
preg_match("/multipart\/form-data/i", $_SERVER["CONTENT_TYPE"] ) | |
) | |
{ | |
$output .= "\n== Multipart File upload. ==\n"; | |
$output .= "Received " . count($_FILES) . " file(s)\n"; | |
$count = 0; | |
foreach($_FILES as $key => $value) | |
{ | |
$output .= " $count: posted name=$key\n"; | |
foreach($_FILES[$key] as $key2 => $value2) | |
{ | |
if(!strcmp($key2, "tmp_name")) { | |
continue; | |
} | |
$output .= " $key2: $value2\n"; | |
} | |
# move the file from temp storage to the actual destination | |
$uploaded = $_FILES[$key]['tmp_name']; | |
$target_filename = "f_" . date("H.i.s") . rand(); | |
$target_path = dir_with_date($filedir) . "/$target_filename"; | |
$target_url = "/files/".date("Y/m/d")."/$target_filename"; | |
if(is_uploaded_file($uploaded)) | |
{ | |
if(copy($uploaded, $target_path)) { | |
$output .= "Uploaded File: http://posttestserver.com$target_url\n"; | |
} | |
else { | |
$output .= "File uploaded successfully but could not be copied.\n"; | |
} | |
} | |
else { | |
$output .= "File specified was not uploaded. Possible file upload attack.\n"; | |
} | |
} | |
} | |
# read in any data uploaded via a PUT | |
$putdata = fopen("php://input", "r"); | |
$didit = false; | |
while ($data = fread($putdata, 1024)) { | |
if(!$didit) { | |
$output .= "\nUpload contains PUT data:\n"; | |
$didit = true; | |
} | |
$output .= $data; | |
} | |
fclose($putdata); | |
$dir = dir_with_date($basedir); | |
# Allowing the end user to name the file is a risk. | |
if(! empty($_GET) && isset($_GET["dir"])) { | |
$target = str_replace(".", "", $_GET["dir"]); | |
$target = str_replace("/", "", $target); | |
$target = str_replace(";", "", $target); | |
if(strlen($target) > 1) { | |
$dir = "$dir/$target"; | |
makeDir($dir); | |
} | |
} | |
# Name the upload w/ a timestamp and random number | |
$filename = date("H.i.s") . rand(); | |
$file = $dir . "/$filename"; | |
$fh = fopen($file, 'w'); | |
fwrite($fh, $output); | |
fclose($fh); | |
# Allow the user to specify a custom response | |
if (isset($_GET['response_body'])) | |
{ | |
echo $_GET['response_body']; | |
} | |
else # or else output the results. | |
{ | |
if (isset($_GET['dump']) == false ) | |
{ | |
echo "Successfully dumped " . count($_POST) . " post variables.\n"; | |
$path = date("Y/m/d") . "/"; | |
if(isset($_GET["dir"])) { | |
$path .= $_GET["dir"] . "/"; | |
} | |
$path .= "$filename\n"; | |
echo "View it at http://www.posttestserver.com/data/$path"; | |
echo "Post body was " . strlen($HTTP_RAW_POST_DATA) . " chars long."; | |
} | |
if (isset($_GET['dump'])) | |
{ | |
if(isset($_GET['html'])) | |
{ | |
echo '<html><head><title>Post test</title></head><body>'; | |
echo str_replace("\n", "<br />", $output); | |
echo '</body></html>'; | |
} | |
else { | |
echo $output; | |
} | |
} | |
} | |
?> |
what version of PHP your code required?
LikeLike
No idea. This is > 10 years old and hasn’t been touched since so something fairly old I’m presuming. I also don’t believe it is using any real features of the language so I don’t think language version will cause any troubles.
LikeLike
Hi Henry,
I have started getting the following “Over Quota” error when testing with your server. Any idea when it is going to be fixed?
Error
Over Quota
This application is temporarily over its serving quota. Please try again later.
I love this server and thanks in advance for providing it for the public!
-tlc
LikeLike
It will reset tomorrow. (it’s a daily quota). I had somebody hitting the server 185,000 times an hour: https://imgur.com/a/Ac1KJYC
I’ve deleted their bucket. Bear with me until this weekend and I’ll add code to block IPs of anybody who hits the service that much.
I’m sorry I don’t have a better answer, but the quota limit just saved my butt so I’m not tweaking it just yet.
LikeLike
Hi Henry, Thanks for the quick response. You ROCK!
LikeLike
Dude, seriously. I could have been enjoying some time out in the sun or being with family, but instead I’m learning all about HTTP POST and GET.
Thank you.
LikeLike
Hi Henry, Is the ptsv2 server being hit with a lot of requests again … it seems to be out again …
https://ptsv2.com/t/8ozif-1518025580
Over Quota
This application is temporarily over its serving quota. Please try again later.
LikeLike
Thanks for letting me know! It actually wasn’t being beaten up by any one actor this time. It just seems to be getting enough use that it is hitting the set $5 per day limit. I upped the limit. It will reset tonight (I can’t do anything about it until then) and then I’ll use the new limit tomorrow to push some changes that make it more efficient.
Always something!
LikeLike
Thanks for the quick response again … much appreciated!
LikeLike
I use ptsv2.com on occasion and find it very helpful. Even the ‘Some rules” page is excellent. Thank you for providing ptsv2.com for reasonable use by developers.
LikeLike
I appreciate the kind words. :)
LikeLiked by 1 person
Hi Henry, I’ve been using this for a little while, it’s such a funny idea I refuse to use anything else while I’m writing static sites!
LikeLike
You’re too kind. :)
LikeLike