Skip to content

Commit

Permalink
Merge pull request #13 from onigoetz/master
Browse files Browse the repository at this point in the history
Multipart handling
  • Loading branch information
WyriHaximus committed Aug 10, 2015
2 parents 8602944 + a3cf90a commit b36fb25
Show file tree
Hide file tree
Showing 9 changed files with 812 additions and 207 deletions.
203 changes: 203 additions & 0 deletions src/MultipartParser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
<?php

namespace React\Http;

/**
* Parse a multipart body
*
* Original source is from https://gist.github.com/jas-/5c3fdc26fedd11cb9fb5
*
* @author jason.gerfen@gmail.com
* @author stephane.goetz@onigoetz.ch
* @license http://www.gnu.org/licenses/gpl.html GPL License 3
*/
class MultipartParser
{
/**
* @var string
*/
protected $input;

/**
* @var string
*/
protected $boundary;

/**
* Contains the resolved posts
*
* @var array
*/
protected $post = [];

/**
* Contains the resolved files
*
* @var array
*/
protected $files = [];

/**
* @param $input
* @param $boundary
*/
public function __construct($input, $boundary)
{
$this->input = $input;
$this->boundary = $boundary;
}

/**
* @return array
*/
public function getPost()
{
return $this->post;
}

/**
* @return array
*/
public function getFiles()
{
return $this->files;
}

/**
* Do the actual parsing
*/
public function parse()
{
$blocks = $this->split($this->boundary);

foreach ($blocks as $value) {
if (empty($value)) {
continue;
}

$this->parseBlock($value);
}
}

/**
* @param $boundary string
* @returns Array
*/
protected function split($boundary)
{
$boundary = preg_quote($boundary);
$result = preg_split("/\\-+$boundary/", $this->input);
array_pop($result);
return $result;
}

/**
* Decide if we handle a file, post value or octet stream
*
* @param $string string
* @returns void
*/
protected function parseBlock($string)
{
if (strpos($string, 'filename') !== false) {
$this->file($string);
return;
}

// This may never be called, if an octet stream
// has a filename it is catched by the previous
// condition already.
if (strpos($string, 'application/octet-stream') !== false) {
$this->octetStream($string);
return;
}

$this->post($string);
}

/**
* Parse a raw octet stream
*
* @param $string
* @return array
*/
protected function octetStream($string)
{
preg_match('/name=\"([^\"]*)\".*stream[\n|\r]+([^\n\r].*)?$/s', $string, $match);

$this->addResolved('post', $match[1], $match[2]);
}

/**
* Parse a file
*
* @param $string
* @return array
*/
protected function file($string)
{
preg_match('/name=\"([^\"]*)\"; filename=\"([^\"]*)\"[\n|\r]+([^\n\r].*)?\r$/s', $string, $match);
preg_match('/Content-Type: (.*)?/', $match[3], $mime);

$content = preg_replace('/Content-Type: (.*)[^\n\r]/', '', $match[3]);
$content = ltrim($content, "\r\n");

// Put content in a stream
$stream = fopen('php://memory', 'r+');
if ($content !== '') {
fwrite($stream, $content);
fseek($stream, 0);
}

$data = [
'name' => $match[2],
'type' => trim($mime[1]),
'stream' => $stream, // Instead of writing to a file, we write to a stream.
'error' => UPLOAD_ERR_OK,
'size' => function_exists('mb_strlen')? mb_strlen($content, '8bit') : strlen($content),
];

//TODO :: have an option to write to files to emulate the same functionality as a real php server
//$path = tempnam(sys_get_temp_dir(), "php");
//$err = file_put_contents($path, $content);
//$data['tmp_name'] = $path;
//$data['error'] = ($err === false) ? UPLOAD_ERR_NO_FILE : UPLOAD_ERR_OK;

$this->addResolved('files', $match[1], $data);
}

/**
* Parse POST values
*
* @param $string
* @return array
*/
protected function post($string)
{
preg_match('/name=\"([^\"]*)\"[\n|\r]+([^\n\r].*)?\r$/s', $string, $match);

$this->addResolved('post', $match[1], $match[2]);
}

/**
* Put the file or post where it belongs,
* The key names can be simple, or containing []
* it can also be a named key
*
* @param $type
* @param $key
* @param $content
*/
protected function addResolved($type, $key, $content)
{
if (preg_match('/^(.*)\[(.*)\]$/i', $key, $tmp)) {
if (!empty($tmp[2])) {
$this->{$type}[$tmp[1]][$tmp[2]] = $content;
} else {
$this->{$type}[$tmp[1]][] = $content;
}
} else {
$this->{$type}[$key] = $content;
}
}
}
52 changes: 48 additions & 4 deletions src/Request.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,25 @@ class Request extends EventEmitter implements ReadableStreamInterface
{
private $readable = true;
private $method;
private $path;
private $url;
private $query;
private $httpVersion;
private $headers;
private $body;
private $post = [];
private $files = [];

// metadata, implicitly added externally
public $remoteAddress;

public function __construct($method, $path, $query = array(), $httpVersion = '1.1', $headers = array())
public function __construct($method, $url, $query = array(), $httpVersion = '1.1', $headers = array(), $body = '')
{
$this->method = $method;
$this->path = $path;
$this->url = $url;
$this->query = $query;
$this->httpVersion = $httpVersion;
$this->headers = $headers;
$this->body = $body;
}

public function getMethod()
Expand All @@ -35,7 +39,12 @@ public function getMethod()

public function getPath()
{
return $this->path;
return $this->url->getPath();
}

public function getUrl()
{
return $this->url;
}

public function getQuery()
Expand All @@ -53,6 +62,41 @@ public function getHeaders()
return $this->headers;
}

public function getBody()
{
return $this->body;
}

public function setBody($body)
{
$this->body = $body;
}

public function getFiles()
{
return $this->files;
}

public function setFiles($files)
{
$this->files = $files;
}

public function getPost()
{
return $this->post;
}

public function setPost($post)
{
$this->post = $post;
}

public function getRemoteAddress()
{
return $this->remoteAddress;
}

public function expectsContinue()
{
return isset($this->headers['Expect']) && '100-continue' === $this->headers['Expect'];
Expand Down
64 changes: 0 additions & 64 deletions src/RequestHeaderParser.php

This file was deleted.

Loading

1 comment on commit b36fb25

@naroga
Copy link

@naroga naroga commented on b36fb25 Aug 10, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fixes #30 and #7 and makes my PR #32 obsolete.

Please sign in to comment.