PageRenderTime 34ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

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

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