Chris@0
|
1 <?php
|
Chris@0
|
2 /**
|
Chris@0
|
3 * Zend Framework (http://framework.zend.com/)
|
Chris@0
|
4 *
|
Chris@0
|
5 * @see http://github.com/zendframework/zend-diactoros for the canonical source repository
|
Chris@0
|
6 * @copyright Copyright (c) 2015-2016 Zend Technologies USA Inc. (http://www.zend.com)
|
Chris@0
|
7 * @license https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License
|
Chris@0
|
8 */
|
Chris@0
|
9
|
Chris@0
|
10 namespace Zend\Diactoros;
|
Chris@0
|
11
|
Chris@0
|
12 use Psr\Http\Message\StreamInterface;
|
Chris@0
|
13 use UnexpectedValueException;
|
Chris@0
|
14
|
Chris@16
|
15 use function array_pop;
|
Chris@16
|
16 use function implode;
|
Chris@16
|
17 use function ltrim;
|
Chris@16
|
18 use function preg_match;
|
Chris@16
|
19 use function sprintf;
|
Chris@16
|
20 use function str_replace;
|
Chris@16
|
21 use function ucwords;
|
Chris@16
|
22
|
Chris@0
|
23 /**
|
Chris@0
|
24 * Provides base functionality for request and response de/serialization
|
Chris@0
|
25 * strategies, including functionality for retrieving a line at a time from
|
Chris@0
|
26 * the message, splitting headers from the body, and serializing headers.
|
Chris@0
|
27 */
|
Chris@0
|
28 abstract class AbstractSerializer
|
Chris@0
|
29 {
|
Chris@0
|
30 const CR = "\r";
|
Chris@0
|
31 const EOL = "\r\n";
|
Chris@0
|
32 const LF = "\n";
|
Chris@0
|
33
|
Chris@0
|
34 /**
|
Chris@0
|
35 * Retrieve a single line from the stream.
|
Chris@0
|
36 *
|
Chris@0
|
37 * Retrieves a line from the stream; a line is defined as a sequence of
|
Chris@0
|
38 * characters ending in a CRLF sequence.
|
Chris@0
|
39 *
|
Chris@0
|
40 * @param StreamInterface $stream
|
Chris@0
|
41 * @return string
|
Chris@0
|
42 * @throws UnexpectedValueException if the sequence contains a CR or LF in
|
Chris@0
|
43 * isolation, or ends in a CR.
|
Chris@0
|
44 */
|
Chris@0
|
45 protected static function getLine(StreamInterface $stream)
|
Chris@0
|
46 {
|
Chris@0
|
47 $line = '';
|
Chris@0
|
48 $crFound = false;
|
Chris@0
|
49 while (! $stream->eof()) {
|
Chris@0
|
50 $char = $stream->read(1);
|
Chris@0
|
51
|
Chris@0
|
52 if ($crFound && $char === self::LF) {
|
Chris@0
|
53 $crFound = false;
|
Chris@0
|
54 break;
|
Chris@0
|
55 }
|
Chris@0
|
56
|
Chris@0
|
57 // CR NOT followed by LF
|
Chris@0
|
58 if ($crFound && $char !== self::LF) {
|
Chris@0
|
59 throw new UnexpectedValueException('Unexpected carriage return detected');
|
Chris@0
|
60 }
|
Chris@0
|
61
|
Chris@0
|
62 // LF in isolation
|
Chris@0
|
63 if (! $crFound && $char === self::LF) {
|
Chris@0
|
64 throw new UnexpectedValueException('Unexpected line feed detected');
|
Chris@0
|
65 }
|
Chris@0
|
66
|
Chris@0
|
67 // CR found; do not append
|
Chris@0
|
68 if ($char === self::CR) {
|
Chris@0
|
69 $crFound = true;
|
Chris@0
|
70 continue;
|
Chris@0
|
71 }
|
Chris@0
|
72
|
Chris@0
|
73 // Any other character: append
|
Chris@0
|
74 $line .= $char;
|
Chris@0
|
75 }
|
Chris@0
|
76
|
Chris@0
|
77 // CR found at end of stream
|
Chris@0
|
78 if ($crFound) {
|
Chris@0
|
79 throw new UnexpectedValueException("Unexpected end of headers");
|
Chris@0
|
80 }
|
Chris@0
|
81
|
Chris@0
|
82 return $line;
|
Chris@0
|
83 }
|
Chris@0
|
84
|
Chris@0
|
85 /**
|
Chris@0
|
86 * Split the stream into headers and body content.
|
Chris@0
|
87 *
|
Chris@0
|
88 * Returns an array containing two elements
|
Chris@0
|
89 *
|
Chris@0
|
90 * - The first is an array of headers
|
Chris@0
|
91 * - The second is a StreamInterface containing the body content
|
Chris@0
|
92 *
|
Chris@0
|
93 * @param StreamInterface $stream
|
Chris@0
|
94 * @return array
|
Chris@0
|
95 * @throws UnexpectedValueException For invalid headers.
|
Chris@0
|
96 */
|
Chris@0
|
97 protected static function splitStream(StreamInterface $stream)
|
Chris@0
|
98 {
|
Chris@0
|
99 $headers = [];
|
Chris@0
|
100 $currentHeader = false;
|
Chris@0
|
101
|
Chris@0
|
102 while ($line = self::getLine($stream)) {
|
Chris@0
|
103 if (preg_match(';^(?P<name>[!#$%&\'*+.^_`\|~0-9a-zA-Z-]+):(?P<value>.*)$;', $line, $matches)) {
|
Chris@0
|
104 $currentHeader = $matches['name'];
|
Chris@0
|
105 if (! isset($headers[$currentHeader])) {
|
Chris@0
|
106 $headers[$currentHeader] = [];
|
Chris@0
|
107 }
|
Chris@0
|
108 $headers[$currentHeader][] = ltrim($matches['value']);
|
Chris@0
|
109 continue;
|
Chris@0
|
110 }
|
Chris@0
|
111
|
Chris@0
|
112 if (! $currentHeader) {
|
Chris@0
|
113 throw new UnexpectedValueException('Invalid header detected');
|
Chris@0
|
114 }
|
Chris@0
|
115
|
Chris@0
|
116 if (! preg_match('#^[ \t]#', $line)) {
|
Chris@0
|
117 throw new UnexpectedValueException('Invalid header continuation');
|
Chris@0
|
118 }
|
Chris@0
|
119
|
Chris@0
|
120 // Append continuation to last header value found
|
Chris@0
|
121 $value = array_pop($headers[$currentHeader]);
|
Chris@0
|
122 $headers[$currentHeader][] = $value . ltrim($line);
|
Chris@0
|
123 }
|
Chris@0
|
124
|
Chris@0
|
125 // use RelativeStream to avoid copying initial stream into memory
|
Chris@0
|
126 return [$headers, new RelativeStream($stream, $stream->tell())];
|
Chris@0
|
127 }
|
Chris@0
|
128
|
Chris@0
|
129 /**
|
Chris@0
|
130 * Serialize headers to string values.
|
Chris@0
|
131 *
|
Chris@0
|
132 * @param array $headers
|
Chris@0
|
133 * @return string
|
Chris@0
|
134 */
|
Chris@0
|
135 protected static function serializeHeaders(array $headers)
|
Chris@0
|
136 {
|
Chris@0
|
137 $lines = [];
|
Chris@0
|
138 foreach ($headers as $header => $values) {
|
Chris@0
|
139 $normalized = self::filterHeader($header);
|
Chris@0
|
140 foreach ($values as $value) {
|
Chris@0
|
141 $lines[] = sprintf('%s: %s', $normalized, $value);
|
Chris@0
|
142 }
|
Chris@0
|
143 }
|
Chris@0
|
144
|
Chris@0
|
145 return implode("\r\n", $lines);
|
Chris@0
|
146 }
|
Chris@0
|
147
|
Chris@0
|
148 /**
|
Chris@0
|
149 * Filter a header name to wordcase
|
Chris@0
|
150 *
|
Chris@0
|
151 * @param string $header
|
Chris@0
|
152 * @return string
|
Chris@0
|
153 */
|
Chris@0
|
154 protected static function filterHeader($header)
|
Chris@0
|
155 {
|
Chris@0
|
156 $filtered = str_replace('-', ' ', $header);
|
Chris@0
|
157 $filtered = ucwords($filtered);
|
Chris@0
|
158 return str_replace(' ', '-', $filtered);
|
Chris@0
|
159 }
|
Chris@0
|
160 }
|