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[!#$%&\'*+.^_`\|~0-9a-zA-Z-]+):(?P.*)$;', $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); } }