| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967 |
- <?php
-
- /**
- * Hoa
- *
- *
- * @license
- *
- * New BSD License
- *
- * Copyright © 2007-2017, Hoa community. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- * * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * * Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * * Neither the name of the Hoa nor the names of its contributors may be
- * used to endorse or promote products derived from this software without
- * specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- */
-
- namespace Hoa\Http\Response;
-
- use Hoa\Consistency;
- use Hoa\Http;
- use Hoa\Stream;
-
- /**
- * Class \Hoa\Http\Response.
- *
- * HTTP response support.
- *
- * @TODO Follow http://tools.ietf.org/html/draft-nottingham-http-new-status-03.
- *
- * @copyright Copyright © 2007-2017 Hoa community
- * @license New BSD License
- */
- class Response
- extends Http
- implements Stream\IStream\Out,
- Stream\IStream\Bufferable
- {
- /**
- * Continue (please, see RFC7231).
- *
- * @const string
- */
- const STATUS_CONTINUE = '100 Continue';
-
- /**
- * Switching protocols (please, see RFC7231).
- *
- * @const string
- */
- const STATUS_SWITCHING_PROTOCOLS = '101 Switching Protocols';
-
- /**
- * Processing (please, see RFC2518).
- *
- * @const string
- */
- const STATUS_PROCESSING = '102 Processing';
-
- /**
- * OK (please, see RFC7231).
- *
- * @const string
- */
- const STATUS_OK = '200 Ok';
-
- /**
- * Created (please, see RFC7231).
- *
- * @const string
- */
- const STATUS_CREATED = '201 Created';
-
- /**
- * Accepted (please, see RFC7231).
- *
- * @const string
- */
- const STATUS_ACCEPTED = '202 Accepted';
-
- /**
- * Non-authoritative information (please, see RFC7231).
- *
- * @const string
- */
- const STATUS_NON_AUTHORITATIVE_INFORMATION = '203 Non Authoritative Information';
-
- /**
- * No content (please, see RFC7231).
- *
- * @const string
- */
- const STATUS_NO_CONTENT = '204 No Content';
-
- /**
- * Reset content (please, see RFC7231).
- *
- * @const string
- */
- const STATUS_RESET_CONTENT = '205 Reset Content';
-
- /**
- * Partial content (please, see RFC7233).
- *
- * @const string
- */
- const STATUS_PARTIAL_CONTENT = '206 Partial Content';
-
- /**
- * Multi-status (please, see RFC4918).
- *
- * @const string
- */
- const STATUS_MULTI_STATUS = '207 Multi-Status';
-
- /**
- * Already Reported (please, see RFC5842).
- *
- * @const string
- */
- const STATUS_ALREADY_REPORTED = '208 Already Reported';
-
- /**
- * IM used (please, see RFC3229).
- *
- * @const string
- */
- const STATUS_IM_USED = '226 IM Used';
-
- /**
- * Multiple choices (please, see RFC7231).
- *
- * @const string
- */
- const STATUS_MULTIPLE_CHOICES = '300 Multiple Choices';
-
- /**
- * Moved permanently (please, see RFC7231).
- *
- * @const string
- */
- const STATUS_MOVED_PERMANENTLY = '301 Moved Permanently';
-
- /**
- * Found (please, see RFC7231).
- *
- * @const string
- */
- const STATUS_FOUND = '302 Found';
-
- /**
- * See other (please, see RFC7231).
- *
- * @const string
- */
- const STATUS_SEE_OTHER = '303 See Other';
-
- /**
- * Not modified (please, see RFC7232).
- *
- * @const string
- */
- const STATUS_NOT_MODIFIED = '304 Not Modified';
-
- /**
- * Use proxy (please, see RFC7231).
- *
- * @const string
- */
- const STATUS_USE_PROXY = '305 Use Proxy';
-
- /**
- * Temporary redirect (please, see RFC7231).
- *
- * @const string
- */
- const STATUS_TEMPORARY_REDIRECT = '307 Temporary Redirect';
-
- /**
- * Permanent redirect (please, see RFC7238).
- *
- * @const string
- */
- const STATUS_PERMANENT_REDIRECT = '308 Permanent Redirect';
-
- /**
- * Bad request (please, see RFC7231).
- *
- * @const string
- */
- const STATUS_BAD_REQUEST = '400 Bad Request';
-
- /**
- * Unauthorized (please, see RFC7235).
- *
- * @const string
- */
- const STATUS_UNAUTHORIZED = '401 Unauthorized';
-
- /**
- * Payment required (please, see RFC7231).
- *
- * @const string
- */
- const STATUS_PAYMENT_REQUIRED = '402 Payment Required';
-
- /**
- * Forbidden (please, see RFC7231).
- *
- * @const string
- */
- const STATUS_FORBIDDEN = '403 Forbidden';
-
- /**
- * Not found (please, see RFC7231).
- *
- * @const string
- */
- const STATUS_NOT_FOUND = '404 Not Found';
-
- /**
- * Method not allowed (please, see RFC7231).
- *
- * @const string
- */
- const STATUS_METHOD_NOT_ALLOWED = '405 Method Not Allowed';
-
- /**
- * Not acceptable (please, see RFC7231).
- *
- * @const string
- */
- const STATUS_NOT_ACCEPTABLE = '406 Not Acceptable';
-
- /**
- * Proxy authentication required (please, see RFC7235).
- *
- * @const string
- */
- const STATUS_PROXY_AUTHENTICATION_REQUIRED = '407 Proxy Authentication Required';
-
- /**
- * Request time-out (please, see RFC7231).
- *
- * @const string
- */
- const STATUS_REQUEST_TIME_OUT = '408 Request Timeout';
-
- /**
- * Conflict (please, see RFC7231).
- *
- * @const string
- */
- const STATUS_CONFLICT = '409 Conflict';
-
- /**
- * Gone (please, see RFC7231).
- *
- * @const string
- */
- const STATUS_GONE = '410 Gone';
-
- /**
- * Length required (please, see RFC7231).
- *
- * @const string
- */
- const STATUS_LENGTH_REQUIRED = '411 Length Required';
-
- /**
- * Precondition failed (please, see RFC7232).
- *
- * @const string
- */
- const STATUS_PRECONDITION_FAILED = '412 Precondition Failed';
-
- /**
- * Request entity too large (please, see RFC7231).
- *
- * @const string
- */
- const STATUS_REQUEST_ENTITY_TOO_LARGE = '413 Request Entity Too Large';
-
- /**
- * Request URI too large (please, see RFC7231).
- *
- * @const string
- */
- const STATUS_REQUEST_URI_TOO_LARGE = '414 Request URI Too Large';
-
- /**
- * Unsupported media type (please, see RFC7231).
- *
- * @const string
- */
- const STATUS_UNSUPPORTED_MEDIA_TYPE = '415 Unsupported Media Type';
-
- /**
- * Requested range not satisfiable (please, see RFC7233).
- *
- * @const string
- */
- const STATUS_REQUESTED_RANGE_NOT_SATISFIABLE = '416 Requested Range Not Satisfiable';
-
- /**
- * Expectation failed (please, see RFC7231).
- *
- * @const string
- */
- const STATUS_EXPECTATION_FAILED = '417 Expectation Failed';
-
- /**
- * I'm a teapot (see RFC2324, April Fool's joke).
- *
- * @const string
- */
- const STATUS_IM_A_TEAPOT = '418 I\'m a teapot';
-
- /**
- * Unprocessable Entity (please, see RFC4918).
- *
- * @const string
- */
- const STATUS_UNPROCESSABLE_ENTITY = '422 Unprocessable Entity';
-
- /**
- * Locked (please, see RFC4918).
- *
- * @const string
- */
- const STATUS_LOCKED = '423 Locked';
-
- /**
- * Failed Dependency (please, see RFC4918).
- *
- * @const string
- */
- const STATUS_FAILED_DEPENDENCY = '424 Failed Dependency';
-
- /**
- * Upgrade required (please, see RFC7231).
- *
- * @const string
- */
- const STATUS_UPGRADE_REQUIRED = '426 Upgrade Required';
-
- /**
- * Precondition Required (please, see RFC6585).
- *
- * @const string
- */
- const STATUS_PRECONDITION_REQUIRED = '428 Precondition Required';
-
- /**
- * Too Many Requests (please, see RFC6585).
- *
- * @const string
- */
- const STATUS_TOO_MANY_REQUESTS = '429 Too Many Requests';
-
- /**
- * Request Header Fields Too Large (please, see RFC6585).
- *
- * @const string
- */
- const STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE = '431 Request Header Fields Too Large';
-
- /**
- * Internal server error (please, see RFC7231).
- *
- * @const string
- */
- const STATUS_INTERNAL_SERVER_ERROR = '500 Internal Server Error';
-
- /**
- * Not implemented (please, see RFC7231).
- *
- * @const string
- */
- const STATUS_NOT_IMPLEMENTED = '501 Not Implemented';
-
- /**
- * Bad gateway (please, see RFC7231).
- *
- * @const string
- */
- const STATUS_BAD_GATEWAY = '502 Bad Gateway';
-
- /**
- * Service unavailable (please, see RFC7231).
- *
- * @const string
- */
- const STATUS_SERVICE_UNAVAILABLE = '503 Service Unavailable';
-
- /**
- * Gateway time-out (please, see RFC7231).
- *
- * @const string
- */
- const STATUS_GATEWAY_TIME_OUT = '504 Gateway Time Out';
-
- /**
- * HTTP version not supported (please, see RFC7231).
- *
- * @const string
- */
- const STATUS_HTTP_VERSION_NOT_SUPPORTED = '505 HTTP Version Not Supported';
-
- /**
- * Variant Also Negotiates (please, see RFC2295).
- *
- * @const string
- */
- const STATUS_VARIANT_ALSO_NEGOTIATES = '506 Variant Also Negotiates';
-
- /**
- * Insufficient Storage (please, see RFC4918).
- *
- * @const string
- */
- const STATUS_INSUFFICIENT_STORAGE = '507 Insufficient Storage';
-
- /**
- * Loop Detected (please, see RFC5842).
- *
- * @const string
- */
- const STATUS_LOOP_DETECTED = '508 Loop Detected';
-
- /**
- * Not Extended (please, see RFC2774).
- *
- * @const string
- */
- const STATUS_NOT_EXTENDED = '510 Not Extended';
-
- /**
- * Network Authentication Required (please, see RFC6585).
- *
- * @const string
- */
- const STATUS_NETWORK_AUTHENTICATION_REQUIRED = '511 Network Authentication Required';
-
- /**
- * Status (different ordering).
- *
- * @var array
- */
- private $_status = [];
-
- /**
- * This object hash.
- *
- * @var string
- */
- private $_hash = null;
-
- /**
- * ob_*() is stateless, so we manage a stack to avoid cross-buffers
- * manipulations.
- *
- * @var array
- */
- private static $_stack = [];
-
-
-
- /**
- * Constructor.
- *
- * @param bool $newBuffer Whether we run $this->newBuffer().
- * Following arguments are for this
- * method.
- * @param mixed $callable Callable.
- * @param int $size Size.
- */
- public function __construct($newBuffer = true, $callable = null, $size = null)
- {
- parent::__construct();
- $this->_hash = spl_object_hash($this);
-
- if (true === $newBuffer) {
- $this->newBuffer($callable, $size);
- }
-
- if (empty($this->_status)) {
- $reflection = new \ReflectionClass($this);
-
- foreach ($reflection->getConstants() as $value) {
- $this->_status[$this->getStatus($value)] = $value;
- }
- }
-
- return;
- }
-
- /**
- * Parse a HTTP packet.
- *
- * @param string $packet HTTP packet.
- * @return void
- * @throws \Hoa\Http\Exception
- */
- public function parse($packet)
- {
- $headers = explode("\r\n", $packet);
- $status = array_shift($headers);
- $this->setBody(null);
-
- foreach ($headers as $i => $header) {
- if ('' == trim($header)) {
- unset($headers[$i]);
- $this->setBody(
- trim(
- implode("\r\n", array_splice($headers, $i))
- )
- );
-
- break;
- }
- }
-
- if (0 === preg_match('#^HTTP/(1\.(?:0|1))\s+(\d{3})#i', $status, $matches)) {
- throw new Http\Exception(
- 'HTTP status is not well-formed: %s.',
- 0,
- $status
- );
- }
-
- if (!isset($this->_status[$matches[2]])) {
- throw new Http\Exception(
- 'Unknown HTTP status %d in %s.',
- 1,
- [$matches[2], $status]
- );
- }
-
- $this->setHttpVersion((float) $matches[1]);
- $this->_parse($headers);
- $this['status'] = $this->_status[$matches[2]];
-
- return;
- }
-
- /**
- * Get real status from static::STATUS_* constants.
- *
- * @param string $status Status.
- * @return int
- */
- public static function getStatus($status)
- {
- return (int) substr($status, 0, 3);
- }
-
- /**
- * Send a new status.
- *
- * @param string $status Status. Please, see static::STATUS_*
- * constants.
- * @param bool $replace Whether replace an existing sent header.
- * @return void
- */
- public function sendStatus($status, $replace = true)
- {
- return $this->sendHeader('status', $status, $replace, $status);
- }
-
- /**
- * Send a new header.
- *
- * @param string $header Header.
- * @param string $value Value.
- * @param bool $replace Whether replace an existing sent header.
- * @param string $status Force a specific status. Please, see
- * static::STATUS_* constants.
- * @return void
- */
- public function sendHeader(
- $header,
- $value,
- $replace = true,
- $status = null
- ) {
- if (0 === strcasecmp('status', $header) &&
- false === self::$_fcgi) {
- header(
- 'HTTP/1.1 ' . $value,
- $replace,
- static::getStatus($value)
- );
-
- return;
- }
-
- header(
- $header . ': ' . $value,
- $replace,
- null !== $status ? static::getStatus($status) : null
- );
-
- return;
- }
-
- /**
- * Send all headers.
- *
- * @return void
- */
- public function sendHeaders()
- {
- foreach ($this->_headers as $header => $value) {
- $this->sendHeader($header, $value);
- }
-
- return;
- }
-
- /**
- * Get send headers.
- *
- * @return string
- */
- public function getSentHeaders()
- {
- return implode("\r\n", headers_list());
- }
-
- /**
- * Start a new buffer.
- * The callable acts like a filter.
- *
- * @param mixed $callable Callable.
- * @param int $size Size.
- * @return int
- */
- public function newBuffer($callable = null, $size = null)
- {
- $last = current(self::$_stack);
- $hash = $this->getHash();
-
- if (false === $last || $hash != $last[0]) {
- self::$_stack[] = [
- 0 => $hash,
- 1 => 1
- ];
- } else {
- ++self::$_stack[key(self::$_stack)][1];
- }
-
- end(self::$_stack);
-
- if (null === $callable) {
- ob_start();
- } else {
- ob_start(xcallable($callable), null === $size ? 0 : $size);
- }
-
- return $this->getBufferLevel();
- }
-
- /**
- * Flush the buffer.
- *
- * @param bool $force Force to flush the output buffer.
- * @return void
- */
- public function flush($force = false)
- {
- if (0 >= $this->getBufferSize()) {
- return;
- }
-
- ob_flush();
-
- if (true === $force) {
- flush();
- }
-
- return;
- }
-
- /**
- * Delete buffer.
- *
- * @return bool
- * @throws \Hoa\Http\Exception\CrossBufferization
- */
- public function deleteBuffer()
- {
- $key = key(self::$_stack);
-
- if ($this->getHash() != self::$_stack[$key][0]) {
- throw new Http\Exception\CrossBufferization(
- 'Cannot delete this buffer because it was not opened by this ' .
- 'class (%s, %s).',
- 2,
- [get_class($this), $this->getHash()]
- );
- }
-
- $out = ob_end_clean();
-
- if (false === $out) {
- return false;
- }
-
- --self::$_stack[$key][1];
-
- if (0 >= self::$_stack[$key][1]) {
- unset(self::$_stack[$key]);
- }
-
- return true;
- }
-
- /**
- * Get buffer level.
- *
- * @return int
- */
- public function getBufferLevel()
- {
- return ob_get_level();
- }
-
- /**
- * Get buffer size.
- *
- * @return int
- */
- public function getBufferSize()
- {
- return ob_get_length();
- }
-
- /**
- * Write n characters.
- *
- * @param string $string String.
- * @param int $length Length.
- * @return mixed
- * @throws \Hoa\Http\Exception
- */
- public function write($string, $length)
- {
- if (0 > $length) {
- throw new Http\Exception(
- 'Length must be greater than 0, given %d.',
- 3,
- $length
- );
- }
-
- if (strlen($string) > $length) {
- $string = substr($string, 0, $length);
- }
-
- echo $string;
-
- return;
- }
-
- /**
- * Write a string.
- *
- * @param string $string String.
- * @return mixed
- */
- public function writeString($string)
- {
- echo (string) $string;
-
- return;
- }
-
- /**
- * Write a character.
- *
- * @param string $character Character.
- * @return mixed
- */
- public function writeCharacter($character)
- {
- echo $character[0];
-
- return;
- }
-
- /**
- * Write a boolean.
- *
- * @param bool $boolean Boolean.
- * @return mixed
- */
- public function writeBoolean($boolean)
- {
- echo (string) (bool) $boolean;
-
- return;
- }
-
- /**
- * Write an integer.
- *
- * @param int $integer Integer.
- * @return mixed
- */
- public function writeInteger($integer)
- {
- echo (string) (int) $integer;
-
- return;
- }
-
- /**
- * Write a float.
- *
- * @param float $float Float.
- * @return mixed
- */
- public function writeFloat($float)
- {
- echo (string) (float) $float;
-
- return;
- }
-
- /**
- * Write an array.
- *
- * @param array $array Array.
- * @return mixed
- */
- public function writeArray(array $array)
- {
- echo var_export($array, true);
-
- return;
- }
-
- /**
- * Write a line.
- *
- * @param string $line Line.
- * @return mixed
- */
- public function writeLine($line)
- {
- if (false !== $n = strpos($line, "\n")) {
- $line = substr($line, 0, $n + 1);
- }
-
- echo $line;
-
- return;
- }
-
- /**
- * Write all, i.e. as much as possible.
- *
- * @param string $string String.
- * @return mixed
- */
- public function writeAll($string)
- {
- echo $string;
-
- return;
- }
-
- /**
- * Truncate a file to a given length.
- *
- * @param int $size Size.
- * @return bool
- */
- public function truncate($size)
- {
- if (0 === $size) {
- ob_clean();
-
- return true;
- }
-
- $bSize = $this->getBufferSize();
-
- if ($size >= $bSize) {
- return true;
- }
-
- echo substr(ob_get_clean(), 0, $size);
-
- return true;
- }
-
- /**
- * Get the current stream.
- *
- * @return resource
- */
- public function getStream()
- {
- return fopen('php://stdout', 'w');
- }
-
- /**
- * Get this object hash.
- *
- * @return string
- */
- public function getHash()
- {
- return $this->_hash;
- }
-
- /**
- * Delete head buffer.
- *
- * @return void
- */
- public function __destruct()
- {
- $last = current(self::$_stack);
-
- if ($this->getHash() != $last[0]) {
- return;
- }
-
- for ($i = 0, $max = $last[1]; $i < $max; ++$i) {
- $this->flush();
-
- if (0 < $this->getBufferLevel()) {
- $this->deleteBuffer();
- }
- }
-
- return;
- }
- }
-
- /**
- * Flex entity.
- */
- Consistency::flexEntity('Hoa\Http\Response\Response');
|