| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147 |
- <?php
- namespace Libs;
- use Psr\Http\Message\ServerRequestInterface;
- class MultipartFormDataParser
- {
- public static function parse(ServerRequestInterface $request): array
- {
- $contentType = $request->getHeaderLine('Content-Type');
- $rawBody = '';
- $stream = $request->getBody();
- $streamMeta = [];
- if (is_object($stream)) {
- if (method_exists($stream, 'isSeekable')) {
- $streamMeta['seekable'] = (bool)$stream->isSeekable();
- }
- if (method_exists($stream, 'isReadable')) {
- $streamMeta['readable'] = (bool)$stream->isReadable();
- }
- if (method_exists($stream, 'getSize')) {
- $streamMeta['size'] = $stream->getSize();
- }
- if (method_exists($stream, 'tell')) {
- try {
- $streamMeta['tell'] = $stream->tell();
- } catch (\Throwable $e) {
- }
- }
- }
- if (is_object($stream) && method_exists($stream, 'getContents')) {
- if (method_exists($stream, 'rewind')) {
- try {
- $stream->rewind();
- } catch (\Throwable $e) {
- }
- }
- $rawBody = (string)$stream->getContents();
- if (method_exists($stream, 'rewind')) {
- try {
- $stream->rewind();
- } catch (\Throwable $e) {
- }
- }
- } else {
- $rawBody = (string)$stream;
- }
- if ($rawBody === '') {
- $rawBody = (string)file_get_contents('php://input');
- }
- return self::parseBody($contentType, $rawBody, $streamMeta);
- }
- public static function parseBody(string $contentType, string $rawBody, array $streamMeta = []): array
- {
- if (!preg_match('/multipart\/form-data;\s*boundary=(?:(?:"([^"]+)")|([^;\s]+))/i', $contentType, $m)) {
- throw new \RuntimeException('Invalid multipart Content-Type');
- }
- $boundary = (isset($m[1]) && $m[1] !== '') ? $m[1] : ($m[2] ?? '');
- if ($boundary === '') {
- throw new \RuntimeException('Missing multipart boundary');
- }
- $delimiter = "--" . $boundary;
- $parts = explode($delimiter, $rawBody);
- $fields = [];
- $files = [];
- $meta = [
- 'content_type' => $contentType,
- 'boundary' => $boundary,
- 'stream' => $streamMeta,
- 'raw_length' => strlen($rawBody),
- 'parts_count' => count($parts),
- 'detected_parts' => [],
- ];
- foreach ($parts as $part) {
- $part = ltrim($part, "\r\n\n");
- $part = rtrim($part, "\r\n\n");
- if ($part === '' || $part === '--') {
- continue;
- }
- $split = preg_split("/\r?\n\r?\n/", $part, 2);
- if (!$split || count($split) !== 2) {
- continue;
- }
- [$rawHeaders, $body] = $split;
- $headers = self::parseHeaders($rawHeaders);
- $cd = $headers['content-disposition'] ?? '';
- if (!preg_match('/name="([^"]+)"/i', $cd, $nm)) {
- continue;
- }
- $name = $nm[1];
- $filename = null;
- if (preg_match('/filename="([^"]*)"/i', $cd, $fm)) {
- $filename = $fm[1];
- }
- $meta['detected_parts'][] = [
- 'name' => $name,
- 'has_filename' => $filename !== null && $filename !== '',
- 'filename' => ($filename !== null && $filename !== '') ? $filename : null,
- 'content_type' => $headers['content-type'] ?? null,
- ];
- $body = preg_replace("/\r\n\z/", '', $body);
- if ($filename !== null && $filename !== '') {
- $files[$name] = [
- 'field' => $name,
- 'filename' => $filename,
- 'content_type' => $headers['content-type'] ?? 'application/octet-stream',
- 'content' => $body,
- ];
- } else {
- $fields[$name] = $body;
- }
- }
- return ['fields' => $fields, 'files' => $files, 'meta' => $meta];
- }
- private static function parseHeaders(string $rawHeaders): array
- {
- $headers = [];
- foreach (preg_split('/\r?\n/', $rawHeaders) as $line) {
- $line = trim($line);
- if ($line === '' || strpos($line, ':') === false) {
- continue;
- }
- [$k, $v] = explode(':', $line, 2);
- $headers[strtolower(trim($k))] = trim($v);
- }
- return $headers;
- }
- }
|