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