PageRenderTime 50ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

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

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