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