summaryrefslogtreecommitdiffstats
path: root/vendor/react/http/src/Io/MultipartParser.php
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/react/http/src/Io/MultipartParser.php')
-rw-r--r--vendor/react/http/src/Io/MultipartParser.php328
1 files changed, 328 insertions, 0 deletions
diff --git a/vendor/react/http/src/Io/MultipartParser.php b/vendor/react/http/src/Io/MultipartParser.php
new file mode 100644
index 0000000..536694f
--- /dev/null
+++ b/vendor/react/http/src/Io/MultipartParser.php
@@ -0,0 +1,328 @@
+<?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;
+
+ /**
+ * 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 $postCount = 0;
+ private $filesCount = 0;
+ private $emptyCount = 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;
+ }
+
+ 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->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)
+ $start = \strpos($buffer, $boundary . "\r\n");
+
+ while ($start !== false) {
+ // search following boundary (preceded by newline)
+ // ignore last if not followed by boundary (SHOULD end with "--")
+ $start += $len + 2;
+ $end = \strpos($buffer, "\r\n" . $boundary, $start);
+ if ($end === false) {
+ break;
+ }
+
+ // parse one part and continue searching for next
+ $this->parsePart(\substr($buffer, $start, $end - $start));
+ $start = $end;
+ }
+ }
+
+ 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;
+ }
+}