Package home | Report new bug | New search | Development Roadmap Status: Open | Feedback | All | Closed Since Version 0.4.1

Bug #14469 POST Multipart Content Not Included in Request Object
Submitted: 2008-08-07 18:14 UTC
From: achristi Assigned:
Status: Feedback Package: HTTP_Server (version 0.4.0)
PHP Version: 5.2.6 OS: Mac OSX 10.5.4
Roadmaps: (Not assigned)    
Subscription  


 [2008-08-07 18:14 UTC] achristi (Andy Christianson)
Description: ------------ When POSTing with the 'Content-type: multipart/form-data' header to a server based on HTTP_Server, only a CRLF is included in the content field of the HTTP_Server_Request. This happens because HTTP_Server::__construct() sets the readEndCharacter property of the Net_Server driver to "\r\n". This works fine for GET requests, but not for POST requests. A single CRLF is included after the last header line in an HTTP request, according to RFC 2616 (HTTP/1.1). This means that HTTP_Server will currently only read headers. I have attached a patch that corrects this bug by changing the functionality of HTTP_Server::onReceiveData. If the request is of method POST, then the method will now attempt to continue reading data until the length meets the 'content-length' request header. The result is that HTTP_Server now correctly populates the HTTP_Server_Request::content property for POST requests.

Comments

 [2008-08-08 12:38 UTC] achristi (Andy Christianson)
