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