PageRenderTime 54ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/vendor/guzzlehttp/psr7/src/functions.php

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