3 * Zend Framework (http://framework.zend.com/)
5 * @see http://github.com/zendframework/zend-diactoros for the canonical source repository
6 * @copyright Copyright (c) 2015-2016 Zend Technologies USA Inc. (http://www.zend.com)
7 * @license https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License
10 namespace Zend\Diactoros;
12 use Psr\Http\Message\StreamInterface;
13 use UnexpectedValueException;
15 use function array_pop;
18 use function preg_match;
20 use function str_replace;
24 * Provides base functionality for request and response de/serialization
25 * strategies, including functionality for retrieving a line at a time from
26 * the message, splitting headers from the body, and serializing headers.
28 abstract class AbstractSerializer
35 * Retrieve a single line from the stream.
37 * Retrieves a line from the stream; a line is defined as a sequence of
38 * characters ending in a CRLF sequence.
40 * @param StreamInterface $stream
42 * @throws UnexpectedValueException if the sequence contains a CR or LF in
43 * isolation, or ends in a CR.
45 protected static function getLine(StreamInterface $stream)
49 while (! $stream->eof()) {
50 $char = $stream->read(1);
52 if ($crFound && $char === self::LF) {
57 // CR NOT followed by LF
58 if ($crFound && $char !== self::LF) {
59 throw new UnexpectedValueException('Unexpected carriage return detected');
63 if (! $crFound && $char === self::LF) {
64 throw new UnexpectedValueException('Unexpected line feed detected');
67 // CR found; do not append
68 if ($char === self::CR) {
73 // Any other character: append
77 // CR found at end of stream
79 throw new UnexpectedValueException("Unexpected end of headers");
86 * Split the stream into headers and body content.
88 * Returns an array containing two elements
90 * - The first is an array of headers
91 * - The second is a StreamInterface containing the body content
93 * @param StreamInterface $stream
95 * @throws UnexpectedValueException For invalid headers.
97 protected static function splitStream(StreamInterface $stream)
100 $currentHeader = false;
102 while ($line = self::getLine($stream)) {
103 if (preg_match(';^(?P<name>[!#$%&\'*+.^_`\|~0-9a-zA-Z-]+):(?P<value>.*)$;', $line, $matches)) {
104 $currentHeader = $matches['name'];
105 if (! isset($headers[$currentHeader])) {
106 $headers[$currentHeader] = [];
108 $headers[$currentHeader][] = ltrim($matches['value']);
112 if (! $currentHeader) {
113 throw new UnexpectedValueException('Invalid header detected');
116 if (! preg_match('#^[ \t]#', $line)) {
117 throw new UnexpectedValueException('Invalid header continuation');
120 // Append continuation to last header value found
121 $value = array_pop($headers[$currentHeader]);
122 $headers[$currentHeader][] = $value . ltrim($line);
125 // use RelativeStream to avoid copying initial stream into memory
126 return [$headers, new RelativeStream($stream, $stream->tell())];
130 * Serialize headers to string values.
132 * @param array $headers
135 protected static function serializeHeaders(array $headers)
138 foreach ($headers as $header => $values) {
139 $normalized = self::filterHeader($header);
140 foreach ($values as $value) {
141 $lines[] = sprintf('%s: %s', $normalized, $value);
145 return implode("\r\n", $lines);
149 * Filter a header name to wordcase
151 * @param string $header
154 protected static function filterHeader($header)
156 $filtered = str_replace('-', ' ', $header);
157 $filtered = ucwords($filtered);
158 return str_replace(' ', '-', $filtered);