All notable changes to this project will be documented in this file, in reverse chronological order by release.
$_SERVER['HTTPS'] value
such that an empty HTTPS-key will result in a scheme of http and not
https.ServerRequest::withParsedBody(). PerPSR-7, it now no longer allows values other than null, arrays, or objects.
#325 changes the behavior of each of Request, ServerRequest, and
Response in relation to the validation of header values. Previously, we
allowed empty arrays to be provided via withHeader(); however, this was
contrary to the PSR-7 specification. Empty arrays are no longer allowed.
#325 ensures that Uri::withUserInfo() no longer ignores values of
0 (numeric zero).
#325 fixes how header values are merged when calling
withAddedHeader(), ensuring that array keys are ignored.
ServerRequestFactory, which made it
impossible to fetch a specific header by name.ServerRequestFactory marshals the request URI. In
prior releases, we would attempt to inspect the X-Rewrite-Url and
X-Original-Url headers, using their values, if present. These headers are
issued by the ISAPI_Rewrite module for IIS (developed by HeliconTech).
However, we have no way of guaranteeing that the module is what issued the
headers, making it an unreliable source for discovering the URI. As such, we
have removed this feature in this release of Diactoros.If you are developing a middleware application, you can mimic the functionality via middleware as follows:
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Zend\Diactoros\Uri;
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
{
$requestUri = null;
$httpXRewriteUrl = $request->getHeaderLine('X-Rewrite-Url');
if ($httpXRewriteUrl !== null) {
$requestUri = $httpXRewriteUrl;
}
$httpXOriginalUrl = $request->getHeaderLine('X-Original-Url');
if ($httpXOriginalUrl !== null) {
$requestUri = $httpXOriginalUrl;
}
if ($requestUri !== null) {
$request = $request->withUri(new Uri($requestUri));
}
return $handler->handle($request);
}
If you use middleware such as the above, make sure you also instruct your web server to strip any incoming headers of the same name so that you can guarantee they are issued by the ISAPI_Rewrite module.
#321 updates the logic in Uri::withPort() to ensure that it checks that the
value provided is either an integer or a string integer, as only those values
may be cast to integer without data loss.
#320 adds checking within Response to ensure that the provided reason
phrase is a string; an InvalidArgumentException is now raised if it is not. This change
ensures the class adheres strictly to the PSR-7 specification.
#319 provides a fix to Zend\Diactoros\Response that ensures that the status
code returned is always an integer (and never a string containing an
integer), thus ensuring it strictly adheres to the PSR-7 specification.
#318 fixes the logic for discovering whether an HTTPS scheme is in play to be case insensitive when comparing header and SAPI values, ensuring no false negative lookups occur.
#314 modifies error handling around opening a file resource within
Zend\Diactoros\Stream::setStream() to no longer use the second argument to
set_error_handler(), and instead check the error type in the handler itself;
this fixes an issue when the handler is nested inside another error handler,
which currently has buggy behavior within the PHP engine.
normalizeUploadedFiles() utility function handles nested trees of
uploaded files, ensuring it detects them properly.Zend\Diactoros namespace, each of
which may be used to derive artifacts from SAPI supergloabls for the purposes
of generating a ServerRequest instance:
normalizeServer(array $server, callable $apacheRequestHeaderCallback = null) : array
(main purpose is to aggregate the Authorization header in the SAPI params
when under Apache)marshalProtocolVersionFromSapi(array $server) : stringmarshalMethodFromSapi(array $server) : stringmarshalUriFromSapi(array $server, array $headers) : UrimarshalHeadersFromSapi(array $server) : arrayparseCookieHeader(string $header) : arraycreateUploadedFile(array $spec) : UploadedFile (creates the instance from
a normal $_FILES entry)normalizeUploadedFiles(array $files) : UploadedFileInterface[] (traverses
a potentially nested array of uploaded file instances and/or $_FILES
entries, including those aggregated under mod_php, php-fpm, and php-cgi in
order to create a flat array of UploadedFileInterface instances to use in a
request)#307 deprecates ServerRequestFactory::normalizeServer(); the method is
no longer used internally, and users should instead use Zend\Diactoros\normalizeServer(),
to which it proxies.
#307 deprecates ServerRequestFactory::marshalHeaders(); the method is
no longer used internally, and users should instead use Zend\Diactoros\marshalHeadersFromSapi(),
to which it proxies.
#307 deprecates ServerRequestFactory::marshalUriFromServer(); the method
is no longer used internally. Users should use marshalUriFromSapi() instead.
#307 deprecates ServerRequestFactory::marshalRequestUri(). the method is no longer
used internally, and currently proxies to marshalUriFromSapi(), pulling the
discovered path from the Uri instance returned by that function. Users
should use marshalUriFromSapi() instead.
#307 deprecates ServerRequestFactory::marshalHostAndPortFromHeaders(); the method
is no longer used internally, and currently proxies to marshalUriFromSapi(),
pulling the discovered host and port from the Uri instance returned by that
function. Users should use marshalUriFromSapi() instead.
#307 deprecates ServerRequestFactory::getHeader(); the method is no longer
used internally. Users should copy and paste the functionality into their own
applications if needed, or rely on headers from a fully-populated Uri
instance instead.
#307 deprecates ServerRequestFactory::stripQueryString(); the method is no longer
used internally, and users can mimic the functionality via the expression
$path = explode('?', $path, 2)[0];.
#307 deprecates ServerRequestFactory::normalizeFiles(); the functionality
is no longer used internally, and users can use normalizeUploadedFiles() as
a replacement.
#303 deprecates Zend\Diactoros\Response\EmitterInterface and its various implementations. These are now provided via the
zendframework/zend-httphandlerrunner package as 1:1 substitutions.
#303 deprecates the Zend\Diactoros\Server class. Users are directed to the RequestHandlerRunner class from the
zendframework/zend-httphandlerrunner package as an alternative.
uri class to ensure non-empty
values are not treated as empty.Uri::getHost() to cast the value via strtolower() before returning it.
While this represents a change, it is fixing a bug in our implementation:
the PSR-7 specification for the method, which follows IETF RFC 3986 section
3.2.2, requires that the host name be normalized to lowercase.Stream::getSize() such that it checks that the result of fstat was
succesful before attempting to return its size member; in the case of an
error, it now returns null.#285 adds a new
custom response type, Zend\Diactoros\Response\XmlResponse, for generating
responses representing XML. Usage is the same as with the HtmlResponse or
TextResponse; the response generated will have a Content-Type:
application/xml header by default.
#280 adds the
response status code/phrase pairing "103 Early Hints" to the
Response::$phrases property. This is a new status proposed via
RFC 8297.
#279 adds explicit support for PHP 7.2; previously, we'd allowed build failures, though none occured; we now require PHP 7.2 builds to pass.
SapiEmitterTrait calls header() to ensure that a response code is
always passed as the third argument; this is done to prevent PHP from
silently overriding it.#270 changes the
behavior of Zend\Diactoros\Server: it no longer creates an output buffer.
#270 changes the behavior of the two SAPI emitters in two backwards-incompatible ways:
They no longer auto-inject a Content-Length header. If you need this
functionality, zendframework/zend-expressive-helpers 4.1+ provides it via
Zend\Expressive\Helper\ContentLengthMiddleware.
They no longer flush the output buffer. Instead, if headers have been sent,
or the output buffer exists and has a non-zero length, the emitters raise an
exception, as mixed PSR-7/output buffer content creates a blocking issue.
If you are emitting content via echo, print, var_dump, etc., or not
catching PHP errors or exceptions, you will need to either fix your
application to always work with a PSR-7 response, or provide your own
emitters that allow mixed output mechanisms.
#205 adds support for PHP 7.2.
#250 adds a new
API to JsonResponse to avoid the need for decoding the response body in
order to make changes to the underlying content. New methods include:
getPayload(): retrieve the unencoded payload.withPayload($data): create a new instance with the given data.getEncodingOptions(): retrieve the flags to use when encoding the payload
to JSON.withEncodingOptions(int $encodingOptions): create a new instance that uses
the provided flags when encoding the payload to JSON.#249 changes the
behavior of the various Uri::with*() methods slightly: if the value
represents no change, these methods will return the same instance instead of a
new one.
#248 changes the
behavior of Uri::getUserInfo() slightly: it now (correctly) returns the
percent-encoded values for the user and/or password, per RFC 3986 Section
3.2.1. withUserInfo() will percent-encode values, using a mechanism that
prevents double-encoding.
#243 changes the
exception messages thrown by UploadedFile::getStream() and moveTo() when
an upload error exists to include details about the upload error.
#233 adds a new
argument to SapiStreamEmitter::emit, $maxBufferLevel between the
$response and $maxBufferLength arguments. This was done because the
Server::listen() method passes only the response and $maxBufferLevel to
emitters; previously, this often meant that streams were being chunked 2 bytes
at a time versus the expected default of 8kb.
If you were calling the SapiStreamEmitter::emit() method manually
previously, you will need to update your code.
Uri class provides user-info within the URI authority; the value is now
correctly percent-encoded , per RFC 3986 Section 3.2.1.#247 fixes the
Stream and RelativeStream __toString() method implementations to check
if the stream isSeekable() before attempting to rewind() it, ensuring that
the method does not raise exceptions (PHP does not allow exceptions in that
method). In particular, this fixes an issue when using AWS S3 streams.
#252 provides a
fix to the SapiEmitterTrait to ensure that any Set-Cookie headers in the
response instance do not override those set by PHP when a session is created
and/or regenerated.
#257 provides a
fix for the PhpInputStream::read() method to ensure string content that
evaluates as empty (including 0) is still cached.
#258 updates the
Uri::filterPath() method to allow parens within a URI path, per RFC 3986
section 3.3 (parens are
within the character set "sub-delims").
#219 adds two new
classes, Zend\Diactoros\Request\ArraySerializer and
Zend\Diactoros\Response\ArraySerializer. Each exposes the static methods
toArray() and fromArray(), allowing de/serialization of messages from and
to arrays.
#236 adds two new
constants to the Response class: MIN_STATUS_CODE_VALUE and
MAX_STATUS_CODE_VALUE.
ServerRequestFactory::fromGlobals() when no $cookies argument
is present. Previously, it would use $_COOKIES; now, if a Cookie header is
present, it will parse and use that to populate the instance instead.This change allows utilizing cookies that contain period characters (.) in
their names (PHP's built-in cookie handling renames these to replace . with
_, which can lead to synchronization issues with clients).
Uri::__toString() to better follow proscribed behavior in PSR-7.
In particular, prior to this release, if a scheme was missing but an authority
was present, the class was incorrectly returning a value that did not include
a // prefix. As of this release, it now does this correctly.psr/http-message-implementation to
simply 1.0 instead of ~1.0.0, to follow how other implementations provide
PSR-7.#161 adds additional validations to header names and values to ensure no malformed values are provided.
#234 fixes a
number of reason phrases in the Response instance, and adds automation from
the canonical IANA sources to ensure any new phrases added are correct.
SapiStreamEmitter causing the response body to be cast
to (string) and also be read as a readable stream, potentially producing
double output.SapiStreamEmitter consuming too much memory when producing output
for readable bodies.SapiStreamEmitter's handling of the Content-Range header to properly only
emit a range of bytes if the header value is in the form bytes {first-last}/length.
This allows using other range units, such as items, without incorrectly
emitting truncated content.Zend\Diactoros\Response, including:
Zend\Diactoros\Uri.REDIRECT_HTTP_* header detection in the ServerRequestFactory.SapiStreamEmitter.SapiStreamEmitter to implement a check for isSeekable() prior to attempts
to rewind; this allows it to work with non-seekable streams such as the
CallbackStream.\r\n\r\n sequence following the
headers, even when no message body is present, to ensure it conforms with RFC
7230.Request class to set the Host header from the URI host if no header is
already present. (Ensures conformity with PSR-7 specification.)Uri class to ensure that string serialization does not include a colon after
the host name if no port is present in the instance.ServerRequestFactory to work correctly with HTTP/2.Response class.null values when calling withoutAttribute().ServerRequestFactory to marshal the request path fragment, if present.HeaderSecurity to include the header name and/or
value.ServerRequestFactory::marshalHeaders() to no longer omit
Cookie headers from the aggregated headers. While the values are parsed and
injected into the cookie params, it's useful to have access to the raw headers
as well.ServerRequest constructor:
array $cookiesarray $queryParamsnull|array|object $parsedBodystring $protocolVersion
ServerRequestFactory was updated to pass values for each of these parameters
when creating an instance, instead of using the related with*() methods on
an instance.ServerRequestFactory to retrieve the HTTP protocol version and inject it in
the generated ServerRequest, which previously was not performed.TextResponse, HtmlResponse,
and JsonResponse); due to the fact that the constructor was not
rewinding the message body stream, getContents() was thus returning null,
as the pointer was at the end of the stream. The constructor now rewinds the
stream after populating it in the constructor.Zend\Diactoros\Response\SapiEmitterTrait, which provides the following
private method definitions:
injectContentLength()emitStatusLine()emitHeaders()flush()filterHeader()
The SapiEmitter implementation has been updated to remove those methods and
instead compose the trait.SapiStreamEmitter; this emitter type will
loop through the stream instead of emitting it in one go, and supports content
ranges.withHeader() implementation to ensure that if the header existed previously
but using a different casing strategy, the previous version will be removed
in the cloned instance.Response to ensure that null status codes are not possible.SapiEmitter to emit a Content-Length header with the content length as
reported by the response body stream, assuming that
StreamInterface::getSize() returns an integer.Zend\Diactoros\Response\TextResponse, for returning plain
text responses. By default, it sets the content type to text/plain;
charset=utf-8; per the other response types, the signature is new
TextResponse($text, $status = 200, array $headers = []).Zend\Diactoros\CallbackStream, allowing you to back a stream with a PHP
callable (such as a generator) to generate the message content. Its
constructor accepts the callable: $stream = new CallbackStream($callable);HtmlResponse to set the charset to utf-8 by default (if no content type
header is provided at instantiation).JSON_UNESCAPED_SLASHES to the default json_encode flags used by
Zend\Diactoros\Response\JsonResponse.withPort() to allow null port values (indicating usage of default for
the given scheme).withUri() to do a case-insensitive check for an existing Host
header, replacing it with the new one.JsonResponse constructor to typehint the $data argument
as mixed.Request such that if it marshals a stream during instantiation,
the stream is marked as writeable (specifically, mode wb+).Zend\Diactoros\Uri's various with*() methods that are
documented as accepting strings to raise exceptions on non-string input.
Previously, several simply passed non-string input on verbatim, others
normalized the input, and a few correctly raised the exceptions. Behavior is
now consistent across each.UploadedFile to ensure that moveTo() works correctly in non-SAPI
environments when the file provided to the constructor is a path.Stream class only accepts stream resources, not any resource.JsonResponse with regards to serialization of null and scalar
values; the new behavior is to serialize them verbatim, without any casting.#52, #58, #59, and #61 create several custom response types for simplifying response creation:
Zend\Diactoros\Response\HtmlResponse accepts HTML content via its
constructor, and sets the Content-Type to text/html.Zend\Diactoros\Response\JsonResponse accepts data to serialize to JSON via
its constructor, and sets the Content-Type to application/json.Zend\Diactoros\Response\EmptyResponse allows creating empty, read-only
responses, with a default status code of 204.Zend\Diactoros\Response\RedirectResponse allows specifying a URI for the
Location header in the constructor, with a default status code of 302.Each also accepts an optional status code, and optional headers (which can
also be used to provide an alternate Content-Type in the case of the HTML
and JSON responses).
ServerRequestFactory::marshalUri() and ServerRequestFactory::marshalHostAndPort(),
which were deprecated prior to the 1.0 release.UploadedFile when the $errorStatus provided at
instantiation is not UPLOAD_ERR_OK. Prior to the fix, an
InvalidArgumentException would occur at instantiation due to the fact that
the upload file was missing or invalid. With the fix, no exception is raised
until a call to moveTo() or getStream() is made.This is a security release.
A patch has been applied to Zend\Diactoros\Uri::filterPath() that ensures that
paths can only begin with a single leading slash. This prevents the following
potential security issues:
//example.com/foo. With the patch,
the leading double slash is reduced to a single slash, preventing the XSS
vector.Location or Link headers,
without a scheme and authority, potential for open redirects exist if clients
do not prepend the scheme and authority. Again, preventing a double slash
corrects the vector.If you are using Zend\Diactoros\Uri for creating links, form targets, or
redirect paths, and only using the path segment, we recommend upgrading
immediately.
MessageTrait::getHeaderLine() to return an empty string instead of null if
the header is undefined (which is the behavior specified in PSR-7).ServerRequestFactory marshals upload files when they are
represented as a nested associative array.MessageInterface::getHeaderLine() MUST return a string (that string CAN be
empty). Previously, Diactoros would return null.Host header is set, the $preserveHost flag MUST be ignored when
calling withUri() (previously, Diactoros would not set the Host header
if $preserveHost was true, but no Host header was present).null.UriInterface instance from getUri(); that
instance CAN be empty. Previously, Diactoros would return null; now it
lazy-instantiates an empty Uri instance on initialization.Uri::filterPath() to prevent emitting a path prepended
with multiple slashes.Zend\Diactoros\RequestTrait to
ensure properties inherited from the MessageTrait are inherited by
implementations.#41 fixes the
namespace for test files to begin with ZendTest instead of Zend.
#46 ensures that
the cookie and query params for the ServerRequest implementation are
initialized as arrays.
#47 modifies the
internal logic in HeaderSecurity::isValid() to use a regular expression
instead of character-by-character comparisons, improving performance.
Zend\Diactoros\RelativeStream, which will return stream contents relative to
a given offset (i.e., a subset of the stream). AbstractSerializer was
updated to create a RelativeStream when creating the body of a message,
which will prevent duplication of the stream in-memory..gitattributes file that excludes directories and files not needed for
production; this will further minify the package for production use cases.Zend\Diactoros\Request to use a php://temp stream by default instead of
php://memory, to ensure requests do not create an out-of-memory condition.Zend\Diactoros\Stream to ensure that write operations trigger an exception
if the stream is not writeable. Additionally, it adds more robust logic for
determining if a stream is writeable.First stable release, and first release as zend-diactoros.