MultipartFormDataParser.php 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. <?php
  2. namespace Libs;
  3. use Psr\Http\Message\ServerRequestInterface;
  4. class MultipartFormDataParser
  5. {
  6. public static function parse(ServerRequestInterface $request): array
  7. {
  8. $contentType = $request->getHeaderLine('Content-Type');
  9. if (!preg_match('/multipart\/form-data;\s*boundary=(?:(?:"([^"]+)")|([^;\s]+))/i', $contentType, $m)) {
  10. throw new \RuntimeException('Invalid multipart Content-Type');
  11. }
  12. $boundary = (isset($m[1]) && $m[1] !== '') ? $m[1] : ($m[2] ?? '');
  13. if ($boundary === '') {
  14. throw new \RuntimeException('Missing multipart boundary');
  15. }
  16. $rawBody = '';
  17. $stream = $request->getBody();
  18. if (is_object($stream) && method_exists($stream, 'getContents')) {
  19. $rawBody = (string)$stream->getContents();
  20. if (method_exists($stream, 'rewind')) {
  21. try {
  22. $stream->rewind();
  23. } catch (\Throwable $e) {
  24. }
  25. }
  26. } else {
  27. $rawBody = (string)$stream;
  28. }
  29. if ($rawBody === '') {
  30. $rawBody = (string)file_get_contents('php://input');
  31. }
  32. $delimiter = "--" . $boundary;
  33. $parts = explode($delimiter, $rawBody);
  34. $fields = [];
  35. $files = [];
  36. foreach ($parts as $part) {
  37. $part = ltrim($part, "\r\n\n");
  38. $part = rtrim($part, "\r\n\n");
  39. if ($part === '' || $part === '--') {
  40. continue;
  41. }
  42. $split = preg_split("/\r?\n\r?\n/", $part, 2);
  43. if (!$split || count($split) !== 2) {
  44. continue;
  45. }
  46. [$rawHeaders, $body] = $split;
  47. $headers = self::parseHeaders($rawHeaders);
  48. $cd = $headers['content-disposition'] ?? '';
  49. if (!preg_match('/name="([^"]+)"/i', $cd, $nm)) {
  50. continue;
  51. }
  52. $name = $nm[1];
  53. $filename = null;
  54. if (preg_match('/filename="([^"]*)"/i', $cd, $fm)) {
  55. $filename = $fm[1];
  56. }
  57. $body = preg_replace("/\r\n\z/", '', $body);
  58. if ($filename !== null && $filename !== '') {
  59. $files[$name] = [
  60. 'field' => $name,
  61. 'filename' => $filename,
  62. 'content_type' => $headers['content-type'] ?? 'application/octet-stream',
  63. 'content' => $body,
  64. ];
  65. } else {
  66. $fields[$name] = $body;
  67. }
  68. }
  69. return ['fields' => $fields, 'files' => $files];
  70. }
  71. private static function parseHeaders(string $rawHeaders): array
  72. {
  73. $headers = [];
  74. foreach (preg_split('/\r?\n/', $rawHeaders) as $line) {
  75. $line = trim($line);
  76. if ($line === '' || strpos($line, ':') === false) {
  77. continue;
  78. }
  79. [$k, $v] = explode(':', $line, 2);
  80. $headers[strtolower(trim($k))] = trim($v);
  81. }
  82. return $headers;
  83. }
  84. }