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 }