<?php /** * Zend Framework (http://framework.zend.com/) * * @see http://github.com/zendframework/zend-diactoros for the canonical source repository * @copyright Copyright (c) 2015-2016 Zend Technologies USA Inc. (http://www.zend.com) * @license https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License */ namespace Zend\Diactoros; use Psr\Http\Message\StreamInterface; use UnexpectedValueException; use function array_pop; use function implode; use function ltrim; use function preg_match; use function sprintf; use function str_replace; use function ucwords; /** * Provides base functionality for request and response de/serialization * strategies, including functionality for retrieving a line at a time from * the message, splitting headers from the body, and serializing headers. */ abstract class AbstractSerializer { const CR = "\r"; const EOL = "\r\n"; const LF = "\n"; /** * Retrieve a single line from the stream. * * Retrieves a line from the stream; a line is defined as a sequence of * characters ending in a CRLF sequence. * * @param StreamInterface $stream * @return string * @throws UnexpectedValueException if the sequence contains a CR or LF in * isolation, or ends in a CR. */ protected static function getLine(StreamInterface $stream) { $line = ''; $crFound = false; while (! $stream->eof()) { $char = $stream->read(1); if ($crFound && $char === self::LF) { $crFound = false; break; } // CR NOT followed by LF if ($crFound && $char !== self::LF) { throw new UnexpectedValueException('Unexpected carriage return detected'); } // LF in isolation if (! $crFound && $char === self::LF) { throw new UnexpectedValueException('Unexpected line feed detected'); } // CR found; do not append if ($char === self::CR) { $crFound = true; continue; } // Any other character: append $line .= $char; } // CR found at end of stream if ($crFound) { throw new UnexpectedValueException("Unexpected end of headers"); } return $line; } /** * Split the stream into headers and body content. * * Returns an array containing two elements * * - The first is an array of headers * - The second is a StreamInterface containing the body content * * @param StreamInterface $stream * @return array * @throws UnexpectedValueException For invalid headers. */ protected static function splitStream(StreamInterface $stream) { $headers = []; $currentHeader = false; while ($line = self::getLine($stream)) { if (preg_match(';^(?P<name>[!#$%&\'*+.^_`\|~0-9a-zA-Z-]+):(?P<value>.*)$;', $line, $matches)) { $currentHeader = $matches['name']; if (! isset($headers[$currentHeader])) { $headers[$currentHeader] = []; } $headers[$currentHeader][] = ltrim($matches['value']); continue; } if (! $currentHeader) { throw new UnexpectedValueException('Invalid header detected'); } if (! preg_match('#^[ \t]#', $line)) { throw new UnexpectedValueException('Invalid header continuation'); } // Append continuation to last header value found $value = array_pop($headers[$currentHeader]); $headers[$currentHeader][] = $value . ltrim($line); } // use RelativeStream to avoid copying initial stream into memory return [$headers, new RelativeStream($stream, $stream->tell())]; } /** * Serialize headers to string values. * * @param array $headers * @return string */ protected static function serializeHeaders(array $headers) { $lines = []; foreach ($headers as $header => $values) { $normalized = self::filterHeader($header); foreach ($values as $value) { $lines[] = sprintf('%s: %s', $normalized, $value); } } return implode("\r\n", $lines); } /** * Filter a header name to wordcase * * @param string $header * @return string */ protected static function filterHeader($header) { $filtered = str_replace('-', ' ', $header); $filtered = ucwords($filtered); return str_replace(' ', '-', $filtered); } }