functions.php 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898
  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|\Iterator $resource Entity body data
  66. * @param array $options Additional options
  67. *
  68. * @return StreamInterface
  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. ->withParsedBody($request->getParsedBody())
  228. ->withQueryParams($request->getQueryParams())
  229. ->withCookieParams($request->getCookieParams())
  230. ->withUploadedFiles($request->getUploadedFiles());
  231. }
  232. return new Request(
  233. isset($changes['method']) ? $changes['method'] : $request->getMethod(),
  234. $uri,
  235. $headers,
  236. isset($changes['body']) ? $changes['body'] : $request->getBody(),
  237. isset($changes['version'])
  238. ? $changes['version']
  239. : $request->getProtocolVersion()
  240. );
  241. }
  242. /**
  243. * Attempts to rewind a message body and throws an exception on failure.
  244. *
  245. * The body of the message will only be rewound if a call to `tell()` returns a
  246. * value other than `0`.
  247. *
  248. * @param MessageInterface $message Message to rewind
  249. *
  250. * @throws \RuntimeException
  251. */
  252. function rewind_body(MessageInterface $message)
  253. {
  254. $body = $message->getBody();
  255. if ($body->tell()) {
  256. $body->rewind();
  257. }
  258. }
  259. /**
  260. * Safely opens a PHP stream resource using a filename.
  261. *
  262. * When fopen fails, PHP normally raises a warning. This function adds an
  263. * error handler that checks for errors and throws an exception instead.
  264. *
  265. * @param string $filename File to open
  266. * @param string $mode Mode used to open the file
  267. *
  268. * @return resource
  269. * @throws \RuntimeException if the file cannot be opened
  270. */
  271. function try_fopen($filename, $mode)
  272. {
  273. $ex = null;
  274. set_error_handler(function () use ($filename, $mode, &$ex) {
  275. $ex = new \RuntimeException(sprintf(
  276. 'Unable to open %s using mode %s: %s',
  277. $filename,
  278. $mode,
  279. func_get_args()[1]
  280. ));
  281. });
  282. $handle = fopen($filename, $mode);
  283. restore_error_handler();
  284. if ($ex) {
  285. /** @var $ex \RuntimeException */
  286. throw $ex;
  287. }
  288. return $handle;
  289. }
  290. /**
  291. * Copy the contents of a stream into a string until the given number of
  292. * bytes have been read.
  293. *
  294. * @param StreamInterface $stream Stream to read
  295. * @param int $maxLen Maximum number of bytes to read. Pass -1
  296. * to read the entire stream.
  297. * @return string
  298. * @throws \RuntimeException on error.
  299. */
  300. function copy_to_string(StreamInterface $stream, $maxLen = -1)
  301. {
  302. $buffer = '';
  303. if ($maxLen === -1) {
  304. while (!$stream->eof()) {
  305. $buf = $stream->read(1048576);
  306. // Using a loose equality here to match on '' and false.
  307. if ($buf == null) {
  308. break;
  309. }
  310. $buffer .= $buf;
  311. }
  312. return $buffer;
  313. }
  314. $len = 0;
  315. while (!$stream->eof() && $len < $maxLen) {
  316. $buf = $stream->read($maxLen - $len);
  317. // Using a loose equality here to match on '' and false.
  318. if ($buf == null) {
  319. break;
  320. }
  321. $buffer .= $buf;
  322. $len = strlen($buffer);
  323. }
  324. return $buffer;
  325. }
  326. /**
  327. * Copy the contents of a stream into another stream until the given number
  328. * of bytes have been read.
  329. *
  330. * @param StreamInterface $source Stream to read from
  331. * @param StreamInterface $dest Stream to write to
  332. * @param int $maxLen Maximum number of bytes to read. Pass -1
  333. * to read the entire stream.
  334. *
  335. * @throws \RuntimeException on error.
  336. */
  337. function copy_to_stream(
  338. StreamInterface $source,
  339. StreamInterface $dest,
  340. $maxLen = -1
  341. ) {
  342. $bufferSize = 8192;
  343. if ($maxLen === -1) {
  344. while (!$source->eof()) {
  345. if (!$dest->write($source->read($bufferSize))) {
  346. break;
  347. }
  348. }
  349. } else {
  350. $remaining = $maxLen;
  351. while ($remaining > 0 && !$source->eof()) {
  352. $buf = $source->read(min($bufferSize, $remaining));
  353. $len = strlen($buf);
  354. if (!$len) {
  355. break;
  356. }
  357. $remaining -= $len;
  358. $dest->write($buf);
  359. }
  360. }
  361. }
  362. /**
  363. * Calculate a hash of a Stream
  364. *
  365. * @param StreamInterface $stream Stream to calculate the hash for
  366. * @param string $algo Hash algorithm (e.g. md5, crc32, etc)
  367. * @param bool $rawOutput Whether or not to use raw output
  368. *
  369. * @return string Returns the hash of the stream
  370. * @throws \RuntimeException on error.
  371. */
  372. function hash(
  373. StreamInterface $stream,
  374. $algo,
  375. $rawOutput = false
  376. ) {
  377. $pos = $stream->tell();
  378. if ($pos > 0) {
  379. $stream->rewind();
  380. }
  381. $ctx = hash_init($algo);
  382. while (!$stream->eof()) {
  383. hash_update($ctx, $stream->read(1048576));
  384. }
  385. $out = hash_final($ctx, (bool) $rawOutput);
  386. $stream->seek($pos);
  387. return $out;
  388. }
  389. /**
  390. * Read a line from the stream up to the maximum allowed buffer length
  391. *
  392. * @param StreamInterface $stream Stream to read from
  393. * @param int $maxLength Maximum buffer length
  394. *
  395. * @return string
  396. */
  397. function readline(StreamInterface $stream, $maxLength = null)
  398. {
  399. $buffer = '';
  400. $size = 0;
  401. while (!$stream->eof()) {
  402. // Using a loose equality here to match on '' and false.
  403. if (null == ($byte = $stream->read(1))) {
  404. return $buffer;
  405. }
  406. $buffer .= $byte;
  407. // Break when a new line is found or the max length - 1 is reached
  408. if ($byte === "\n" || ++$size === $maxLength - 1) {
  409. break;
  410. }
  411. }
  412. return $buffer;
  413. }
  414. /**
  415. * Parses a request message string into a request object.
  416. *
  417. * @param string $message Request message string.
  418. *
  419. * @return Request
  420. */
  421. function parse_request($message)
  422. {
  423. $data = _parse_message($message);
  424. $matches = [];
  425. if (!preg_match('/^[\S]+\s+([a-zA-Z]+:\/\/|\/).*/', $data['start-line'], $matches)) {
  426. throw new \InvalidArgumentException('Invalid request string');
  427. }
  428. $parts = explode(' ', $data['start-line'], 3);
  429. $version = isset($parts[2]) ? explode('/', $parts[2])[1] : '1.1';
  430. $request = new Request(
  431. $parts[0],
  432. $matches[1] === '/' ? _parse_request_uri($parts[1], $data['headers']) : $parts[1],
  433. $data['headers'],
  434. $data['body'],
  435. $version
  436. );
  437. return $matches[1] === '/' ? $request : $request->withRequestTarget($parts[1]);
  438. }
  439. /**
  440. * Parses a response message string into a response object.
  441. *
  442. * @param string $message Response message string.
  443. *
  444. * @return Response
  445. */
  446. function parse_response($message)
  447. {
  448. $data = _parse_message($message);
  449. // According to https://tools.ietf.org/html/rfc7230#section-3.1.2 the space
  450. // between status-code and reason-phrase is required. But browsers accept
  451. // responses without space and reason as well.
  452. if (!preg_match('/^HTTP\/.* [0-9]{3}( .*|$)/', $data['start-line'])) {
  453. throw new \InvalidArgumentException('Invalid response string: ' . $data['start-line']);
  454. }
  455. $parts = explode(' ', $data['start-line'], 3);
  456. return new Response(
  457. $parts[1],
  458. $data['headers'],
  459. $data['body'],
  460. explode('/', $parts[0])[1],
  461. isset($parts[2]) ? $parts[2] : null
  462. );
  463. }
  464. /**
  465. * Parse a query string into an associative array.
  466. *
  467. * If multiple values are found for the same key, the value of that key
  468. * value pair will become an array. This function does not parse nested
  469. * PHP style arrays into an associative array (e.g., foo[a]=1&foo[b]=2 will
  470. * be parsed into ['foo[a]' => '1', 'foo[b]' => '2']).
  471. *
  472. * @param string $str Query string to parse
  473. * @param int|bool $urlEncoding How the query string is encoded
  474. *
  475. * @return array
  476. */
  477. function parse_query($str, $urlEncoding = true)
  478. {
  479. $result = [];
  480. if ($str === '') {
  481. return $result;
  482. }
  483. if ($urlEncoding === true) {
  484. $decoder = function ($value) {
  485. return rawurldecode(str_replace('+', ' ', $value));
  486. };
  487. } elseif ($urlEncoding === PHP_QUERY_RFC3986) {
  488. $decoder = 'rawurldecode';
  489. } elseif ($urlEncoding === PHP_QUERY_RFC1738) {
  490. $decoder = 'urldecode';
  491. } else {
  492. $decoder = function ($str) { return $str; };
  493. }
  494. foreach (explode('&', $str) as $kvp) {
  495. $parts = explode('=', $kvp, 2);
  496. $key = $decoder($parts[0]);
  497. $value = isset($parts[1]) ? $decoder($parts[1]) : null;
  498. if (!isset($result[$key])) {
  499. $result[$key] = $value;
  500. } else {
  501. if (!is_array($result[$key])) {
  502. $result[$key] = [$result[$key]];
  503. }
  504. $result[$key][] = $value;
  505. }
  506. }
  507. return $result;
  508. }
  509. /**
  510. * Build a query string from an array of key value pairs.
  511. *
  512. * This function can use the return value of parse_query() to build a query
  513. * string. This function does not modify the provided keys when an array is
  514. * encountered (like http_build_query would).
  515. *
  516. * @param array $params Query string parameters.
  517. * @param int|false $encoding Set to false to not encode, PHP_QUERY_RFC3986
  518. * to encode using RFC3986, or PHP_QUERY_RFC1738
  519. * to encode using RFC1738.
  520. * @return string
  521. */
  522. function build_query(array $params, $encoding = PHP_QUERY_RFC3986)
  523. {
  524. if (!$params) {
  525. return '';
  526. }
  527. if ($encoding === false) {
  528. $encoder = function ($str) { return $str; };
  529. } elseif ($encoding === PHP_QUERY_RFC3986) {
  530. $encoder = 'rawurlencode';
  531. } elseif ($encoding === PHP_QUERY_RFC1738) {
  532. $encoder = 'urlencode';
  533. } else {
  534. throw new \InvalidArgumentException('Invalid type');
  535. }
  536. $qs = '';
  537. foreach ($params as $k => $v) {
  538. $k = $encoder($k);
  539. if (!is_array($v)) {
  540. $qs .= $k;
  541. if ($v !== null) {
  542. $qs .= '=' . $encoder($v);
  543. }
  544. $qs .= '&';
  545. } else {
  546. foreach ($v as $vv) {
  547. $qs .= $k;
  548. if ($vv !== null) {
  549. $qs .= '=' . $encoder($vv);
  550. }
  551. $qs .= '&';
  552. }
  553. }
  554. }
  555. return $qs ? (string) substr($qs, 0, -1) : '';
  556. }
  557. /**
  558. * Determines the mimetype of a file by looking at its extension.
  559. *
  560. * @param $filename
  561. *
  562. * @return null|string
  563. */
  564. function mimetype_from_filename($filename)
  565. {
  566. return mimetype_from_extension(pathinfo($filename, PATHINFO_EXTENSION));
  567. }
  568. /**
  569. * Maps a file extensions to a mimetype.
  570. *
  571. * @param $extension string The file extension.
  572. *
  573. * @return string|null
  574. * @link http://svn.apache.org/repos/asf/httpd/httpd/branches/1.3.x/conf/mime.types
  575. */
  576. function mimetype_from_extension($extension)
  577. {
  578. static $mimetypes = [
  579. '3gp' => 'video/3gpp',
  580. '7z' => 'application/x-7z-compressed',
  581. 'aac' => 'audio/x-aac',
  582. 'ai' => 'application/postscript',
  583. 'aif' => 'audio/x-aiff',
  584. 'asc' => 'text/plain',
  585. 'asf' => 'video/x-ms-asf',
  586. 'atom' => 'application/atom+xml',
  587. 'avi' => 'video/x-msvideo',
  588. 'bmp' => 'image/bmp',
  589. 'bz2' => 'application/x-bzip2',
  590. 'cer' => 'application/pkix-cert',
  591. 'crl' => 'application/pkix-crl',
  592. 'crt' => 'application/x-x509-ca-cert',
  593. 'css' => 'text/css',
  594. 'csv' => 'text/csv',
  595. 'cu' => 'application/cu-seeme',
  596. 'deb' => 'application/x-debian-package',
  597. 'doc' => 'application/msword',
  598. 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
  599. 'dvi' => 'application/x-dvi',
  600. 'eot' => 'application/vnd.ms-fontobject',
  601. 'eps' => 'application/postscript',
  602. 'epub' => 'application/epub+zip',
  603. 'etx' => 'text/x-setext',
  604. 'flac' => 'audio/flac',
  605. 'flv' => 'video/x-flv',
  606. 'gif' => 'image/gif',
  607. 'gz' => 'application/gzip',
  608. 'htm' => 'text/html',
  609. 'html' => 'text/html',
  610. 'ico' => 'image/x-icon',
  611. 'ics' => 'text/calendar',
  612. 'ini' => 'text/plain',
  613. 'iso' => 'application/x-iso9660-image',
  614. 'jar' => 'application/java-archive',
  615. 'jpe' => 'image/jpeg',
  616. 'jpeg' => 'image/jpeg',
  617. 'jpg' => 'image/jpeg',
  618. 'js' => 'text/javascript',
  619. 'json' => 'application/json',
  620. 'latex' => 'application/x-latex',
  621. 'log' => 'text/plain',
  622. 'm4a' => 'audio/mp4',
  623. 'm4v' => 'video/mp4',
  624. 'mid' => 'audio/midi',
  625. 'midi' => 'audio/midi',
  626. 'mov' => 'video/quicktime',
  627. 'mkv' => 'video/x-matroska',
  628. 'mp3' => 'audio/mpeg',
  629. 'mp4' => 'video/mp4',
  630. 'mp4a' => 'audio/mp4',
  631. 'mp4v' => 'video/mp4',
  632. 'mpe' => 'video/mpeg',
  633. 'mpeg' => 'video/mpeg',
  634. 'mpg' => 'video/mpeg',
  635. 'mpg4' => 'video/mp4',
  636. 'oga' => 'audio/ogg',
  637. 'ogg' => 'audio/ogg',
  638. 'ogv' => 'video/ogg',
  639. 'ogx' => 'application/ogg',
  640. 'pbm' => 'image/x-portable-bitmap',
  641. 'pdf' => 'application/pdf',
  642. 'pgm' => 'image/x-portable-graymap',
  643. 'png' => 'image/png',
  644. 'pnm' => 'image/x-portable-anymap',
  645. 'ppm' => 'image/x-portable-pixmap',
  646. 'ppt' => 'application/vnd.ms-powerpoint',
  647. 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
  648. 'ps' => 'application/postscript',
  649. 'qt' => 'video/quicktime',
  650. 'rar' => 'application/x-rar-compressed',
  651. 'ras' => 'image/x-cmu-raster',
  652. 'rss' => 'application/rss+xml',
  653. 'rtf' => 'application/rtf',
  654. 'sgm' => 'text/sgml',
  655. 'sgml' => 'text/sgml',
  656. 'svg' => 'image/svg+xml',
  657. 'swf' => 'application/x-shockwave-flash',
  658. 'tar' => 'application/x-tar',
  659. 'tif' => 'image/tiff',
  660. 'tiff' => 'image/tiff',
  661. 'torrent' => 'application/x-bittorrent',
  662. 'ttf' => 'application/x-font-ttf',
  663. 'txt' => 'text/plain',
  664. 'wav' => 'audio/x-wav',
  665. 'webm' => 'video/webm',
  666. 'wma' => 'audio/x-ms-wma',
  667. 'wmv' => 'video/x-ms-wmv',
  668. 'woff' => 'application/x-font-woff',
  669. 'wsdl' => 'application/wsdl+xml',
  670. 'xbm' => 'image/x-xbitmap',
  671. 'xls' => 'application/vnd.ms-excel',
  672. 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
  673. 'xml' => 'application/xml',
  674. 'xpm' => 'image/x-xpixmap',
  675. 'xwd' => 'image/x-xwindowdump',
  676. 'yaml' => 'text/yaml',
  677. 'yml' => 'text/yaml',
  678. 'zip' => 'application/zip',
  679. ];
  680. $extension = strtolower($extension);
  681. return isset($mimetypes[$extension])
  682. ? $mimetypes[$extension]
  683. : null;
  684. }
  685. /**
  686. * Parses an HTTP message into an associative array.
  687. *
  688. * The array contains the "start-line" key containing the start line of
  689. * the message, "headers" key containing an associative array of header
  690. * array values, and a "body" key containing the body of the message.
  691. *
  692. * @param string $message HTTP request or response to parse.
  693. *
  694. * @return array
  695. * @internal
  696. */
  697. function _parse_message($message)
  698. {
  699. if (!$message) {
  700. throw new \InvalidArgumentException('Invalid message');
  701. }
  702. $message = ltrim($message, "\r\n");
  703. $messageParts = preg_split("/\r?\n\r?\n/", $message, 2);
  704. if ($messageParts === false || count($messageParts) !== 2) {
  705. throw new \InvalidArgumentException('Invalid message: Missing header delimiter');
  706. }
  707. list($rawHeaders, $body) = $messageParts;
  708. $rawHeaders .= "\r\n"; // Put back the delimiter we split previously
  709. $headerParts = preg_split("/\r?\n/", $rawHeaders, 2);
  710. if ($headerParts === false || count($headerParts) !== 2) {
  711. throw new \InvalidArgumentException('Invalid message: Missing status line');
  712. }
  713. list($startLine, $rawHeaders) = $headerParts;
  714. if (preg_match("/(?:^HTTP\/|^[A-Z]+ \S+ HTTP\/)(\d+(?:\.\d+)?)/i", $startLine, $matches) && $matches[1] === '1.0') {
  715. // Header folding is deprecated for HTTP/1.1, but allowed in HTTP/1.0
  716. $rawHeaders = preg_replace(Rfc7230::HEADER_FOLD_REGEX, ' ', $rawHeaders);
  717. }
  718. /** @var array[] $headerLines */
  719. $count = preg_match_all(Rfc7230::HEADER_REGEX, $rawHeaders, $headerLines, PREG_SET_ORDER);
  720. // If these aren't the same, then one line didn't match and there's an invalid header.
  721. if ($count !== substr_count($rawHeaders, "\n")) {
  722. // Folding is deprecated, see https://tools.ietf.org/html/rfc7230#section-3.2.4
  723. if (preg_match(Rfc7230::HEADER_FOLD_REGEX, $rawHeaders)) {
  724. throw new \InvalidArgumentException('Invalid header syntax: Obsolete line folding');
  725. }
  726. throw new \InvalidArgumentException('Invalid header syntax');
  727. }
  728. $headers = [];
  729. foreach ($headerLines as $headerLine) {
  730. $headers[$headerLine[1]][] = $headerLine[2];
  731. }
  732. return [
  733. 'start-line' => $startLine,
  734. 'headers' => $headers,
  735. 'body' => $body,
  736. ];
  737. }
  738. /**
  739. * Constructs a URI for an HTTP request message.
  740. *
  741. * @param string $path Path from the start-line
  742. * @param array $headers Array of headers (each value an array).
  743. *
  744. * @return string
  745. * @internal
  746. */
  747. function _parse_request_uri($path, array $headers)
  748. {
  749. $hostKey = array_filter(array_keys($headers), function ($k) {
  750. return strtolower($k) === 'host';
  751. });
  752. // If no host is found, then a full URI cannot be constructed.
  753. if (!$hostKey) {
  754. return $path;
  755. }
  756. $host = $headers[reset($hostKey)][0];
  757. $scheme = substr($host, -4) === ':443' ? 'https' : 'http';
  758. return $scheme . '://' . $host . '/' . ltrim($path, '/');
  759. }
  760. /**
  761. * Get a short summary of the message body
  762. *
  763. * Will return `null` if the response is not printable.
  764. *
  765. * @param MessageInterface $message The message to get the body summary
  766. * @param int $truncateAt The maximum allowed size of the summary
  767. *
  768. * @return null|string
  769. */
  770. function get_message_body_summary(MessageInterface $message, $truncateAt = 120)
  771. {
  772. $body = $message->getBody();
  773. if (!$body->isSeekable() || !$body->isReadable()) {
  774. return null;
  775. }
  776. $size = $body->getSize();
  777. if ($size === 0) {
  778. return null;
  779. }
  780. $summary = $body->read($truncateAt);
  781. $body->rewind();
  782. if ($size > $truncateAt) {
  783. $summary .= ' (truncated...)';
  784. }
  785. // Matches any printable character, including unicode characters:
  786. // letters, marks, numbers, punctuation, spacing, and separators.
  787. if (preg_match('/[^\pL\pM\pN\pP\pS\pZ\n\r\t]/', $summary)) {
  788. return null;
  789. }
  790. return $summary;
  791. }
  792. /** @internal */
  793. function _caseless_remove($keys, array $data)
  794. {
  795. $result = [];
  796. foreach ($keys as &$key) {
  797. $key = strtolower($key);
  798. }
  799. foreach ($data as $k => $v) {
  800. if (!in_array(strtolower($k), $keys)) {
  801. $result[$k] = $v;
  802. }
  803. }
  804. return $result;
  805. }