File indexing completed on 2024-12-29 05:27:23
0001 <?php 0002 namespace GuzzleHttp\Psr7; 0003 0004 use Psr\Http\Message\MessageInterface; 0005 use Psr\Http\Message\RequestInterface; 0006 use Psr\Http\Message\ResponseInterface; 0007 use Psr\Http\Message\ServerRequestInterface; 0008 use Psr\Http\Message\StreamInterface; 0009 use Psr\Http\Message\UriInterface; 0010 0011 /** 0012 * Returns the string representation of an HTTP message. 0013 * 0014 * @param MessageInterface $message Message to convert to a string. 0015 * 0016 * @return string 0017 */ 0018 function str(MessageInterface $message) 0019 { 0020 if ($message instanceof RequestInterface) { 0021 $msg = trim($message->getMethod() . ' ' 0022 . $message->getRequestTarget()) 0023 . ' HTTP/' . $message->getProtocolVersion(); 0024 if (!$message->hasHeader('host')) { 0025 $msg .= "\r\nHost: " . $message->getUri()->getHost(); 0026 } 0027 } elseif ($message instanceof ResponseInterface) { 0028 $msg = 'HTTP/' . $message->getProtocolVersion() . ' ' 0029 . $message->getStatusCode() . ' ' 0030 . $message->getReasonPhrase(); 0031 } else { 0032 throw new \InvalidArgumentException('Unknown message type'); 0033 } 0034 0035 foreach ($message->getHeaders() as $name => $values) { 0036 $msg .= "\r\n{$name}: " . implode(', ', $values); 0037 } 0038 0039 return "{$msg}\r\n\r\n" . $message->getBody(); 0040 } 0041 0042 /** 0043 * Returns a UriInterface for the given value. 0044 * 0045 * This function accepts a string or {@see Psr\Http\Message\UriInterface} and 0046 * returns a UriInterface for the given value. If the value is already a 0047 * `UriInterface`, it is returned as-is. 0048 * 0049 * @param string|UriInterface $uri 0050 * 0051 * @return UriInterface 0052 * @throws \InvalidArgumentException 0053 */ 0054 function uri_for($uri) 0055 { 0056 if ($uri instanceof UriInterface) { 0057 return $uri; 0058 } elseif (is_string($uri)) { 0059 return new Uri($uri); 0060 } 0061 0062 throw new \InvalidArgumentException('URI must be a string or UriInterface'); 0063 } 0064 0065 /** 0066 * Create a new stream based on the input type. 0067 * 0068 * Options is an associative array that can contain the following keys: 0069 * - metadata: Array of custom metadata. 0070 * - size: Size of the stream. 0071 * 0072 * @param resource|string|null|int|float|bool|StreamInterface|callable $resource Entity body data 0073 * @param array $options Additional options 0074 * 0075 * @return Stream 0076 * @throws \InvalidArgumentException if the $resource arg is not valid. 0077 */ 0078 function stream_for($resource = '', array $options = []) 0079 { 0080 if (is_scalar($resource)) { 0081 $stream = fopen('php://temp', 'r+'); 0082 if ($resource !== '') { 0083 fwrite($stream, $resource); 0084 fseek($stream, 0); 0085 } 0086 return new Stream($stream, $options); 0087 } 0088 0089 switch (gettype($resource)) { 0090 case 'resource': 0091 return new Stream($resource, $options); 0092 case 'object': 0093 if ($resource instanceof StreamInterface) { 0094 return $resource; 0095 } elseif ($resource instanceof \Iterator) { 0096 return new PumpStream(function () use ($resource) { 0097 if (!$resource->valid()) { 0098 return false; 0099 } 0100 $result = $resource->current(); 0101 $resource->next(); 0102 return $result; 0103 }, $options); 0104 } elseif (method_exists($resource, '__toString')) { 0105 return stream_for((string) $resource, $options); 0106 } 0107 break; 0108 case 'NULL': 0109 return new Stream(fopen('php://temp', 'r+'), $options); 0110 } 0111 0112 if (is_callable($resource)) { 0113 return new PumpStream($resource, $options); 0114 } 0115 0116 throw new \InvalidArgumentException('Invalid resource type: ' . gettype($resource)); 0117 } 0118 0119 /** 0120 * Parse an array of header values containing ";" separated data into an 0121 * array of associative arrays representing the header key value pair 0122 * data of the header. When a parameter does not contain a value, but just 0123 * contains a key, this function will inject a key with a '' string value. 0124 * 0125 * @param string|array $header Header to parse into components. 0126 * 0127 * @return array Returns the parsed header values. 0128 */ 0129 function parse_header($header) 0130 { 0131 static $trimmed = "\"' \n\t\r"; 0132 $params = $matches = []; 0133 0134 foreach (normalize_header($header) as $val) { 0135 $part = []; 0136 foreach (preg_split('/;(?=([^"]*"[^"]*")*[^"]*$)/', $val) as $kvp) { 0137 if (preg_match_all('/<[^>]+>|[^=]+/', $kvp, $matches)) { 0138 $m = $matches[0]; 0139 if (isset($m[1])) { 0140 $part[trim($m[0], $trimmed)] = trim($m[1], $trimmed); 0141 } else { 0142 $part[] = trim($m[0], $trimmed); 0143 } 0144 } 0145 } 0146 if ($part) { 0147 $params[] = $part; 0148 } 0149 } 0150 0151 return $params; 0152 } 0153 0154 /** 0155 * Converts an array of header values that may contain comma separated 0156 * headers into an array of headers with no comma separated values. 0157 * 0158 * @param string|array $header Header to normalize. 0159 * 0160 * @return array Returns the normalized header field values. 0161 */ 0162 function normalize_header($header) 0163 { 0164 if (!is_array($header)) { 0165 return array_map('trim', explode(',', $header)); 0166 } 0167 0168 $result = []; 0169 foreach ($header as $value) { 0170 foreach ((array) $value as $v) { 0171 if (strpos($v, ',') === false) { 0172 $result[] = $v; 0173 continue; 0174 } 0175 foreach (preg_split('/,(?=([^"]*"[^"]*")*[^"]*$)/', $v) as $vv) { 0176 $result[] = trim($vv); 0177 } 0178 } 0179 } 0180 0181 return $result; 0182 } 0183 0184 /** 0185 * Clone and modify a request with the given changes. 0186 * 0187 * The changes can be one of: 0188 * - method: (string) Changes the HTTP method. 0189 * - set_headers: (array) Sets the given headers. 0190 * - remove_headers: (array) Remove the given headers. 0191 * - body: (mixed) Sets the given body. 0192 * - uri: (UriInterface) Set the URI. 0193 * - query: (string) Set the query string value of the URI. 0194 * - version: (string) Set the protocol version. 0195 * 0196 * @param RequestInterface $request Request to clone and modify. 0197 * @param array $changes Changes to apply. 0198 * 0199 * @return RequestInterface 0200 */ 0201 function modify_request(RequestInterface $request, array $changes) 0202 { 0203 if (!$changes) { 0204 return $request; 0205 } 0206 0207 $headers = $request->getHeaders(); 0208 0209 if (!isset($changes['uri'])) { 0210 $uri = $request->getUri(); 0211 } else { 0212 // Remove the host header if one is on the URI 0213 if ($host = $changes['uri']->getHost()) { 0214 $changes['set_headers']['Host'] = $host; 0215 0216 if ($port = $changes['uri']->getPort()) { 0217 $standardPorts = ['http' => 80, 'https' => 443]; 0218 $scheme = $changes['uri']->getScheme(); 0219 if (isset($standardPorts[$scheme]) && $port != $standardPorts[$scheme]) { 0220 $changes['set_headers']['Host'] .= ':'.$port; 0221 } 0222 } 0223 } 0224 $uri = $changes['uri']; 0225 } 0226 0227 if (!empty($changes['remove_headers'])) { 0228 $headers = _caseless_remove($changes['remove_headers'], $headers); 0229 } 0230 0231 if (!empty($changes['set_headers'])) { 0232 $headers = _caseless_remove(array_keys($changes['set_headers']), $headers); 0233 $headers = $changes['set_headers'] + $headers; 0234 } 0235 0236 if (isset($changes['query'])) { 0237 $uri = $uri->withQuery($changes['query']); 0238 } 0239 0240 if ($request instanceof ServerRequestInterface) { 0241 return new ServerRequest( 0242 isset($changes['method']) ? $changes['method'] : $request->getMethod(), 0243 $uri, 0244 $headers, 0245 isset($changes['body']) ? $changes['body'] : $request->getBody(), 0246 isset($changes['version']) 0247 ? $changes['version'] 0248 : $request->getProtocolVersion(), 0249 $request->getServerParams() 0250 ); 0251 } 0252 0253 return new Request( 0254 isset($changes['method']) ? $changes['method'] : $request->getMethod(), 0255 $uri, 0256 $headers, 0257 isset($changes['body']) ? $changes['body'] : $request->getBody(), 0258 isset($changes['version']) 0259 ? $changes['version'] 0260 : $request->getProtocolVersion() 0261 ); 0262 } 0263 0264 /** 0265 * Attempts to rewind a message body and throws an exception on failure. 0266 * 0267 * The body of the message will only be rewound if a call to `tell()` returns a 0268 * value other than `0`. 0269 * 0270 * @param MessageInterface $message Message to rewind 0271 * 0272 * @throws \RuntimeException 0273 */ 0274 function rewind_body(MessageInterface $message) 0275 { 0276 $body = $message->getBody(); 0277 0278 if ($body->tell()) { 0279 $body->rewind(); 0280 } 0281 } 0282 0283 /** 0284 * Safely opens a PHP stream resource using a filename. 0285 * 0286 * When fopen fails, PHP normally raises a warning. This function adds an 0287 * error handler that checks for errors and throws an exception instead. 0288 * 0289 * @param string $filename File to open 0290 * @param string $mode Mode used to open the file 0291 * 0292 * @return resource 0293 * @throws \RuntimeException if the file cannot be opened 0294 */ 0295 function try_fopen($filename, $mode) 0296 { 0297 $ex = null; 0298 set_error_handler(function () use ($filename, $mode, &$ex) { 0299 $ex = new \RuntimeException(sprintf( 0300 'Unable to open %s using mode %s: %s', 0301 $filename, 0302 $mode, 0303 func_get_args()[1] 0304 )); 0305 }); 0306 0307 $handle = fopen($filename, $mode); 0308 restore_error_handler(); 0309 0310 if ($ex) { 0311 /** @var $ex \RuntimeException */ 0312 throw $ex; 0313 } 0314 0315 return $handle; 0316 } 0317 0318 /** 0319 * Copy the contents of a stream into a string until the given number of 0320 * bytes have been read. 0321 * 0322 * @param StreamInterface $stream Stream to read 0323 * @param int $maxLen Maximum number of bytes to read. Pass -1 0324 * to read the entire stream. 0325 * @return string 0326 * @throws \RuntimeException on error. 0327 */ 0328 function copy_to_string(StreamInterface $stream, $maxLen = -1) 0329 { 0330 $buffer = ''; 0331 0332 if ($maxLen === -1) { 0333 while (!$stream->eof()) { 0334 $buf = $stream->read(1048576); 0335 // Using a loose equality here to match on '' and false. 0336 if ($buf == null) { 0337 break; 0338 } 0339 $buffer .= $buf; 0340 } 0341 return $buffer; 0342 } 0343 0344 $len = 0; 0345 while (!$stream->eof() && $len < $maxLen) { 0346 $buf = $stream->read($maxLen - $len); 0347 // Using a loose equality here to match on '' and false. 0348 if ($buf == null) { 0349 break; 0350 } 0351 $buffer .= $buf; 0352 $len = strlen($buffer); 0353 } 0354 0355 return $buffer; 0356 } 0357 0358 /** 0359 * Copy the contents of a stream into another stream until the given number 0360 * of bytes have been read. 0361 * 0362 * @param StreamInterface $source Stream to read from 0363 * @param StreamInterface $dest Stream to write to 0364 * @param int $maxLen Maximum number of bytes to read. Pass -1 0365 * to read the entire stream. 0366 * 0367 * @throws \RuntimeException on error. 0368 */ 0369 function copy_to_stream( 0370 StreamInterface $source, 0371 StreamInterface $dest, 0372 $maxLen = -1 0373 ) { 0374 $bufferSize = 8192; 0375 0376 if ($maxLen === -1) { 0377 while (!$source->eof()) { 0378 if (!$dest->write($source->read($bufferSize))) { 0379 break; 0380 } 0381 } 0382 } else { 0383 $remaining = $maxLen; 0384 while ($remaining > 0 && !$source->eof()) { 0385 $buf = $source->read(min($bufferSize, $remaining)); 0386 $len = strlen($buf); 0387 if (!$len) { 0388 break; 0389 } 0390 $remaining -= $len; 0391 $dest->write($buf); 0392 } 0393 } 0394 } 0395 0396 /** 0397 * Calculate a hash of a Stream 0398 * 0399 * @param StreamInterface $stream Stream to calculate the hash for 0400 * @param string $algo Hash algorithm (e.g. md5, crc32, etc) 0401 * @param bool $rawOutput Whether or not to use raw output 0402 * 0403 * @return string Returns the hash of the stream 0404 * @throws \RuntimeException on error. 0405 */ 0406 function hash( 0407 StreamInterface $stream, 0408 $algo, 0409 $rawOutput = false 0410 ) { 0411 $pos = $stream->tell(); 0412 0413 if ($pos > 0) { 0414 $stream->rewind(); 0415 } 0416 0417 $ctx = hash_init($algo); 0418 while (!$stream->eof()) { 0419 hash_update($ctx, $stream->read(1048576)); 0420 } 0421 0422 $out = hash_final($ctx, (bool) $rawOutput); 0423 $stream->seek($pos); 0424 0425 return $out; 0426 } 0427 0428 /** 0429 * Read a line from the stream up to the maximum allowed buffer length 0430 * 0431 * @param StreamInterface $stream Stream to read from 0432 * @param int $maxLength Maximum buffer length 0433 * 0434 * @return string|bool 0435 */ 0436 function readline(StreamInterface $stream, $maxLength = null) 0437 { 0438 $buffer = ''; 0439 $size = 0; 0440 0441 while (!$stream->eof()) { 0442 // Using a loose equality here to match on '' and false. 0443 if (null == ($byte = $stream->read(1))) { 0444 return $buffer; 0445 } 0446 $buffer .= $byte; 0447 // Break when a new line is found or the max length - 1 is reached 0448 if ($byte === "\n" || ++$size === $maxLength - 1) { 0449 break; 0450 } 0451 } 0452 0453 return $buffer; 0454 } 0455 0456 /** 0457 * Parses a request message string into a request object. 0458 * 0459 * @param string $message Request message string. 0460 * 0461 * @return Request 0462 */ 0463 function parse_request($message) 0464 { 0465 $data = _parse_message($message); 0466 $matches = []; 0467 if (!preg_match('/^[\S]+\s+([a-zA-Z]+:\/\/|\/).*/', $data['start-line'], $matches)) { 0468 throw new \InvalidArgumentException('Invalid request string'); 0469 } 0470 $parts = explode(' ', $data['start-line'], 3); 0471 $version = isset($parts[2]) ? explode('/', $parts[2])[1] : '1.1'; 0472 0473 $request = new Request( 0474 $parts[0], 0475 $matches[1] === '/' ? _parse_request_uri($parts[1], $data['headers']) : $parts[1], 0476 $data['headers'], 0477 $data['body'], 0478 $version 0479 ); 0480 0481 return $matches[1] === '/' ? $request : $request->withRequestTarget($parts[1]); 0482 } 0483 0484 /** 0485 * Parses a response message string into a response object. 0486 * 0487 * @param string $message Response message string. 0488 * 0489 * @return Response 0490 */ 0491 function parse_response($message) 0492 { 0493 $data = _parse_message($message); 0494 // According to https://tools.ietf.org/html/rfc7230#section-3.1.2 the space 0495 // between status-code and reason-phrase is required. But browsers accept 0496 // responses without space and reason as well. 0497 if (!preg_match('/^HTTP\/.* [0-9]{3}( .*|$)/', $data['start-line'])) { 0498 throw new \InvalidArgumentException('Invalid response string'); 0499 } 0500 $parts = explode(' ', $data['start-line'], 3); 0501 0502 return new Response( 0503 $parts[1], 0504 $data['headers'], 0505 $data['body'], 0506 explode('/', $parts[0])[1], 0507 isset($parts[2]) ? $parts[2] : null 0508 ); 0509 } 0510 0511 /** 0512 * Parse a query string into an associative array. 0513 * 0514 * If multiple values are found for the same key, the value of that key 0515 * value pair will become an array. This function does not parse nested 0516 * PHP style arrays into an associative array (e.g., foo[a]=1&foo[b]=2 will 0517 * be parsed into ['foo[a]' => '1', 'foo[b]' => '2']). 0518 * 0519 * @param string $str Query string to parse 0520 * @param bool|string $urlEncoding How the query string is encoded 0521 * 0522 * @return array 0523 */ 0524 function parse_query($str, $urlEncoding = true) 0525 { 0526 $result = []; 0527 0528 if ($str === '') { 0529 return $result; 0530 } 0531 0532 if ($urlEncoding === true) { 0533 $decoder = function ($value) { 0534 return rawurldecode(str_replace('+', ' ', $value)); 0535 }; 0536 } elseif ($urlEncoding == PHP_QUERY_RFC3986) { 0537 $decoder = 'rawurldecode'; 0538 } elseif ($urlEncoding == PHP_QUERY_RFC1738) { 0539 $decoder = 'urldecode'; 0540 } else { 0541 $decoder = function ($str) { return $str; }; 0542 } 0543 0544 foreach (explode('&', $str) as $kvp) { 0545 $parts = explode('=', $kvp, 2); 0546 $key = $decoder($parts[0]); 0547 $value = isset($parts[1]) ? $decoder($parts[1]) : null; 0548 if (!isset($result[$key])) { 0549 $result[$key] = $value; 0550 } else { 0551 if (!is_array($result[$key])) { 0552 $result[$key] = [$result[$key]]; 0553 } 0554 $result[$key][] = $value; 0555 } 0556 } 0557 0558 return $result; 0559 } 0560 0561 /** 0562 * Build a query string from an array of key value pairs. 0563 * 0564 * This function can use the return value of parse_query() to build a query 0565 * string. This function does not modify the provided keys when an array is 0566 * encountered (like http_build_query would). 0567 * 0568 * @param array $params Query string parameters. 0569 * @param int|false $encoding Set to false to not encode, PHP_QUERY_RFC3986 0570 * to encode using RFC3986, or PHP_QUERY_RFC1738 0571 * to encode using RFC1738. 0572 * @return string 0573 */ 0574 function build_query(array $params, $encoding = PHP_QUERY_RFC3986) 0575 { 0576 if (!$params) { 0577 return ''; 0578 } 0579 0580 if ($encoding === false) { 0581 $encoder = function ($str) { return $str; }; 0582 } elseif ($encoding === PHP_QUERY_RFC3986) { 0583 $encoder = 'rawurlencode'; 0584 } elseif ($encoding === PHP_QUERY_RFC1738) { 0585 $encoder = 'urlencode'; 0586 } else { 0587 throw new \InvalidArgumentException('Invalid type'); 0588 } 0589 0590 $qs = ''; 0591 foreach ($params as $k => $v) { 0592 $k = $encoder($k); 0593 if (!is_array($v)) { 0594 $qs .= $k; 0595 if ($v !== null) { 0596 $qs .= '=' . $encoder($v); 0597 } 0598 $qs .= '&'; 0599 } else { 0600 foreach ($v as $vv) { 0601 $qs .= $k; 0602 if ($vv !== null) { 0603 $qs .= '=' . $encoder($vv); 0604 } 0605 $qs .= '&'; 0606 } 0607 } 0608 } 0609 0610 return $qs ? (string) substr($qs, 0, -1) : ''; 0611 } 0612 0613 /** 0614 * Determines the mimetype of a file by looking at its extension. 0615 * 0616 * @param $filename 0617 * 0618 * @return null|string 0619 */ 0620 function mimetype_from_filename($filename) 0621 { 0622 return mimetype_from_extension(pathinfo($filename, PATHINFO_EXTENSION)); 0623 } 0624 0625 /** 0626 * Maps a file extensions to a mimetype. 0627 * 0628 * @param $extension string The file extension. 0629 * 0630 * @return string|null 0631 * @link http://svn.apache.org/repos/asf/httpd/httpd/branches/1.3.x/conf/mime.types 0632 */ 0633 function mimetype_from_extension($extension) 0634 { 0635 static $mimetypes = [ 0636 '7z' => 'application/x-7z-compressed', 0637 'aac' => 'audio/x-aac', 0638 'ai' => 'application/postscript', 0639 'aif' => 'audio/x-aiff', 0640 'asc' => 'text/plain', 0641 'asf' => 'video/x-ms-asf', 0642 'atom' => 'application/atom+xml', 0643 'avi' => 'video/x-msvideo', 0644 'bmp' => 'image/bmp', 0645 'bz2' => 'application/x-bzip2', 0646 'cer' => 'application/pkix-cert', 0647 'crl' => 'application/pkix-crl', 0648 'crt' => 'application/x-x509-ca-cert', 0649 'css' => 'text/css', 0650 'csv' => 'text/csv', 0651 'cu' => 'application/cu-seeme', 0652 'deb' => 'application/x-debian-package', 0653 'doc' => 'application/msword', 0654 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 0655 'dvi' => 'application/x-dvi', 0656 'eot' => 'application/vnd.ms-fontobject', 0657 'eps' => 'application/postscript', 0658 'epub' => 'application/epub+zip', 0659 'etx' => 'text/x-setext', 0660 'flac' => 'audio/flac', 0661 'flv' => 'video/x-flv', 0662 'gif' => 'image/gif', 0663 'gz' => 'application/gzip', 0664 'htm' => 'text/html', 0665 'html' => 'text/html', 0666 'ico' => 'image/x-icon', 0667 'ics' => 'text/calendar', 0668 'ini' => 'text/plain', 0669 'iso' => 'application/x-iso9660-image', 0670 'jar' => 'application/java-archive', 0671 'jpe' => 'image/jpeg', 0672 'jpeg' => 'image/jpeg', 0673 'jpg' => 'image/jpeg', 0674 'js' => 'text/javascript', 0675 'json' => 'application/json', 0676 'latex' => 'application/x-latex', 0677 'log' => 'text/plain', 0678 'm4a' => 'audio/mp4', 0679 'm4v' => 'video/mp4', 0680 'mid' => 'audio/midi', 0681 'midi' => 'audio/midi', 0682 'mov' => 'video/quicktime', 0683 'mp3' => 'audio/mpeg', 0684 'mp4' => 'video/mp4', 0685 'mp4a' => 'audio/mp4', 0686 'mp4v' => 'video/mp4', 0687 'mpe' => 'video/mpeg', 0688 'mpeg' => 'video/mpeg', 0689 'mpg' => 'video/mpeg', 0690 'mpg4' => 'video/mp4', 0691 'oga' => 'audio/ogg', 0692 'ogg' => 'audio/ogg', 0693 'ogv' => 'video/ogg', 0694 'ogx' => 'application/ogg', 0695 'pbm' => 'image/x-portable-bitmap', 0696 'pdf' => 'application/pdf', 0697 'pgm' => 'image/x-portable-graymap', 0698 'png' => 'image/png', 0699 'pnm' => 'image/x-portable-anymap', 0700 'ppm' => 'image/x-portable-pixmap', 0701 'ppt' => 'application/vnd.ms-powerpoint', 0702 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 0703 'ps' => 'application/postscript', 0704 'qt' => 'video/quicktime', 0705 'rar' => 'application/x-rar-compressed', 0706 'ras' => 'image/x-cmu-raster', 0707 'rss' => 'application/rss+xml', 0708 'rtf' => 'application/rtf', 0709 'sgm' => 'text/sgml', 0710 'sgml' => 'text/sgml', 0711 'svg' => 'image/svg+xml', 0712 'swf' => 'application/x-shockwave-flash', 0713 'tar' => 'application/x-tar', 0714 'tif' => 'image/tiff', 0715 'tiff' => 'image/tiff', 0716 'torrent' => 'application/x-bittorrent', 0717 'ttf' => 'application/x-font-ttf', 0718 'txt' => 'text/plain', 0719 'wav' => 'audio/x-wav', 0720 'webm' => 'video/webm', 0721 'wma' => 'audio/x-ms-wma', 0722 'wmv' => 'video/x-ms-wmv', 0723 'woff' => 'application/x-font-woff', 0724 'wsdl' => 'application/wsdl+xml', 0725 'xbm' => 'image/x-xbitmap', 0726 'xls' => 'application/vnd.ms-excel', 0727 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 0728 'xml' => 'application/xml', 0729 'xpm' => 'image/x-xpixmap', 0730 'xwd' => 'image/x-xwindowdump', 0731 'yaml' => 'text/yaml', 0732 'yml' => 'text/yaml', 0733 'zip' => 'application/zip', 0734 ]; 0735 0736 $extension = strtolower($extension); 0737 0738 return isset($mimetypes[$extension]) 0739 ? $mimetypes[$extension] 0740 : null; 0741 } 0742 0743 /** 0744 * Parses an HTTP message into an associative array. 0745 * 0746 * The array contains the "start-line" key containing the start line of 0747 * the message, "headers" key containing an associative array of header 0748 * array values, and a "body" key containing the body of the message. 0749 * 0750 * @param string $message HTTP request or response to parse. 0751 * 0752 * @return array 0753 * @internal 0754 */ 0755 function _parse_message($message) 0756 { 0757 if (!$message) { 0758 throw new \InvalidArgumentException('Invalid message'); 0759 } 0760 0761 // Iterate over each line in the message, accounting for line endings 0762 $lines = preg_split('/(\\r?\\n)/', $message, -1, PREG_SPLIT_DELIM_CAPTURE); 0763 $result = ['start-line' => array_shift($lines), 'headers' => [], 'body' => '']; 0764 array_shift($lines); 0765 0766 for ($i = 0, $totalLines = count($lines); $i < $totalLines; $i += 2) { 0767 $line = $lines[$i]; 0768 // If two line breaks were encountered, then this is the end of body 0769 if (empty($line)) { 0770 if ($i < $totalLines - 1) { 0771 $result['body'] = implode('', array_slice($lines, $i + 2)); 0772 } 0773 break; 0774 } 0775 if (strpos($line, ':')) { 0776 $parts = explode(':', $line, 2); 0777 $key = trim($parts[0]); 0778 $value = isset($parts[1]) ? trim($parts[1]) : ''; 0779 $result['headers'][$key][] = $value; 0780 } 0781 } 0782 0783 return $result; 0784 } 0785 0786 /** 0787 * Constructs a URI for an HTTP request message. 0788 * 0789 * @param string $path Path from the start-line 0790 * @param array $headers Array of headers (each value an array). 0791 * 0792 * @return string 0793 * @internal 0794 */ 0795 function _parse_request_uri($path, array $headers) 0796 { 0797 $hostKey = array_filter(array_keys($headers), function ($k) { 0798 return strtolower($k) === 'host'; 0799 }); 0800 0801 // If no host is found, then a full URI cannot be constructed. 0802 if (!$hostKey) { 0803 return $path; 0804 } 0805 0806 $host = $headers[reset($hostKey)][0]; 0807 $scheme = substr($host, -4) === ':443' ? 'https' : 'http'; 0808 0809 return $scheme . '://' . $host . '/' . ltrim($path, '/'); 0810 } 0811 0812 /** @internal */ 0813 function _caseless_remove($keys, array $data) 0814 { 0815 $result = []; 0816 0817 foreach ($keys as &$key) { 0818 $key = strtolower($key); 0819 } 0820 0821 foreach ($data as $k => $v) { 0822 if (!in_array(strtolower($k), $keys)) { 0823 $result[$k] = $v; 0824 } 0825 } 0826 0827 return $result; 0828 }