functions.php 24 KB

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