酒店预订平台
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

428 rivejä
14 KiB

  1. <?php
  2. namespace GuzzleHttp\Psr7;
  3. use Psr\Http\Message\RequestInterface;
  4. use Psr\Http\Message\ServerRequestInterface;
  5. use Psr\Http\Message\StreamInterface;
  6. use Psr\Http\Message\UriInterface;
  7. final class Utils
  8. {
  9. /**
  10. * Remove the items given by the keys, case insensitively from the data.
  11. *
  12. * @param iterable<string> $keys
  13. *
  14. * @return array
  15. */
  16. public static function caselessRemove($keys, array $data)
  17. {
  18. $result = [];
  19. foreach ($keys as &$key) {
  20. $key = strtolower($key);
  21. }
  22. foreach ($data as $k => $v) {
  23. if (!in_array(strtolower($k), $keys)) {
  24. $result[$k] = $v;
  25. }
  26. }
  27. return $result;
  28. }
  29. /**
  30. * Copy the contents of a stream into another stream until the given number
  31. * of bytes have been read.
  32. *
  33. * @param StreamInterface $source Stream to read from
  34. * @param StreamInterface $dest Stream to write to
  35. * @param int $maxLen Maximum number of bytes to read. Pass -1
  36. * to read the entire stream.
  37. *
  38. * @throws \RuntimeException on error.
  39. */
  40. public static function copyToStream(StreamInterface $source, StreamInterface $dest, $maxLen = -1)
  41. {
  42. $bufferSize = 8192;
  43. if ($maxLen === -1) {
  44. while (!$source->eof()) {
  45. if (!$dest->write($source->read($bufferSize))) {
  46. break;
  47. }
  48. }
  49. } else {
  50. $remaining = $maxLen;
  51. while ($remaining > 0 && !$source->eof()) {
  52. $buf = $source->read(min($bufferSize, $remaining));
  53. $len = strlen($buf);
  54. if (!$len) {
  55. break;
  56. }
  57. $remaining -= $len;
  58. $dest->write($buf);
  59. }
  60. }
  61. }
  62. /**
  63. * Copy the contents of a stream into a string until the given number of
  64. * bytes have been read.
  65. *
  66. * @param StreamInterface $stream Stream to read
  67. * @param int $maxLen Maximum number of bytes to read. Pass -1
  68. * to read the entire stream.
  69. *
  70. * @return string
  71. *
  72. * @throws \RuntimeException on error.
  73. */
  74. public static function copyToString(StreamInterface $stream, $maxLen = -1)
  75. {
  76. $buffer = '';
  77. if ($maxLen === -1) {
  78. while (!$stream->eof()) {
  79. $buf = $stream->read(1048576);
  80. // Using a loose equality here to match on '' and false.
  81. if ($buf == null) {
  82. break;
  83. }
  84. $buffer .= $buf;
  85. }
  86. return $buffer;
  87. }
  88. $len = 0;
  89. while (!$stream->eof() && $len < $maxLen) {
  90. $buf = $stream->read($maxLen - $len);
  91. // Using a loose equality here to match on '' and false.
  92. if ($buf == null) {
  93. break;
  94. }
  95. $buffer .= $buf;
  96. $len = strlen($buffer);
  97. }
  98. return $buffer;
  99. }
  100. /**
  101. * Calculate a hash of a stream.
  102. *
  103. * This method reads the entire stream to calculate a rolling hash, based
  104. * on PHP's `hash_init` functions.
  105. *
  106. * @param StreamInterface $stream Stream to calculate the hash for
  107. * @param string $algo Hash algorithm (e.g. md5, crc32, etc)
  108. * @param bool $rawOutput Whether or not to use raw output
  109. *
  110. * @return string Returns the hash of the stream
  111. *
  112. * @throws \RuntimeException on error.
  113. */
  114. public static function hash(StreamInterface $stream, $algo, $rawOutput = false)
  115. {
  116. $pos = $stream->tell();
  117. if ($pos > 0) {
  118. $stream->rewind();
  119. }
  120. $ctx = hash_init($algo);
  121. while (!$stream->eof()) {
  122. hash_update($ctx, $stream->read(1048576));
  123. }
  124. $out = hash_final($ctx, (bool) $rawOutput);
  125. $stream->seek($pos);
  126. return $out;
  127. }
  128. /**
  129. * Clone and modify a request with the given changes.
  130. *
  131. * This method is useful for reducing the number of clones needed to mutate
  132. * a message.
  133. *
  134. * The changes can be one of:
  135. * - method: (string) Changes the HTTP method.
  136. * - set_headers: (array) Sets the given headers.
  137. * - remove_headers: (array) Remove the given headers.
  138. * - body: (mixed) Sets the given body.
  139. * - uri: (UriInterface) Set the URI.
  140. * - query: (string) Set the query string value of the URI.
  141. * - version: (string) Set the protocol version.
  142. *
  143. * @param RequestInterface $request Request to clone and modify.
  144. * @param array $changes Changes to apply.
  145. *
  146. * @return RequestInterface
  147. */
  148. public static function modifyRequest(RequestInterface $request, array $changes)
  149. {
  150. if (!$changes) {
  151. return $request;
  152. }
  153. $headers = $request->getHeaders();
  154. if (!isset($changes['uri'])) {
  155. $uri = $request->getUri();
  156. } else {
  157. // Remove the host header if one is on the URI
  158. if ($host = $changes['uri']->getHost()) {
  159. $changes['set_headers']['Host'] = $host;
  160. if ($port = $changes['uri']->getPort()) {
  161. $standardPorts = ['http' => 80, 'https' => 443];
  162. $scheme = $changes['uri']->getScheme();
  163. if (isset($standardPorts[$scheme]) && $port != $standardPorts[$scheme]) {
  164. $changes['set_headers']['Host'] .= ':' . $port;
  165. }
  166. }
  167. }
  168. $uri = $changes['uri'];
  169. }
  170. if (!empty($changes['remove_headers'])) {
  171. $headers = self::caselessRemove($changes['remove_headers'], $headers);
  172. }
  173. if (!empty($changes['set_headers'])) {
  174. $headers = self::caselessRemove(array_keys($changes['set_headers']), $headers);
  175. $headers = $changes['set_headers'] + $headers;
  176. }
  177. if (isset($changes['query'])) {
  178. $uri = $uri->withQuery($changes['query']);
  179. }
  180. if ($request instanceof ServerRequestInterface) {
  181. $new = (new ServerRequest(
  182. isset($changes['method']) ? $changes['method'] : $request->getMethod(),
  183. $uri,
  184. $headers,
  185. isset($changes['body']) ? $changes['body'] : $request->getBody(),
  186. isset($changes['version'])
  187. ? $changes['version']
  188. : $request->getProtocolVersion(),
  189. $request->getServerParams()
  190. ))
  191. ->withParsedBody($request->getParsedBody())
  192. ->withQueryParams($request->getQueryParams())
  193. ->withCookieParams($request->getCookieParams())
  194. ->withUploadedFiles($request->getUploadedFiles());
  195. foreach ($request->getAttributes() as $key => $value) {
  196. $new = $new->withAttribute($key, $value);
  197. }
  198. return $new;
  199. }
  200. return new Request(
  201. isset($changes['method']) ? $changes['method'] : $request->getMethod(),
  202. $uri,
  203. $headers,
  204. isset($changes['body']) ? $changes['body'] : $request->getBody(),
  205. isset($changes['version'])
  206. ? $changes['version']
  207. : $request->getProtocolVersion()
  208. );
  209. }
  210. /**
  211. * Read a line from the stream up to the maximum allowed buffer length.
  212. *
  213. * @param StreamInterface $stream Stream to read from
  214. * @param int|null $maxLength Maximum buffer length
  215. *
  216. * @return string
  217. */
  218. public static function readLine(StreamInterface $stream, $maxLength = null)
  219. {
  220. $buffer = '';
  221. $size = 0;
  222. while (!$stream->eof()) {
  223. // Using a loose equality here to match on '' and false.
  224. if (null == ($byte = $stream->read(1))) {
  225. return $buffer;
  226. }
  227. $buffer .= $byte;
  228. // Break when a new line is found or the max length - 1 is reached
  229. if ($byte === "\n" || ++$size === $maxLength - 1) {
  230. break;
  231. }
  232. }
  233. return $buffer;
  234. }
  235. /**
  236. * Create a new stream based on the input type.
  237. *
  238. * Options is an associative array that can contain the following keys:
  239. * - metadata: Array of custom metadata.
  240. * - size: Size of the stream.
  241. *
  242. * This method accepts the following `$resource` types:
  243. * - `Psr\Http\Message\StreamInterface`: Returns the value as-is.
  244. * - `string`: Creates a stream object that uses the given string as the contents.
  245. * - `resource`: Creates a stream object that wraps the given PHP stream resource.
  246. * - `Iterator`: If the provided value implements `Iterator`, then a read-only
  247. * stream object will be created that wraps the given iterable. Each time the
  248. * stream is read from, data from the iterator will fill a buffer and will be
  249. * continuously called until the buffer is equal to the requested read size.
  250. * Subsequent read calls will first read from the buffer and then call `next`
  251. * on the underlying iterator until it is exhausted.
  252. * - `object` with `__toString()`: If the object has the `__toString()` method,
  253. * the object will be cast to a string and then a stream will be returned that
  254. * uses the string value.
  255. * - `NULL`: When `null` is passed, an empty stream object is returned.
  256. * - `callable` When a callable is passed, a read-only stream object will be
  257. * created that invokes the given callable. The callable is invoked with the
  258. * number of suggested bytes to read. The callable can return any number of
  259. * bytes, but MUST return `false` when there is no more data to return. The
  260. * stream object that wraps the callable will invoke the callable until the
  261. * number of requested bytes are available. Any additional bytes will be
  262. * buffered and used in subsequent reads.
  263. *
  264. * @param resource|string|int|float|bool|StreamInterface|callable|\Iterator|null $resource Entity body data
  265. * @param array $options Additional options
  266. *
  267. * @return StreamInterface
  268. *
  269. * @throws \InvalidArgumentException if the $resource arg is not valid.
  270. */
  271. public static function streamFor($resource = '', array $options = [])
  272. {
  273. if (is_scalar($resource)) {
  274. $stream = self::tryFopen('php://temp', 'r+');
  275. if ($resource !== '') {
  276. fwrite($stream, $resource);
  277. fseek($stream, 0);
  278. }
  279. return new Stream($stream, $options);
  280. }
  281. switch (gettype($resource)) {
  282. case 'resource':
  283. /*
  284. * The 'php://input' is a special stream with quirks and inconsistencies.
  285. * We avoid using that stream by reading it into php://temp
  286. */
  287. if (\stream_get_meta_data($resource)['uri'] === 'php://input') {
  288. $stream = self::tryFopen('php://temp', 'w+');
  289. fwrite($stream, stream_get_contents($resource));
  290. fseek($stream, 0);
  291. $resource = $stream;
  292. }
  293. return new Stream($resource, $options);
  294. case 'object':
  295. if ($resource instanceof StreamInterface) {
  296. return $resource;
  297. } elseif ($resource instanceof \Iterator) {
  298. return new PumpStream(function () use ($resource) {
  299. if (!$resource->valid()) {
  300. return false;
  301. }
  302. $result = $resource->current();
  303. $resource->next();
  304. return $result;
  305. }, $options);
  306. } elseif (method_exists($resource, '__toString')) {
  307. return Utils::streamFor((string) $resource, $options);
  308. }
  309. break;
  310. case 'NULL':
  311. return new Stream(self::tryFopen('php://temp', 'r+'), $options);
  312. }
  313. if (is_callable($resource)) {
  314. return new PumpStream($resource, $options);
  315. }
  316. throw new \InvalidArgumentException('Invalid resource type: ' . gettype($resource));
  317. }
  318. /**
  319. * Safely opens a PHP stream resource using a filename.
  320. *
  321. * When fopen fails, PHP normally raises a warning. This function adds an
  322. * error handler that checks for errors and throws an exception instead.
  323. *
  324. * @param string $filename File to open
  325. * @param string $mode Mode used to open the file
  326. *
  327. * @return resource
  328. *
  329. * @throws \RuntimeException if the file cannot be opened
  330. */
  331. public static function tryFopen($filename, $mode)
  332. {
  333. $ex = null;
  334. set_error_handler(function () use ($filename, $mode, &$ex) {
  335. $ex = new \RuntimeException(sprintf(
  336. 'Unable to open "%s" using mode "%s": %s',
  337. $filename,
  338. $mode,
  339. func_get_args()[1]
  340. ));
  341. return true;
  342. });
  343. try {
  344. $handle = fopen($filename, $mode);
  345. } catch (\Throwable $e) {
  346. $ex = new \RuntimeException(sprintf(
  347. 'Unable to open "%s" using mode "%s": %s',
  348. $filename,
  349. $mode,
  350. $e->getMessage()
  351. ), 0, $e);
  352. }
  353. restore_error_handler();
  354. if ($ex) {
  355. /** @var $ex \RuntimeException */
  356. throw $ex;
  357. }
  358. return $handle;
  359. }
  360. /**
  361. * Returns a UriInterface for the given value.
  362. *
  363. * This function accepts a string or UriInterface and returns a
  364. * UriInterface for the given value. If the value is already a
  365. * UriInterface, it is returned as-is.
  366. *
  367. * @param string|UriInterface $uri
  368. *
  369. * @return UriInterface
  370. *
  371. * @throws \InvalidArgumentException
  372. */
  373. public static function uriFor($uri)
  374. {
  375. if ($uri instanceof UriInterface) {
  376. return $uri;
  377. }
  378. if (is_string($uri)) {
  379. return new Uri($uri);
  380. }
  381. throw new \InvalidArgumentException('URI must be a string or UriInterface');
  382. }
  383. }