|
@@ -69,10 +69,10 @@ function uri_for($uri)
|
|
|
* - metadata: Array of custom metadata.
|
|
* - metadata: Array of custom metadata.
|
|
|
* - size: Size of the stream.
|
|
* - size: Size of the stream.
|
|
|
*
|
|
*
|
|
|
- * @param resource|string|null|int|float|bool|StreamInterface|callable $resource Entity body data
|
|
|
|
|
- * @param array $options Additional options
|
|
|
|
|
|
|
+ * @param resource|string|null|int|float|bool|StreamInterface|callable|\Iterator $resource Entity body data
|
|
|
|
|
+ * @param array $options Additional options
|
|
|
*
|
|
*
|
|
|
- * @return Stream
|
|
|
|
|
|
|
+ * @return StreamInterface
|
|
|
* @throws \InvalidArgumentException if the $resource arg is not valid.
|
|
* @throws \InvalidArgumentException if the $resource arg is not valid.
|
|
|
*/
|
|
*/
|
|
|
function stream_for($resource = '', array $options = [])
|
|
function stream_for($resource = '', array $options = [])
|
|
@@ -238,7 +238,7 @@ function modify_request(RequestInterface $request, array $changes)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if ($request instanceof ServerRequestInterface) {
|
|
if ($request instanceof ServerRequestInterface) {
|
|
|
- return new ServerRequest(
|
|
|
|
|
|
|
+ return (new ServerRequest(
|
|
|
isset($changes['method']) ? $changes['method'] : $request->getMethod(),
|
|
isset($changes['method']) ? $changes['method'] : $request->getMethod(),
|
|
|
$uri,
|
|
$uri,
|
|
|
$headers,
|
|
$headers,
|
|
@@ -247,7 +247,11 @@ function modify_request(RequestInterface $request, array $changes)
|
|
|
? $changes['version']
|
|
? $changes['version']
|
|
|
: $request->getProtocolVersion(),
|
|
: $request->getProtocolVersion(),
|
|
|
$request->getServerParams()
|
|
$request->getServerParams()
|
|
|
- );
|
|
|
|
|
|
|
+ ))
|
|
|
|
|
+ ->withParsedBody($request->getParsedBody())
|
|
|
|
|
+ ->withQueryParams($request->getQueryParams())
|
|
|
|
|
+ ->withCookieParams($request->getCookieParams())
|
|
|
|
|
+ ->withUploadedFiles($request->getUploadedFiles());
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
return new Request(
|
|
return new Request(
|
|
@@ -431,7 +435,7 @@ function hash(
|
|
|
* @param StreamInterface $stream Stream to read from
|
|
* @param StreamInterface $stream Stream to read from
|
|
|
* @param int $maxLength Maximum buffer length
|
|
* @param int $maxLength Maximum buffer length
|
|
|
*
|
|
*
|
|
|
- * @return string|bool
|
|
|
|
|
|
|
+ * @return string
|
|
|
*/
|
|
*/
|
|
|
function readline(StreamInterface $stream, $maxLength = null)
|
|
function readline(StreamInterface $stream, $maxLength = null)
|
|
|
{
|
|
{
|
|
@@ -495,7 +499,7 @@ function parse_response($message)
|
|
|
// between status-code and reason-phrase is required. But browsers accept
|
|
// between status-code and reason-phrase is required. But browsers accept
|
|
|
// responses without space and reason as well.
|
|
// responses without space and reason as well.
|
|
|
if (!preg_match('/^HTTP\/.* [0-9]{3}( .*|$)/', $data['start-line'])) {
|
|
if (!preg_match('/^HTTP\/.* [0-9]{3}( .*|$)/', $data['start-line'])) {
|
|
|
- throw new \InvalidArgumentException('Invalid response string');
|
|
|
|
|
|
|
+ throw new \InvalidArgumentException('Invalid response string: ' . $data['start-line']);
|
|
|
}
|
|
}
|
|
|
$parts = explode(' ', $data['start-line'], 3);
|
|
$parts = explode(' ', $data['start-line'], 3);
|
|
|
|
|
|
|
@@ -516,8 +520,8 @@ function parse_response($message)
|
|
|
* PHP style arrays into an associative array (e.g., foo[a]=1&foo[b]=2 will
|
|
* PHP style arrays into an associative array (e.g., foo[a]=1&foo[b]=2 will
|
|
|
* be parsed into ['foo[a]' => '1', 'foo[b]' => '2']).
|
|
* be parsed into ['foo[a]' => '1', 'foo[b]' => '2']).
|
|
|
*
|
|
*
|
|
|
- * @param string $str Query string to parse
|
|
|
|
|
- * @param bool|string $urlEncoding How the query string is encoded
|
|
|
|
|
|
|
+ * @param string $str Query string to parse
|
|
|
|
|
+ * @param int|bool $urlEncoding How the query string is encoded
|
|
|
*
|
|
*
|
|
|
* @return array
|
|
* @return array
|
|
|
*/
|
|
*/
|
|
@@ -533,9 +537,9 @@ function parse_query($str, $urlEncoding = true)
|
|
|
$decoder = function ($value) {
|
|
$decoder = function ($value) {
|
|
|
return rawurldecode(str_replace('+', ' ', $value));
|
|
return rawurldecode(str_replace('+', ' ', $value));
|
|
|
};
|
|
};
|
|
|
- } elseif ($urlEncoding == PHP_QUERY_RFC3986) {
|
|
|
|
|
|
|
+ } elseif ($urlEncoding === PHP_QUERY_RFC3986) {
|
|
|
$decoder = 'rawurldecode';
|
|
$decoder = 'rawurldecode';
|
|
|
- } elseif ($urlEncoding == PHP_QUERY_RFC1738) {
|
|
|
|
|
|
|
+ } elseif ($urlEncoding === PHP_QUERY_RFC1738) {
|
|
|
$decoder = 'urldecode';
|
|
$decoder = 'urldecode';
|
|
|
} else {
|
|
} else {
|
|
|
$decoder = function ($str) { return $str; };
|
|
$decoder = function ($str) { return $str; };
|
|
@@ -633,6 +637,7 @@ function mimetype_from_filename($filename)
|
|
|
function mimetype_from_extension($extension)
|
|
function mimetype_from_extension($extension)
|
|
|
{
|
|
{
|
|
|
static $mimetypes = [
|
|
static $mimetypes = [
|
|
|
|
|
+ '3gp' => 'video/3gpp',
|
|
|
'7z' => 'application/x-7z-compressed',
|
|
'7z' => 'application/x-7z-compressed',
|
|
|
'aac' => 'audio/x-aac',
|
|
'aac' => 'audio/x-aac',
|
|
|
'ai' => 'application/postscript',
|
|
'ai' => 'application/postscript',
|
|
@@ -680,6 +685,7 @@ function mimetype_from_extension($extension)
|
|
|
'mid' => 'audio/midi',
|
|
'mid' => 'audio/midi',
|
|
|
'midi' => 'audio/midi',
|
|
'midi' => 'audio/midi',
|
|
|
'mov' => 'video/quicktime',
|
|
'mov' => 'video/quicktime',
|
|
|
|
|
+ 'mkv' => 'video/x-matroska',
|
|
|
'mp3' => 'audio/mpeg',
|
|
'mp3' => 'audio/mpeg',
|
|
|
'mp4' => 'video/mp4',
|
|
'mp4' => 'video/mp4',
|
|
|
'mp4a' => 'audio/mp4',
|
|
'mp4a' => 'audio/mp4',
|
|
@@ -718,6 +724,7 @@ function mimetype_from_extension($extension)
|
|
|
'txt' => 'text/plain',
|
|
'txt' => 'text/plain',
|
|
|
'wav' => 'audio/x-wav',
|
|
'wav' => 'audio/x-wav',
|
|
|
'webm' => 'video/webm',
|
|
'webm' => 'video/webm',
|
|
|
|
|
+ 'webp' => 'image/webp',
|
|
|
'wma' => 'audio/x-ms-wma',
|
|
'wma' => 'audio/x-ms-wma',
|
|
|
'wmv' => 'video/x-ms-wmv',
|
|
'wmv' => 'video/x-ms-wmv',
|
|
|
'woff' => 'application/x-font-woff',
|
|
'woff' => 'application/x-font-woff',
|
|
@@ -758,29 +765,53 @@ function _parse_message($message)
|
|
|
throw new \InvalidArgumentException('Invalid message');
|
|
throw new \InvalidArgumentException('Invalid message');
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // Iterate over each line in the message, accounting for line endings
|
|
|
|
|
- $lines = preg_split('/(\\r?\\n)/', $message, -1, PREG_SPLIT_DELIM_CAPTURE);
|
|
|
|
|
- $result = ['start-line' => array_shift($lines), 'headers' => [], 'body' => ''];
|
|
|
|
|
- array_shift($lines);
|
|
|
|
|
|
|
+ $message = ltrim($message, "\r\n");
|
|
|
|
|
|
|
|
- for ($i = 0, $totalLines = count($lines); $i < $totalLines; $i += 2) {
|
|
|
|
|
- $line = $lines[$i];
|
|
|
|
|
- // If two line breaks were encountered, then this is the end of body
|
|
|
|
|
- if (empty($line)) {
|
|
|
|
|
- if ($i < $totalLines - 1) {
|
|
|
|
|
- $result['body'] = implode('', array_slice($lines, $i + 2));
|
|
|
|
|
- }
|
|
|
|
|
- break;
|
|
|
|
|
- }
|
|
|
|
|
- if (strpos($line, ':')) {
|
|
|
|
|
- $parts = explode(':', $line, 2);
|
|
|
|
|
- $key = trim($parts[0]);
|
|
|
|
|
- $value = isset($parts[1]) ? trim($parts[1]) : '';
|
|
|
|
|
- $result['headers'][$key][] = $value;
|
|
|
|
|
|
|
+ $messageParts = preg_split("/\r?\n\r?\n/", $message, 2);
|
|
|
|
|
+
|
|
|
|
|
+ if ($messageParts === false || count($messageParts) !== 2) {
|
|
|
|
|
+ throw new \InvalidArgumentException('Invalid message: Missing header delimiter');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ list($rawHeaders, $body) = $messageParts;
|
|
|
|
|
+ $rawHeaders .= "\r\n"; // Put back the delimiter we split previously
|
|
|
|
|
+ $headerParts = preg_split("/\r?\n/", $rawHeaders, 2);
|
|
|
|
|
+
|
|
|
|
|
+ if ($headerParts === false || count($headerParts) !== 2) {
|
|
|
|
|
+ throw new \InvalidArgumentException('Invalid message: Missing status line');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ list($startLine, $rawHeaders) = $headerParts;
|
|
|
|
|
+
|
|
|
|
|
+ if (preg_match("/(?:^HTTP\/|^[A-Z]+ \S+ HTTP\/)(\d+(?:\.\d+)?)/i", $startLine, $matches) && $matches[1] === '1.0') {
|
|
|
|
|
+ // Header folding is deprecated for HTTP/1.1, but allowed in HTTP/1.0
|
|
|
|
|
+ $rawHeaders = preg_replace(Rfc7230::HEADER_FOLD_REGEX, ' ', $rawHeaders);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** @var array[] $headerLines */
|
|
|
|
|
+ $count = preg_match_all(Rfc7230::HEADER_REGEX, $rawHeaders, $headerLines, PREG_SET_ORDER);
|
|
|
|
|
+
|
|
|
|
|
+ // If these aren't the same, then one line didn't match and there's an invalid header.
|
|
|
|
|
+ if ($count !== substr_count($rawHeaders, "\n")) {
|
|
|
|
|
+ // Folding is deprecated, see https://tools.ietf.org/html/rfc7230#section-3.2.4
|
|
|
|
|
+ if (preg_match(Rfc7230::HEADER_FOLD_REGEX, $rawHeaders)) {
|
|
|
|
|
+ throw new \InvalidArgumentException('Invalid header syntax: Obsolete line folding');
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ throw new \InvalidArgumentException('Invalid header syntax');
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- return $result;
|
|
|
|
|
|
|
+ $headers = [];
|
|
|
|
|
+
|
|
|
|
|
+ foreach ($headerLines as $headerLine) {
|
|
|
|
|
+ $headers[$headerLine[1]][] = $headerLine[2];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return [
|
|
|
|
|
+ 'start-line' => $startLine,
|
|
|
|
|
+ 'headers' => $headers,
|
|
|
|
|
+ 'body' => $body,
|
|
|
|
|
+ ];
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -809,6 +840,46 @@ function _parse_request_uri($path, array $headers)
|
|
|
return $scheme . '://' . $host . '/' . ltrim($path, '/');
|
|
return $scheme . '://' . $host . '/' . ltrim($path, '/');
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * Get a short summary of the message body
|
|
|
|
|
+ *
|
|
|
|
|
+ * Will return `null` if the response is not printable.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param MessageInterface $message The message to get the body summary
|
|
|
|
|
+ * @param int $truncateAt The maximum allowed size of the summary
|
|
|
|
|
+ *
|
|
|
|
|
+ * @return null|string
|
|
|
|
|
+ */
|
|
|
|
|
+function get_message_body_summary(MessageInterface $message, $truncateAt = 120)
|
|
|
|
|
+{
|
|
|
|
|
+ $body = $message->getBody();
|
|
|
|
|
+
|
|
|
|
|
+ if (!$body->isSeekable() || !$body->isReadable()) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $size = $body->getSize();
|
|
|
|
|
+
|
|
|
|
|
+ if ($size === 0) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $summary = $body->read($truncateAt);
|
|
|
|
|
+ $body->rewind();
|
|
|
|
|
+
|
|
|
|
|
+ if ($size > $truncateAt) {
|
|
|
|
|
+ $summary .= ' (truncated...)';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Matches any printable character, including unicode characters:
|
|
|
|
|
+ // letters, marks, numbers, punctuation, spacing, and separators.
|
|
|
|
|
+ if (preg_match('/[^\pL\pM\pN\pP\pS\pZ\n\r\t]/', $summary)) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return $summary;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
/** @internal */
|
|
/** @internal */
|
|
|
function _caseless_remove($keys, array $data)
|
|
function _caseless_remove($keys, array $data)
|
|
|
{
|
|
{
|