"Fossies" - the Fresh Open Source Software Archive

Member "grav/vendor/guzzlehttp/psr7/src/functions.php" (1 Sep 2020, 26687 Bytes) of package /linux/www/grav-v1.6.27.zip:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) PHP source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "functions.php" see the Fossies "Dox" file reference documentation.

    1 <?php
    2 namespace GuzzleHttp\Psr7;
    3 
    4 use Psr\Http\Message\MessageInterface;
    5 use Psr\Http\Message\RequestInterface;
    6 use Psr\Http\Message\ResponseInterface;
    7 use Psr\Http\Message\ServerRequestInterface;
    8 use Psr\Http\Message\StreamInterface;
    9 use Psr\Http\Message\UriInterface;
   10 
   11 /**
   12  * Returns the string representation of an HTTP message.
   13  *
   14  * @param MessageInterface $message Message to convert to a string.
   15  *
   16  * @return string
   17  */
   18 function str(MessageInterface $message)
   19 {
   20     if ($message instanceof RequestInterface) {
   21         $msg = trim($message->getMethod() . ' '
   22                 . $message->getRequestTarget())
   23             . ' HTTP/' . $message->getProtocolVersion();
   24         if (!$message->hasHeader('host')) {
   25             $msg .= "\r\nHost: " . $message->getUri()->getHost();
   26         }
   27     } elseif ($message instanceof ResponseInterface) {
   28         $msg = 'HTTP/' . $message->getProtocolVersion() . ' '
   29             . $message->getStatusCode() . ' '
   30             . $message->getReasonPhrase();
   31     } else {
   32         throw new \InvalidArgumentException('Unknown message type');
   33     }
   34 
   35     foreach ($message->getHeaders() as $name => $values) {
   36         $msg .= "\r\n{$name}: " . implode(', ', $values);
   37     }
   38 
   39     return "{$msg}\r\n\r\n" . $message->getBody();
   40 }
   41 
   42 /**
   43  * Returns a UriInterface for the given value.
   44  *
   45  * This function accepts a string or {@see Psr\Http\Message\UriInterface} and
   46  * returns a UriInterface for the given value. If the value is already a
   47  * `UriInterface`, it is returned as-is.
   48  *
   49  * @param string|UriInterface $uri
   50  *
   51  * @return UriInterface
   52  * @throws \InvalidArgumentException
   53  */
   54 function uri_for($uri)
   55 {
   56     if ($uri instanceof UriInterface) {
   57         return $uri;
   58     } elseif (is_string($uri)) {
   59         return new Uri($uri);
   60     }
   61 
   62     throw new \InvalidArgumentException('URI must be a string or UriInterface');
   63 }
   64 
   65 /**
   66  * Create a new stream based on the input type.
   67  *
   68  * Options is an associative array that can contain the following keys:
   69  * - metadata: Array of custom metadata.
   70  * - size: Size of the stream.
   71  *
   72  * @param resource|string|null|int|float|bool|StreamInterface|callable|\Iterator $resource Entity body data
   73  * @param array                                                                  $options  Additional options
   74  *
   75  * @return StreamInterface
   76  * @throws \InvalidArgumentException if the $resource arg is not valid.
   77  */
   78 function stream_for($resource = '', array $options = [])
   79 {
   80     if (is_scalar($resource)) {
   81         $stream = fopen('php://temp', 'r+');
   82         if ($resource !== '') {
   83             fwrite($stream, $resource);
   84             fseek($stream, 0);
   85         }
   86         return new Stream($stream, $options);
   87     }
   88 
   89     switch (gettype($resource)) {
   90         case 'resource':
   91             return new Stream($resource, $options);
   92         case 'object':
   93             if ($resource instanceof StreamInterface) {
   94                 return $resource;
   95             } elseif ($resource instanceof \Iterator) {
   96                 return new PumpStream(function () use ($resource) {
   97                     if (!$resource->valid()) {
   98                         return false;
   99                     }
  100                     $result = $resource->current();
  101                     $resource->next();
  102                     return $result;
  103                 }, $options);
  104             } elseif (method_exists($resource, '__toString')) {
  105                 return stream_for((string) $resource, $options);
  106             }
  107             break;
  108         case 'NULL':
  109             return new Stream(fopen('php://temp', 'r+'), $options);
  110     }
  111 
  112     if (is_callable($resource)) {
  113         return new PumpStream($resource, $options);
  114     }
  115 
  116     throw new \InvalidArgumentException('Invalid resource type: ' . gettype($resource));
  117 }
  118 
  119 /**
  120  * Parse an array of header values containing ";" separated data into an
  121  * array of associative arrays representing the header key value pair
  122  * data of the header. When a parameter does not contain a value, but just
  123  * contains a key, this function will inject a key with a '' string value.
  124  *
  125  * @param string|array $header Header to parse into components.
  126  *
  127  * @return array Returns the parsed header values.
  128  */
  129 function parse_header($header)
  130 {
  131     static $trimmed = "\"'  \n\t\r";
  132     $params = $matches = [];
  133 
  134     foreach (normalize_header($header) as $val) {
  135         $part = [];
  136         foreach (preg_split('/;(?=([^"]*"[^"]*")*[^"]*$)/', $val) as $kvp) {
  137             if (preg_match_all('/<[^>]+>|[^=]+/', $kvp, $matches)) {
  138                 $m = $matches[0];
  139                 if (isset($m[1])) {
  140                     $part[trim($m[0], $trimmed)] = trim($m[1], $trimmed);
  141                 } else {
  142                     $part[] = trim($m[0], $trimmed);
  143                 }
  144             }
  145         }
  146         if ($part) {
  147             $params[] = $part;
  148         }
  149     }
  150 
  151     return $params;
  152 }
  153 
  154 /**
  155  * Converts an array of header values that may contain comma separated
  156  * headers into an array of headers with no comma separated values.
  157  *
  158  * @param string|array $header Header to normalize.
  159  *
  160  * @return array Returns the normalized header field values.
  161  */
  162 function normalize_header($header)
  163 {
  164     if (!is_array($header)) {
  165         return array_map('trim', explode(',', $header));
  166     }
  167 
  168     $result = [];
  169     foreach ($header as $value) {
  170         foreach ((array) $value as $v) {
  171             if (strpos($v, ',') === false) {
  172                 $result[] = $v;
  173                 continue;
  174             }
  175             foreach (preg_split('/,(?=([^"]*"[^"]*")*[^"]*$)/', $v) as $vv) {
  176                 $result[] = trim($vv);
  177             }
  178         }
  179     }
  180 
  181     return $result;
  182 }
  183 
  184 /**
  185  * Clone and modify a request with the given changes.
  186  *
  187  * The changes can be one of:
  188  * - method: (string) Changes the HTTP method.
  189  * - set_headers: (array) Sets the given headers.
  190  * - remove_headers: (array) Remove the given headers.
  191  * - body: (mixed) Sets the given body.
  192  * - uri: (UriInterface) Set the URI.
  193  * - query: (string) Set the query string value of the URI.
  194  * - version: (string) Set the protocol version.
  195  *
  196  * @param RequestInterface $request Request to clone and modify.
  197  * @param array            $changes Changes to apply.
  198  *
  199  * @return RequestInterface
  200  */
  201 function modify_request(RequestInterface $request, array $changes)
  202 {
  203     if (!$changes) {
  204         return $request;
  205     }
  206 
  207     $headers = $request->getHeaders();
  208 
  209     if (!isset($changes['uri'])) {
  210         $uri = $request->getUri();
  211     } else {
  212         // Remove the host header if one is on the URI
  213         if ($host = $changes['uri']->getHost()) {
  214             $changes['set_headers']['Host'] = $host;
  215 
  216             if ($port = $changes['uri']->getPort()) {
  217                 $standardPorts = ['http' => 80, 'https' => 443];
  218                 $scheme = $changes['uri']->getScheme();
  219                 if (isset($standardPorts[$scheme]) && $port != $standardPorts[$scheme]) {
  220                     $changes['set_headers']['Host'] .= ':'.$port;
  221                 }
  222             }
  223         }
  224         $uri = $changes['uri'];
  225     }
  226 
  227     if (!empty($changes['remove_headers'])) {
  228         $headers = _caseless_remove($changes['remove_headers'], $headers);
  229     }
  230 
  231     if (!empty($changes['set_headers'])) {
  232         $headers = _caseless_remove(array_keys($changes['set_headers']), $headers);
  233         $headers = $changes['set_headers'] + $headers;
  234     }
  235 
  236     if (isset($changes['query'])) {
  237         $uri = $uri->withQuery($changes['query']);
  238     }
  239 
  240     if ($request instanceof ServerRequestInterface) {
  241         return (new ServerRequest(
  242             isset($changes['method']) ? $changes['method'] : $request->getMethod(),
  243             $uri,
  244             $headers,
  245             isset($changes['body']) ? $changes['body'] : $request->getBody(),
  246             isset($changes['version'])
  247                 ? $changes['version']
  248                 : $request->getProtocolVersion(),
  249             $request->getServerParams()
  250         ))
  251         ->withParsedBody($request->getParsedBody())
  252         ->withQueryParams($request->getQueryParams())
  253         ->withCookieParams($request->getCookieParams())
  254         ->withUploadedFiles($request->getUploadedFiles());
  255     }
  256 
  257     return new Request(
  258         isset($changes['method']) ? $changes['method'] : $request->getMethod(),
  259         $uri,
  260         $headers,
  261         isset($changes['body']) ? $changes['body'] : $request->getBody(),
  262         isset($changes['version'])
  263             ? $changes['version']
  264             : $request->getProtocolVersion()
  265     );
  266 }
  267 
  268 /**
  269  * Attempts to rewind a message body and throws an exception on failure.
  270  *
  271  * The body of the message will only be rewound if a call to `tell()` returns a
  272  * value other than `0`.
  273  *
  274  * @param MessageInterface $message Message to rewind
  275  *
  276  * @throws \RuntimeException
  277  */
  278 function rewind_body(MessageInterface $message)
  279 {
  280     $body = $message->getBody();
  281 
  282     if ($body->tell()) {
  283         $body->rewind();
  284     }
  285 }
  286 
  287 /**
  288  * Safely opens a PHP stream resource using a filename.
  289  *
  290  * When fopen fails, PHP normally raises a warning. This function adds an
  291  * error handler that checks for errors and throws an exception instead.
  292  *
  293  * @param string $filename File to open
  294  * @param string $mode     Mode used to open the file
  295  *
  296  * @return resource
  297  * @throws \RuntimeException if the file cannot be opened
  298  */
  299 function try_fopen($filename, $mode)
  300 {
  301     $ex = null;
  302     set_error_handler(function () use ($filename, $mode, &$ex) {
  303         $ex = new \RuntimeException(sprintf(
  304             'Unable to open %s using mode %s: %s',
  305             $filename,
  306             $mode,
  307             func_get_args()[1]
  308         ));
  309     });
  310 
  311     $handle = fopen($filename, $mode);
  312     restore_error_handler();
  313 
  314     if ($ex) {
  315         /** @var $ex \RuntimeException */
  316         throw $ex;
  317     }
  318 
  319     return $handle;
  320 }
  321 
  322 /**
  323  * Copy the contents of a stream into a string until the given number of
  324  * bytes have been read.
  325  *
  326  * @param StreamInterface $stream Stream to read
  327  * @param int             $maxLen Maximum number of bytes to read. Pass -1
  328  *                                to read the entire stream.
  329  * @return string
  330  * @throws \RuntimeException on error.
  331  */
  332 function copy_to_string(StreamInterface $stream, $maxLen = -1)
  333 {
  334     $buffer = '';
  335 
  336     if ($maxLen === -1) {
  337         while (!$stream->eof()) {
  338             $buf = $stream->read(1048576);
  339             // Using a loose equality here to match on '' and false.
  340             if ($buf == null) {
  341                 break;
  342             }
  343             $buffer .= $buf;
  344         }
  345         return $buffer;
  346     }
  347 
  348     $len = 0;
  349     while (!$stream->eof() && $len < $maxLen) {
  350         $buf = $stream->read($maxLen - $len);
  351         // Using a loose equality here to match on '' and false.
  352         if ($buf == null) {
  353             break;
  354         }
  355         $buffer .= $buf;
  356         $len = strlen($buffer);
  357     }
  358 
  359     return $buffer;
  360 }
  361 
  362 /**
  363  * Copy the contents of a stream into another stream until the given number
  364  * of bytes have been read.
  365  *
  366  * @param StreamInterface $source Stream to read from
  367  * @param StreamInterface $dest   Stream to write to
  368  * @param int             $maxLen Maximum number of bytes to read. Pass -1
  369  *                                to read the entire stream.
  370  *
  371  * @throws \RuntimeException on error.
  372  */
  373 function copy_to_stream(
  374     StreamInterface $source,
  375     StreamInterface $dest,
  376     $maxLen = -1
  377 ) {
  378     $bufferSize = 8192;
  379 
  380     if ($maxLen === -1) {
  381         while (!$source->eof()) {
  382             if (!$dest->write($source->read($bufferSize))) {
  383                 break;
  384             }
  385         }
  386     } else {
  387         $remaining = $maxLen;
  388         while ($remaining > 0 && !$source->eof()) {
  389             $buf = $source->read(min($bufferSize, $remaining));
  390             $len = strlen($buf);
  391             if (!$len) {
  392                 break;
  393             }
  394             $remaining -= $len;
  395             $dest->write($buf);
  396         }
  397     }
  398 }
  399 
  400 /**
  401  * Calculate a hash of a Stream
  402  *
  403  * @param StreamInterface $stream    Stream to calculate the hash for
  404  * @param string          $algo      Hash algorithm (e.g. md5, crc32, etc)
  405  * @param bool            $rawOutput Whether or not to use raw output
  406  *
  407  * @return string Returns the hash of the stream
  408  * @throws \RuntimeException on error.
  409  */
  410 function hash(
  411     StreamInterface $stream,
  412     $algo,
  413     $rawOutput = false
  414 ) {
  415     $pos = $stream->tell();
  416 
  417     if ($pos > 0) {
  418         $stream->rewind();
  419     }
  420 
  421     $ctx = hash_init($algo);
  422     while (!$stream->eof()) {
  423         hash_update($ctx, $stream->read(1048576));
  424     }
  425 
  426     $out = hash_final($ctx, (bool) $rawOutput);
  427     $stream->seek($pos);
  428 
  429     return $out;
  430 }
  431 
  432 /**
  433  * Read a line from the stream up to the maximum allowed buffer length
  434  *
  435  * @param StreamInterface $stream    Stream to read from
  436  * @param int             $maxLength Maximum buffer length
  437  *
  438  * @return string
  439  */
  440 function readline(StreamInterface $stream, $maxLength = null)
  441 {
  442     $buffer = '';
  443     $size = 0;
  444 
  445     while (!$stream->eof()) {
  446         // Using a loose equality here to match on '' and false.
  447         if (null == ($byte = $stream->read(1))) {
  448             return $buffer;
  449         }
  450         $buffer .= $byte;
  451         // Break when a new line is found or the max length - 1 is reached
  452         if ($byte === "\n" || ++$size === $maxLength - 1) {
  453             break;
  454         }
  455     }
  456 
  457     return $buffer;
  458 }
  459 
  460 /**
  461  * Parses a request message string into a request object.
  462  *
  463  * @param string $message Request message string.
  464  *
  465  * @return Request
  466  */
  467 function parse_request($message)
  468 {
  469     $data = _parse_message($message);
  470     $matches = [];
  471     if (!preg_match('/^[\S]+\s+([a-zA-Z]+:\/\/|\/).*/', $data['start-line'], $matches)) {
  472         throw new \InvalidArgumentException('Invalid request string');
  473     }
  474     $parts = explode(' ', $data['start-line'], 3);
  475     $version = isset($parts[2]) ? explode('/', $parts[2])[1] : '1.1';
  476 
  477     $request = new Request(
  478         $parts[0],
  479         $matches[1] === '/' ? _parse_request_uri($parts[1], $data['headers']) : $parts[1],
  480         $data['headers'],
  481         $data['body'],
  482         $version
  483     );
  484 
  485     return $matches[1] === '/' ? $request : $request->withRequestTarget($parts[1]);
  486 }
  487 
  488 /**
  489  * Parses a response message string into a response object.
  490  *
  491  * @param string $message Response message string.
  492  *
  493  * @return Response
  494  */
  495 function parse_response($message)
  496 {
  497     $data = _parse_message($message);
  498     // According to https://tools.ietf.org/html/rfc7230#section-3.1.2 the space
  499     // between status-code and reason-phrase is required. But browsers accept
  500     // responses without space and reason as well.
  501     if (!preg_match('/^HTTP\/.* [0-9]{3}( .*|$)/', $data['start-line'])) {
  502         throw new \InvalidArgumentException('Invalid response string: ' . $data['start-line']);
  503     }
  504     $parts = explode(' ', $data['start-line'], 3);
  505 
  506     return new Response(
  507         $parts[1],
  508         $data['headers'],
  509         $data['body'],
  510         explode('/', $parts[0])[1],
  511         isset($parts[2]) ? $parts[2] : null
  512     );
  513 }
  514 
  515 /**
  516  * Parse a query string into an associative array.
  517  *
  518  * If multiple values are found for the same key, the value of that key
  519  * value pair will become an array. This function does not parse nested
  520  * PHP style arrays into an associative array (e.g., foo[a]=1&foo[b]=2 will
  521  * be parsed into ['foo[a]' => '1', 'foo[b]' => '2']).
  522  *
  523  * @param string   $str         Query string to parse
  524  * @param int|bool $urlEncoding How the query string is encoded
  525  *
  526  * @return array
  527  */
  528 function parse_query($str, $urlEncoding = true)
  529 {
  530     $result = [];
  531 
  532     if ($str === '') {
  533         return $result;
  534     }
  535 
  536     if ($urlEncoding === true) {
  537         $decoder = function ($value) {
  538             return rawurldecode(str_replace('+', ' ', $value));
  539         };
  540     } elseif ($urlEncoding === PHP_QUERY_RFC3986) {
  541         $decoder = 'rawurldecode';
  542     } elseif ($urlEncoding === PHP_QUERY_RFC1738) {
  543         $decoder = 'urldecode';
  544     } else {
  545         $decoder = function ($str) { return $str; };
  546     }
  547 
  548     foreach (explode('&', $str) as $kvp) {
  549         $parts = explode('=', $kvp, 2);
  550         $key = $decoder($parts[0]);
  551         $value = isset($parts[1]) ? $decoder($parts[1]) : null;
  552         if (!isset($result[$key])) {
  553             $result[$key] = $value;
  554         } else {
  555             if (!is_array($result[$key])) {
  556                 $result[$key] = [$result[$key]];
  557             }
  558             $result[$key][] = $value;
  559         }
  560     }
  561 
  562     return $result;
  563 }
  564 
  565 /**
  566  * Build a query string from an array of key value pairs.
  567  *
  568  * This function can use the return value of parse_query() to build a query
  569  * string. This function does not modify the provided keys when an array is
  570  * encountered (like http_build_query would).
  571  *
  572  * @param array     $params   Query string parameters.
  573  * @param int|false $encoding Set to false to not encode, PHP_QUERY_RFC3986
  574  *                            to encode using RFC3986, or PHP_QUERY_RFC1738
  575  *                            to encode using RFC1738.
  576  * @return string
  577  */
  578 function build_query(array $params, $encoding = PHP_QUERY_RFC3986)
  579 {
  580     if (!$params) {
  581         return '';
  582     }
  583 
  584     if ($encoding === false) {
  585         $encoder = function ($str) { return $str; };
  586     } elseif ($encoding === PHP_QUERY_RFC3986) {
  587         $encoder = 'rawurlencode';
  588     } elseif ($encoding === PHP_QUERY_RFC1738) {
  589         $encoder = 'urlencode';
  590     } else {
  591         throw new \InvalidArgumentException('Invalid type');
  592     }
  593 
  594     $qs = '';
  595     foreach ($params as $k => $v) {
  596         $k = $encoder($k);
  597         if (!is_array($v)) {
  598             $qs .= $k;
  599             if ($v !== null) {
  600                 $qs .= '=' . $encoder($v);
  601             }
  602             $qs .= '&';
  603         } else {
  604             foreach ($v as $vv) {
  605                 $qs .= $k;
  606                 if ($vv !== null) {
  607                     $qs .= '=' . $encoder($vv);
  608                 }
  609                 $qs .= '&';
  610             }
  611         }
  612     }
  613 
  614     return $qs ? (string) substr($qs, 0, -1) : '';
  615 }
  616 
  617 /**
  618  * Determines the mimetype of a file by looking at its extension.
  619  *
  620  * @param $filename
  621  *
  622  * @return null|string
  623  */
  624 function mimetype_from_filename($filename)
  625 {
  626     return mimetype_from_extension(pathinfo($filename, PATHINFO_EXTENSION));
  627 }
  628 
  629 /**
  630  * Maps a file extensions to a mimetype.
  631  *
  632  * @param $extension string The file extension.
  633  *
  634  * @return string|null
  635  * @link http://svn.apache.org/repos/asf/httpd/httpd/branches/1.3.x/conf/mime.types
  636  */
  637 function mimetype_from_extension($extension)
  638 {
  639     static $mimetypes = [
  640         '3gp' => 'video/3gpp',
  641         '7z' => 'application/x-7z-compressed',
  642         'aac' => 'audio/x-aac',
  643         'ai' => 'application/postscript',
  644         'aif' => 'audio/x-aiff',
  645         'asc' => 'text/plain',
  646         'asf' => 'video/x-ms-asf',
  647         'atom' => 'application/atom+xml',
  648         'avi' => 'video/x-msvideo',
  649         'bmp' => 'image/bmp',
  650         'bz2' => 'application/x-bzip2',
  651         'cer' => 'application/pkix-cert',
  652         'crl' => 'application/pkix-crl',
  653         'crt' => 'application/x-x509-ca-cert',
  654         'css' => 'text/css',
  655         'csv' => 'text/csv',
  656         'cu' => 'application/cu-seeme',
  657         'deb' => 'application/x-debian-package',
  658         'doc' => 'application/msword',
  659         'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
  660         'dvi' => 'application/x-dvi',
  661         'eot' => 'application/vnd.ms-fontobject',
  662         'eps' => 'application/postscript',
  663         'epub' => 'application/epub+zip',
  664         'etx' => 'text/x-setext',
  665         'flac' => 'audio/flac',
  666         'flv' => 'video/x-flv',
  667         'gif' => 'image/gif',
  668         'gz' => 'application/gzip',
  669         'htm' => 'text/html',
  670         'html' => 'text/html',
  671         'ico' => 'image/x-icon',
  672         'ics' => 'text/calendar',
  673         'ini' => 'text/plain',
  674         'iso' => 'application/x-iso9660-image',
  675         'jar' => 'application/java-archive',
  676         'jpe' => 'image/jpeg',
  677         'jpeg' => 'image/jpeg',
  678         'jpg' => 'image/jpeg',
  679         'js' => 'text/javascript',
  680         'json' => 'application/json',
  681         'latex' => 'application/x-latex',
  682         'log' => 'text/plain',
  683         'm4a' => 'audio/mp4',
  684         'm4v' => 'video/mp4',
  685         'mid' => 'audio/midi',
  686         'midi' => 'audio/midi',
  687         'mov' => 'video/quicktime',
  688         'mkv' => 'video/x-matroska',
  689         'mp3' => 'audio/mpeg',
  690         'mp4' => 'video/mp4',
  691         'mp4a' => 'audio/mp4',
  692         'mp4v' => 'video/mp4',
  693         'mpe' => 'video/mpeg',
  694         'mpeg' => 'video/mpeg',
  695         'mpg' => 'video/mpeg',
  696         'mpg4' => 'video/mp4',
  697         'oga' => 'audio/ogg',
  698         'ogg' => 'audio/ogg',
  699         'ogv' => 'video/ogg',
  700         'ogx' => 'application/ogg',
  701         'pbm' => 'image/x-portable-bitmap',
  702         'pdf' => 'application/pdf',
  703         'pgm' => 'image/x-portable-graymap',
  704         'png' => 'image/png',
  705         'pnm' => 'image/x-portable-anymap',
  706         'ppm' => 'image/x-portable-pixmap',
  707         'ppt' => 'application/vnd.ms-powerpoint',
  708         'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
  709         'ps' => 'application/postscript',
  710         'qt' => 'video/quicktime',
  711         'rar' => 'application/x-rar-compressed',
  712         'ras' => 'image/x-cmu-raster',
  713         'rss' => 'application/rss+xml',
  714         'rtf' => 'application/rtf',
  715         'sgm' => 'text/sgml',
  716         'sgml' => 'text/sgml',
  717         'svg' => 'image/svg+xml',
  718         'swf' => 'application/x-shockwave-flash',
  719         'tar' => 'application/x-tar',
  720         'tif' => 'image/tiff',
  721         'tiff' => 'image/tiff',
  722         'torrent' => 'application/x-bittorrent',
  723         'ttf' => 'application/x-font-ttf',
  724         'txt' => 'text/plain',
  725         'wav' => 'audio/x-wav',
  726         'webm' => 'video/webm',
  727         'webp' => 'image/webp',
  728         'wma' => 'audio/x-ms-wma',
  729         'wmv' => 'video/x-ms-wmv',
  730         'woff' => 'application/x-font-woff',
  731         'wsdl' => 'application/wsdl+xml',
  732         'xbm' => 'image/x-xbitmap',
  733         'xls' => 'application/vnd.ms-excel',
  734         'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
  735         'xml' => 'application/xml',
  736         'xpm' => 'image/x-xpixmap',
  737         'xwd' => 'image/x-xwindowdump',
  738         'yaml' => 'text/yaml',
  739         'yml' => 'text/yaml',
  740         'zip' => 'application/zip',
  741     ];
  742 
  743     $extension = strtolower($extension);
  744 
  745     return isset($mimetypes[$extension])
  746         ? $mimetypes[$extension]
  747         : null;
  748 }
  749 
  750 /**
  751  * Parses an HTTP message into an associative array.
  752  *
  753  * The array contains the "start-line" key containing the start line of
  754  * the message, "headers" key containing an associative array of header
  755  * array values, and a "body" key containing the body of the message.
  756  *
  757  * @param string $message HTTP request or response to parse.
  758  *
  759  * @return array
  760  * @internal
  761  */
  762 function _parse_message($message)
  763 {
  764     if (!$message) {
  765         throw new \InvalidArgumentException('Invalid message');
  766     }
  767 
  768     $message = ltrim($message, "\r\n");
  769 
  770     $messageParts = preg_split("/\r?\n\r?\n/", $message, 2);
  771 
  772     if ($messageParts === false || count($messageParts) !== 2) {
  773         throw new \InvalidArgumentException('Invalid message: Missing header delimiter');
  774     }
  775 
  776     list($rawHeaders, $body) = $messageParts;
  777     $rawHeaders .= "\r\n"; // Put back the delimiter we split previously
  778     $headerParts = preg_split("/\r?\n/", $rawHeaders, 2);
  779 
  780     if ($headerParts === false || count($headerParts) !== 2) {
  781         throw new \InvalidArgumentException('Invalid message: Missing status line');
  782     }
  783 
  784     list($startLine, $rawHeaders) = $headerParts;
  785 
  786     if (preg_match("/(?:^HTTP\/|^[A-Z]+ \S+ HTTP\/)(\d+(?:\.\d+)?)/i", $startLine, $matches) && $matches[1] === '1.0') {
  787         // Header folding is deprecated for HTTP/1.1, but allowed in HTTP/1.0
  788         $rawHeaders = preg_replace(Rfc7230::HEADER_FOLD_REGEX, ' ', $rawHeaders);
  789     }
  790 
  791     /** @var array[] $headerLines */
  792     $count = preg_match_all(Rfc7230::HEADER_REGEX, $rawHeaders, $headerLines, PREG_SET_ORDER);
  793 
  794     // If these aren't the same, then one line didn't match and there's an invalid header.
  795     if ($count !== substr_count($rawHeaders, "\n")) {
  796         // Folding is deprecated, see https://tools.ietf.org/html/rfc7230#section-3.2.4
  797         if (preg_match(Rfc7230::HEADER_FOLD_REGEX, $rawHeaders)) {
  798             throw new \InvalidArgumentException('Invalid header syntax: Obsolete line folding');
  799         }
  800 
  801         throw new \InvalidArgumentException('Invalid header syntax');
  802     }
  803 
  804     $headers = [];
  805 
  806     foreach ($headerLines as $headerLine) {
  807         $headers[$headerLine[1]][] = $headerLine[2];
  808     }
  809 
  810     return [
  811         'start-line' => $startLine,
  812         'headers' => $headers,
  813         'body' => $body,
  814     ];
  815 }
  816 
  817 /**
  818  * Constructs a URI for an HTTP request message.
  819  *
  820  * @param string $path    Path from the start-line
  821  * @param array  $headers Array of headers (each value an array).
  822  *
  823  * @return string
  824  * @internal
  825  */
  826 function _parse_request_uri($path, array $headers)
  827 {
  828     $hostKey = array_filter(array_keys($headers), function ($k) {
  829         return strtolower($k) === 'host';
  830     });
  831 
  832     // If no host is found, then a full URI cannot be constructed.
  833     if (!$hostKey) {
  834         return $path;
  835     }
  836 
  837     $host = $headers[reset($hostKey)][0];
  838     $scheme = substr($host, -4) === ':443' ? 'https' : 'http';
  839 
  840     return $scheme . '://' . $host . '/' . ltrim($path, '/');
  841 }
  842 
  843 /**
  844  * Get a short summary of the message body
  845  *
  846  * Will return `null` if the response is not printable.
  847  *
  848  * @param MessageInterface $message    The message to get the body summary
  849  * @param int              $truncateAt The maximum allowed size of the summary
  850  *
  851  * @return null|string
  852  */
  853 function get_message_body_summary(MessageInterface $message, $truncateAt = 120)
  854 {
  855     $body = $message->getBody();
  856 
  857     if (!$body->isSeekable() || !$body->isReadable()) {
  858         return null;
  859     }
  860 
  861     $size = $body->getSize();
  862 
  863     if ($size === 0) {
  864         return null;
  865     }
  866 
  867     $summary = $body->read($truncateAt);
  868     $body->rewind();
  869 
  870     if ($size > $truncateAt) {
  871         $summary .= ' (truncated...)';
  872     }
  873 
  874     // Matches any printable character, including unicode characters:
  875     // letters, marks, numbers, punctuation, spacing, and separators.
  876     if (preg_match('/[^\pL\pM\pN\pP\pS\pZ\n\r\t]/', $summary)) {
  877         return null;
  878     }
  879 
  880     return $summary;
  881 }
  882 
  883 /** @internal */
  884 function _caseless_remove($keys, array $data)
  885 {
  886     $result = [];
  887 
  888     foreach ($keys as &$key) {
  889         $key = strtolower($key);
  890     }
  891 
  892     foreach ($data as $k => $v) {
  893         if (!in_array(strtolower($k), $keys)) {
  894             $result[$k] = $v;
  895         }
  896     }
  897 
  898     return $result;
  899 }