| Current Path : /home/users/unlimited/www/admin.priyotama.com/vendor/react/http/src/Io/ |
| Current File : /home/users/unlimited/www/admin.priyotama.com/vendor/react/http/src/Io/MultipartParser.php |
<?php
namespace React\Http\Io;
use Psr\Http\Message\ServerRequestInterface;
/**
* [Internal] Parses a string body with "Content-Type: multipart/form-data" into structured data
*
* This is use internally to parse incoming request bodies into structured data
* that resembles PHP's `$_POST` and `$_FILES` superglobals.
*
* @internal
* @link https://tools.ietf.org/html/rfc7578
* @link https://tools.ietf.org/html/rfc2046#section-5.1.1
*/
final class MultipartParser
{
/**
* @var ServerRequestInterface|null
*/
private $request;
/**
* @var int|null
*/
private $maxFileSize;
/**
* Based on $maxInputVars and $maxFileUploads
*
* @var int
*/
private $maxMultipartBodyParts;
/**
* ini setting "max_input_vars"
*
* Does not exist in PHP < 5.3.9 or HHVM, so assume PHP's default 1000 here.
*
* @var int
* @link http://php.net/manual/en/info.configuration.php#ini.max-input-vars
*/
private $maxInputVars = 1000;
/**
* ini setting "max_input_nesting_level"
*
* Does not exist in HHVM, but assumes hard coded to 64 (PHP's default).
*
* @var int
* @link http://php.net/manual/en/info.configuration.php#ini.max-input-nesting-level
*/
private $maxInputNestingLevel = 64;
/**
* ini setting "upload_max_filesize"
*
* @var int
*/
private $uploadMaxFilesize;
/**
* ini setting "max_file_uploads"
*
* Additionally, setting "file_uploads = off" effectively sets this to zero.
*
* @var int
*/
private $maxFileUploads;
private $multipartBodyPartCount = 0;
private $postCount = 0;
private $filesCount = 0;
private $emptyCount = 0;
private $cursor = 0;
/**
* @param int|string|null $uploadMaxFilesize
* @param int|null $maxFileUploads
*/
public function __construct($uploadMaxFilesize = null, $maxFileUploads = null)
{
$var = \ini_get('max_input_vars');
if ($var !== false) {
$this->maxInputVars = (int)$var;
}
$var = \ini_get('max_input_nesting_level');
if ($var !== false) {
$this->maxInputNestingLevel = (int)$var;
}
if ($uploadMaxFilesize === null) {
$uploadMaxFilesize = \ini_get('upload_max_filesize');
}
$this->uploadMaxFilesize = IniUtil::iniSizeToBytes($uploadMaxFilesize);
$this->maxFileUploads = $maxFileUploads === null ? (\ini_get('file_uploads') === '' ? 0 : (int)\ini_get('max_file_uploads')) : (int)$maxFileUploads;
$this->maxMultipartBodyParts = $this->maxInputVars + $this->maxFileUploads;
}
public function parse(ServerRequestInterface $request)
{
$contentType = $request->getHeaderLine('content-type');
if(!\preg_match('/boundary="?(.*?)"?$/', $contentType, $matches)) {
return $request;
}
$this->request = $request;
$this->parseBody('--' . $matches[1], (string)$request->getBody());
$request = $this->request;
$this->request = null;
$this->multipartBodyPartCount = 0;
$this->cursor = 0;
$this->postCount = 0;
$this->filesCount = 0;
$this->emptyCount = 0;
$this->maxFileSize = null;
return $request;
}
private function parseBody($boundary, $buffer)
{
$len = \strlen($boundary);
// ignore everything before initial boundary (SHOULD be empty)
$this->cursor = \strpos($buffer, $boundary . "\r\n");
while ($this->cursor !== false) {
// search following boundary (preceded by newline)
// ignore last if not followed by boundary (SHOULD end with "--")
$this->cursor += $len + 2;
$end = \strpos($buffer, "\r\n" . $boundary, $this->cursor);
if ($end === false) {
break;
}
// parse one part and continue searching for next
$this->parsePart(\substr($buffer, $this->cursor, $end - $this->cursor));
$this->cursor = $end;
if (++$this->multipartBodyPartCount > $this->maxMultipartBodyParts) {
break;
}
}
}
private function parsePart($chunk)
{
$pos = \strpos($chunk, "\r\n\r\n");
if ($pos === false) {
return;
}
$headers = $this->parseHeaders((string)substr($chunk, 0, $pos));
$body = (string)\substr($chunk, $pos + 4);
if (!isset($headers['content-disposition'])) {
return;
}
$name = $this->getParameterFromHeader($headers['content-disposition'], 'name');
if ($name === null) {
return;
}
$filename = $this->getParameterFromHeader($headers['content-disposition'], 'filename');
if ($filename !== null) {
$this->parseFile(
$name,
$filename,
isset($headers['content-type'][0]) ? $headers['content-type'][0] : null,
$body
);
} else {
$this->parsePost($name, $body);
}
}
private function parseFile($name, $filename, $contentType, $contents)
{
$file = $this->parseUploadedFile($filename, $contentType, $contents);
if ($file === null) {
return;
}
$this->request = $this->request->withUploadedFiles($this->extractPost(
$this->request->getUploadedFiles(),
$name,
$file
));
}
private function parseUploadedFile($filename, $contentType, $contents)
{
$size = \strlen($contents);
// no file selected (zero size and empty filename)
if ($size === 0 && $filename === '') {
// ignore excessive number of empty file uploads
if (++$this->emptyCount + $this->filesCount > $this->maxInputVars) {
return;
}
return new UploadedFile(
new BufferedBody(''),
$size,
\UPLOAD_ERR_NO_FILE,
$filename,
$contentType
);
}
// ignore excessive number of file uploads
if (++$this->filesCount > $this->maxFileUploads) {
return;
}
// file exceeds "upload_max_filesize" ini setting
if ($size > $this->uploadMaxFilesize) {
return new UploadedFile(
new BufferedBody(''),
$size,
\UPLOAD_ERR_INI_SIZE,
$filename,
$contentType
);
}
// file exceeds MAX_FILE_SIZE value
if ($this->maxFileSize !== null && $size > $this->maxFileSize) {
return new UploadedFile(
new BufferedBody(''),
$size,
\UPLOAD_ERR_FORM_SIZE,
$filename,
$contentType
);
}
return new UploadedFile(
new BufferedBody($contents),
$size,
\UPLOAD_ERR_OK,
$filename,
$contentType
);
}
private function parsePost($name, $value)
{
// ignore excessive number of post fields
if (++$this->postCount > $this->maxInputVars) {
return;
}
$this->request = $this->request->withParsedBody($this->extractPost(
$this->request->getParsedBody(),
$name,
$value
));
if (\strtoupper($name) === 'MAX_FILE_SIZE') {
$this->maxFileSize = (int)$value;
if ($this->maxFileSize === 0) {
$this->maxFileSize = null;
}
}
}
private function parseHeaders($header)
{
$headers = array();
foreach (\explode("\r\n", \trim($header)) as $line) {
$parts = \explode(':', $line, 2);
if (!isset($parts[1])) {
continue;
}
$key = \strtolower(trim($parts[0]));
$values = \explode(';', $parts[1]);
$values = \array_map('trim', $values);
$headers[$key] = $values;
}
return $headers;
}
private function getParameterFromHeader(array $header, $parameter)
{
foreach ($header as $part) {
if (\preg_match('/' . $parameter . '="?(.*?)"?$/', $part, $matches)) {
return $matches[1];
}
}
return null;
}
private function extractPost($postFields, $key, $value)
{
$chunks = \explode('[', $key);
if (\count($chunks) == 1) {
$postFields[$key] = $value;
return $postFields;
}
// ignore this key if maximum nesting level is exceeded
if (isset($chunks[$this->maxInputNestingLevel])) {
return $postFields;
}
$chunkKey = \rtrim($chunks[0], ']');
$parent = &$postFields;
for ($i = 1; isset($chunks[$i]); $i++) {
$previousChunkKey = $chunkKey;
if ($previousChunkKey === '') {
$parent[] = array();
\end($parent);
$parent = &$parent[\key($parent)];
} else {
if (!isset($parent[$previousChunkKey]) || !\is_array($parent[$previousChunkKey])) {
$parent[$previousChunkKey] = array();
}
$parent = &$parent[$previousChunkKey];
}
$chunkKey = \rtrim($chunks[$i], ']');
}
if ($chunkKey === '') {
$parent[] = $value;
} else {
$parent[$chunkKey] = $value;
}
return $postFields;
}
}