"This happens because HTTP_Server::__construct() sets the readEndCharacter property of the Net_Server driver to "\r\n"." Actually, it sets it to "\r\n\r\n" --I typed it out wrong when I posted this. The bug still applies.
 [2008-08-09 03:05 UTC] doconnor (Daniel O'Connor)
Hey thanks for the patch Andy! I don't suppose you'd feel up to a very small test case to better demonstrate this? That makes it easier for people who don't know the package well to evaluate your patch...
 [2008-08-12 17:26 UTC] achristi (Andy Christianson)
Here's a very rough test case: class MyServer extends HTTP_Server { public function POST($clientId, $request) { var_dump($request->content); // expected output: string containing full POST message sent from browser // actual output: string containing a small amount of whitespace (\r\n?) } } Also, here's a thoroughly-tested onReceiveData. I ran into some problems with IE/Firefox on a Windows host. The following code works perfectly, as far as I can tell, with any combination of Unix/Windows hosts and Firefox/Safari/IE clients. The catch here is that this handles multipart/form-data rather than any POST request. I apologize that it is not formatted according to PEAR standards. I haven't got enough time to do the formatting and create a proper patch at the moment. Hopefully this will help someone and/or make its way into HTTP_Server: function onReceiveData($clientId, $data) { // read until double \r\n (end of HTTP headers) while(false === strpos($data,"\r\n\r\n")) { $data .= $this->_driver->readFromSocket($clientId); } // parse request headers $request = &HTTP_Server_Request::parse($data); if(is_array($request->headers) && array_key_exists('content-type',$request->headers) && preg_match('/^multipart\/form- data.*boundary=(\S+).*$/ms', $request->headers['content-type'], $matches)) { $boundary = "--".$matches[1]."--\r\n"; $originalReadEndCharacter = $this->_driver->readEndCharacter; $this->_driver->readEndCharacter = "\n"; while(false === strpos($request->content,$boundary)) { $request->content .= $this->_driver- >readFromSocket($clientId); } $this->_driver->readEndCharacter = $originalReadEndCharacter; } if ($request === false) { $response = $this->handleBadRequest($clientId, $data); $this->_sendResponse($clientId, $response); } else { $this->_serveRequest($clientId, $request); } // close the connection $this->_driver->closeConnection($clientId); }
 [2008-08-12 17:27 UTC] achristi (Andy Christianson)
I left out that the code above also relies on the following line in HTTP_Server::__construct(): $this->_driver->readEndCharacter = "\n";
 [2008-08-12 17:31 UTC] achristi (Andy Christianson)
...which also means that a few redundant lines can be removed. New version (mind the line wrapping): function onReceiveData($clientId, $data) { // read until double \r\n (end of HTTP headers) while(false === strpos($data,"\r\n\r\n")) { $data .= $this->_driver->readFromSocket($clientId); } // parse request headers $request = &HTTP_Server_Request::parse($data); if(is_array($request->headers) && array_key_exists('content-type',$request->headers) && preg_match('/^multipart\/form- data.*boundary=(\S+).*$/ms', $request->headers['content-type'], $matches)) { $boundary = "--".$matches[1]."--\r\n"; while(false === strpos($request->content,$boundary)) { $request->content .= $this->_driver- >readFromSocket($clientId); } } if ($request === false) { $response = $this->handleBadRequest($clientId, $data); $this->_sendResponse($clientId, $response); } else { $this->_serveRequest($clientId, $request); } // close the connection $this->_driver->closeConnection($clientId); }
 [2008-11-14 17:02 UTC] mrsol (Oleg Savostin)
Here is variant under which stright not it is necessary to use the function Driver reading from socket. Plus is added restriction of the request. Single that here else it is necessary to correct here is this bug http://pear.php.net/bugs/bug.php?id=15030 ----------------------- var $ClientsInfo = array(); var $MaximalLenRequest = 51200; var $readBuferDataSize = 256; function __construct($hostname, $port, $driver = 'Fork'){ $this->_driver = &Net_Server::create($driver, $hostname, $port); $this->_driver->readEndCharacter = "\n"; $this->_driver->readBufferSize = 1; $this->_driver->setCallbackObject($this); } function onReceiveData($clientId, $data){ if(empty($this->ClientsInfo[$clientId])){ $this->ClientsInfo[$clientId]['data'] = $data; $this->ClientsInfo[$clientId]['end'] = false; $this->ClientsInfo[$clientId]['last_content'] = 0; $this->ClientsInfo[$clientId]['found_content_len'] = false; $this->ClientsInfo[$clientId]['full_len'] = strlen($data); $this->ClientsInfo[$clientId]['content_len'] = NULL; }else{ $this->ClientsInfo[$clientId]['data'] .= $data; if($this->ClientsInfo[$clientId]['content_len']===NULL and strpos($data, 'Content-Length')!==false){ if(preg_match("'([^: ]+): (.+)'", $data, $regs)){ $this->ClientsInfo[$clientId]['last_content'] = $regs[2]; $this->ClientsInfo[$clientId]['content_len'] = $regs[2]; } } if($this->ClientsInfo[$clientId]['found_content_len']){ $this->ClientsInfo[$clientId]['last_content'] -= strlen($data); if($this->ClientsInfo[$clientId]['last_content']>$this->readBuferDataSize){ $this->_driver->readBufferSize = $this->readBuferDataSize; }else{ $this->_driver->readBufferSize = $this->ClientsInfo[$clientId]['last_content']; } } if($this->ClientsInfo[$clientId]['found_content_len']!=true and $data=="\r\n"){ $this->ClientsInfo[$clientId]['found_content_len'] = true; $this->_driver->setEndCharacter(null); } $this->ClientsInfo[$clientId]['last_data'] = $data; $this->ClientsInfo[$clientId]['full_len'] += strlen($data); } if($this->ClientsInfo[$clientId]['last_content']<=0 and $this->ClientsInfo[$clientId]['found_content_len']){ $this->ClientsInfo[$clientId]['end'] = true; } if($this->ClientsInfo[$clientId]['end']){ $request = &HTTP_Server_Request::parse($this->ClientsInfo[$clientId]['data']); if($request === false){ $response = $this->handleBadRequest($clientId, $this->ClientsInfo[$clientId]['data']); $this->_sendResponse($clientId, $response); }else{ $this->_serveRequest($clientId, $request); } }else{ if($this->ClientsInfo[$clientId]['full_len']>$this->MaximalLenRequest){ $response = $this->handleBadRequestCode($clientId, $this->ClientsInfo[$clientId]['data'], 413); }else{ return true; } } // close the connection $this->_driver->setEndCharacter("\n"); $this->_driver->readBufferSize = 1; unset($ClientsInfo[$clientId]); $this->_driver->closeConnection($clientId); } function handleBadRequestCode($clientId, $data, $Code){ return array( 'code' => $Code ); }
 [2011-08-08 08:17 UTC] doconnor (Daniel O'Connor)
-Status: Open +Status: Feedback
Andy/Oleg; can I get either of you to do the updated patches as actual diffs? It's a bit tricky for me to read them otherwise.