borobudur-php /
borobudur-http
This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
| 1 | <?php |
||
| 2 | /* |
||
| 3 | * This file is part of the Borobudur-Http package. |
||
| 4 | * |
||
| 5 | * (c) Hexacodelabs <http://hexacodelabs.com> |
||
| 6 | * |
||
| 7 | * For the full copyright and license information, please view the LICENSE |
||
| 8 | * file that was distributed with this source code. |
||
| 9 | */ |
||
| 10 | |||
| 11 | namespace Borobudur\Http; |
||
| 12 | |||
| 13 | use Borobudur\Http\Exception\InvalidArgumentException; |
||
| 14 | use Borobudur\Http\Header\Accept\AcceptHeader; |
||
| 15 | use Borobudur\Http\Header\CacheControlHeader; |
||
| 16 | use Borobudur\Http\Header\Content\ContentLengthHeader; |
||
| 17 | use Borobudur\Http\Header\Content\ContentTypeHeader; |
||
| 18 | use Borobudur\Http\Header\ExpiresHeader; |
||
| 19 | use Borobudur\Http\Header\PragmaHeader; |
||
| 20 | use Borobudur\Http\Header\TransferEncodingHeader; |
||
| 21 | use DateTime; |
||
| 22 | |||
| 23 | /** |
||
| 24 | * @author Iqbal Maulana <[email protected]> |
||
| 25 | * @created 7/30/15 |
||
| 26 | */ |
||
| 27 | class Response extends AbstractResponse |
||
| 28 | { |
||
| 29 | use HttpStatusTrait; |
||
| 30 | |||
| 31 | /** |
||
| 32 | * @const string |
||
| 33 | */ |
||
| 34 | const HTTP_VERSION_10 = '1.0'; |
||
| 35 | const HTTP_VERSION_11 = '1.1'; |
||
| 36 | |||
| 37 | /** |
||
| 38 | * @var SetCookieHeaderBag |
||
| 39 | */ |
||
| 40 | public $cookies; |
||
| 41 | |||
| 42 | /** |
||
| 43 | * @var int |
||
| 44 | */ |
||
| 45 | protected $chunkLength; |
||
| 46 | |||
| 47 | /** |
||
| 48 | * @var int |
||
| 49 | */ |
||
| 50 | protected $chunkDelayResponse; |
||
| 51 | |||
| 52 | /** |
||
| 53 | * Constructor. |
||
| 54 | * |
||
| 55 | * @param string $content |
||
| 56 | * @param int $statusCode |
||
| 57 | * @param array $headers |
||
| 58 | * @param array $cookies |
||
| 59 | */ |
||
| 60 | public function __construct($content = '', $statusCode = 200, array $headers = array(), array $cookies = array()) |
||
| 61 | { |
||
| 62 | $this->headers = new HeaderBag($headers); |
||
| 63 | $this->cookies = new SetCookieHeaderBag($cookies); |
||
| 64 | $this->setStatusCode($statusCode); |
||
| 65 | $this->setContent($content); |
||
| 66 | $this->setProtocolVersion(Response::HTTP_VERSION_11); |
||
| 67 | } |
||
| 68 | |||
| 69 | /** |
||
| 70 | * Prepare Response base on Request. |
||
| 71 | * |
||
| 72 | * @param Request $request |
||
| 73 | * |
||
| 74 | * @return $this |
||
| 75 | */ |
||
| 76 | public function prepare(Request $request) |
||
| 77 | { |
||
| 78 | if ($this->isEmpty() || $this->isInformational()) { |
||
| 79 | $this->setContent(null); |
||
| 80 | $this->headers->remove('Content-Type'); |
||
| 81 | $this->headers->remove('Content-Length'); |
||
| 82 | } else { |
||
| 83 | $this->fixContentType($request); |
||
| 84 | $this->fixContentLength($request); |
||
| 85 | } |
||
| 86 | |||
| 87 | // Fix protocol |
||
| 88 | if ('HTTP/1.1' !== $request->getServerBag()->get('SERVER_PROTOCOL')) { |
||
| 89 | $this->setProtocolVersion('1.0'); |
||
| 90 | } |
||
| 91 | |||
| 92 | $this->fixExpire(); |
||
| 93 | |||
| 94 | return $this; |
||
| 95 | } |
||
| 96 | |||
| 97 | /** |
||
| 98 | * Set response as chunked. |
||
| 99 | * |
||
| 100 | * @param int $length Content split length. |
||
| 101 | * @param int $delay Delay response in microseconds. |
||
| 102 | * |
||
| 103 | * @return $this |
||
| 104 | */ |
||
| 105 | public function setChunkedTransferEncoding($length = 76, $delay = 1000000) |
||
| 106 | { |
||
| 107 | $this->headers->set(new TransferEncodingHeader('chunked'), true); |
||
| 108 | $this->chunkLength = $length; |
||
| 109 | $this->chunkDelayResponse = $delay; |
||
| 110 | |||
| 111 | return $this; |
||
| 112 | } |
||
| 113 | |||
| 114 | /** |
||
| 115 | * Set response as not modified. |
||
| 116 | * |
||
| 117 | * @return $this |
||
| 118 | */ |
||
| 119 | public function setNotModified() |
||
| 120 | { |
||
| 121 | $this->setStatusCode(HttpStatus::HTTP_NOT_MODIFIED); |
||
| 122 | $this->setContent(null); |
||
| 123 | |||
| 124 | foreach ( |
||
| 125 | array( |
||
| 126 | 'Allow', |
||
| 127 | 'Content-Encoding', |
||
| 128 | 'Content-Language', |
||
| 129 | 'Content-Length', |
||
| 130 | 'Content-MD5', |
||
| 131 | 'Content-Type', |
||
| 132 | 'Transfer-Encoding', |
||
| 133 | 'Last-Modified', |
||
| 134 | ) as $header |
||
| 135 | ) { |
||
| 136 | $this->headers->remove($header); |
||
| 137 | } |
||
| 138 | |||
| 139 | return $this; |
||
| 140 | } |
||
| 141 | |||
| 142 | /** |
||
| 143 | * Check if response is no cache. |
||
| 144 | * |
||
| 145 | * @return bool |
||
| 146 | */ |
||
| 147 | public function isNoCache() |
||
| 148 | { |
||
| 149 | return |
||
| 150 | !$this->headers->has('Cache-Control') |
||
| 151 | && !$this->headers->has('ETag') |
||
| 152 | && !$this->headers->has('Last-Modified') |
||
| 153 | && !$this->headers->has('Expires'); |
||
| 154 | } |
||
| 155 | |||
| 156 | /** |
||
| 157 | * Send response to client. |
||
| 158 | * |
||
| 159 | * @return $this |
||
| 160 | */ |
||
| 161 | public function send() |
||
| 162 | { |
||
| 163 | $content = $this->computeContent(); |
||
| 164 | $this->sendHeaders(); |
||
| 165 | |||
| 166 | if ($content) { |
||
|
0 ignored issues
–
show
|
|||
| 167 | if ($this->isChunked()) { |
||
| 168 | $this->sendChunkContent($content); |
||
| 169 | } else { |
||
| 170 | echo $content; |
||
| 171 | } |
||
| 172 | |||
| 173 | if (function_exists('fastcgi_finish_request')) { |
||
| 174 | fastcgi_finish_request(); |
||
| 175 | } else { |
||
| 176 | ob_end_flush(); |
||
| 177 | } |
||
| 178 | } |
||
| 179 | |||
| 180 | return $this; |
||
| 181 | } |
||
| 182 | |||
| 183 | /** |
||
| 184 | * Send response header. |
||
| 185 | * |
||
| 186 | * @return $this |
||
| 187 | */ |
||
| 188 | public function sendHeaders() |
||
| 189 | { |
||
| 190 | // check is headers already sent. |
||
| 191 | if (headers_sent()) { |
||
| 192 | return $this; |
||
| 193 | } |
||
| 194 | |||
| 195 | $this->computeContentType(); |
||
| 196 | $this->headers->set($this->computeCacheControl(), true); |
||
| 197 | |||
| 198 | // send status header |
||
| 199 | header(sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText), true, $this->statusCode); |
||
| 200 | // send headers |
||
| 201 | $this->createHeaders(); |
||
| 202 | // send cookies header |
||
| 203 | $this->createCookieHeaders(); |
||
| 204 | |||
| 205 | return $this; |
||
| 206 | } |
||
| 207 | |||
| 208 | /** |
||
| 209 | * Check if current response is chunked. |
||
| 210 | * |
||
| 211 | * @return bool |
||
| 212 | */ |
||
| 213 | public function isChunked() |
||
| 214 | { |
||
| 215 | return |
||
| 216 | $this->headers->has('Transfer-Encoding') |
||
| 217 | && 'chunked' === $this->headers->first('Transfer-Encoding')->getFieldValue(); |
||
| 218 | } |
||
| 219 | |||
| 220 | /** |
||
| 221 | * Cast Response as string representation. |
||
| 222 | * |
||
| 223 | * @return string |
||
| 224 | */ |
||
| 225 | public function __toString() |
||
| 226 | { |
||
| 227 | $content = $this->computeContent(); |
||
| 228 | $this->computeContentType(); |
||
| 229 | $this->headers->set($this->computeCacheControl(), true); |
||
| 230 | |||
| 231 | return |
||
| 232 | sprintf(sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText)) . "\r\n" . |
||
| 233 | $this->headers . "\r\n" . |
||
| 234 | $this->cookies . "\r\n" . |
||
| 235 | $content; |
||
| 236 | } |
||
| 237 | |||
| 238 | /** |
||
| 239 | * Add charset if content type exist. |
||
| 240 | */ |
||
| 241 | protected function computeContentType() |
||
| 242 | { |
||
| 243 | if ($this->headers->has('Content-Type')) { |
||
| 244 | /** |
||
| 245 | * @var ContentTypeHeader $contentType |
||
| 246 | */ |
||
| 247 | $contentType = $this->headers->first('Content-Type'); |
||
| 248 | $contentType->setParameter('charset', $this->getCharset()); |
||
| 249 | |||
| 250 | $this->headers->set($contentType, true); |
||
| 251 | } |
||
| 252 | } |
||
| 253 | |||
| 254 | /** |
||
| 255 | * Get computed content with specific encoding. |
||
| 256 | * |
||
| 257 | * @return string |
||
| 258 | */ |
||
| 259 | protected function computeContent() |
||
| 260 | { |
||
| 261 | if ($this->isNotModified()) { |
||
| 262 | $this->setNotModified(); |
||
| 263 | |||
| 264 | return null; |
||
| 265 | } |
||
| 266 | |||
| 267 | $encoding = $this->fixEncoding(); |
||
| 268 | $this->headers->set(new ContentLengthHeader($encoding->encode()->getLength()), true); |
||
| 269 | |||
| 270 | if ($this->isChunked()) { |
||
| 271 | if ('1.0' === $this->getProtocolVersion()) { |
||
| 272 | throw new InvalidArgumentException(sprintf( |
||
| 273 | 'Transfer-Encoding chunked need HTTP Protocol version 1.1, "%s" given.', |
||
| 274 | $this->getProtocolVersion() |
||
| 275 | )); |
||
| 276 | } |
||
| 277 | |||
| 278 | return chunk_split($encoding->getContent(), $this->chunkLength); |
||
| 279 | } |
||
| 280 | |||
| 281 | return $encoding->getContent(); |
||
| 282 | } |
||
| 283 | |||
| 284 | /** |
||
| 285 | * Get calculated cache control header. |
||
| 286 | * |
||
| 287 | * @return CacheControlHeader |
||
| 288 | */ |
||
| 289 | protected function computeCacheControl() |
||
| 290 | { |
||
| 291 | if ($this->isNoCache()) { |
||
| 292 | return (new CacheControlHeader())->setNoCache(); |
||
| 293 | } |
||
| 294 | |||
| 295 | if (!$this->headers->has('Cache-Control')) { |
||
| 296 | return (new CacheControlHeader())->setPrivate()->setMustReValidate(); |
||
| 297 | } |
||
| 298 | |||
| 299 | /** |
||
| 300 | * @var CacheControlHeader $header |
||
| 301 | */ |
||
| 302 | $header = $this->headers->first('Cache-Control', new CacheControlHeader()); |
||
| 303 | |||
| 304 | return $header->setAutoPrivilege(); |
||
| 305 | } |
||
| 306 | |||
| 307 | /** |
||
| 308 | * Add content type header from request if not defined. |
||
| 309 | * |
||
| 310 | * @param Request $request |
||
| 311 | */ |
||
| 312 | private function fixContentType(Request $request) |
||
| 313 | { |
||
| 314 | // Add content type from request if not defined. |
||
| 315 | if (false === $this->headers->has('Content-Type') && true === $request->getHeaderBag()->has('Accept')) { |
||
| 316 | /** |
||
| 317 | * @var AcceptHeader $acceptHeader |
||
| 318 | */ |
||
| 319 | $acceptHeader = $request->getHeaderBag()->first('Accept'); |
||
| 320 | // get first accept content type= |
||
| 321 | $this->setContentType($acceptHeader->first()->getValue()); |
||
| 322 | } |
||
| 323 | } |
||
| 324 | |||
| 325 | /** |
||
| 326 | * Fix content length header. |
||
| 327 | * |
||
| 328 | * @param Request $request |
||
| 329 | */ |
||
| 330 | private function fixContentLength(Request $request) |
||
| 331 | { |
||
| 332 | if ($this->headers->has('Transfer-Encoding')) { |
||
| 333 | $this->headers->remove('Content-Length'); |
||
| 334 | } |
||
| 335 | |||
| 336 | if ($request->isMethod(Request::HTTP_METHOD_HEAD)) { |
||
| 337 | $contentLengthHeader = $this->headers->first('Content-Length'); |
||
| 338 | $this->setContent(null); |
||
| 339 | if ($length = $contentLengthHeader->getFieldValue()) { |
||
| 340 | $this->headers->set(new ContentLengthHeader($length), true); |
||
| 341 | } |
||
| 342 | } |
||
| 343 | } |
||
| 344 | |||
| 345 | /** |
||
| 346 | * Send extra expire info if needed. |
||
| 347 | */ |
||
| 348 | private function fixExpire() |
||
| 349 | { |
||
| 350 | if ('1.0' === $this->getProtocolVersion() && $this->headers->has('Cache-Control')) { |
||
| 351 | /** |
||
| 352 | * @var CacheControlHeader $cacheControl |
||
| 353 | */ |
||
| 354 | $cacheControl = $this->headers->first('Cache-Control'); |
||
| 355 | if (true === $cacheControl->isNoCache()) { |
||
| 356 | $this->headers->set(new PragmaHeader('no-cache'), true); |
||
| 357 | $this->headers->set(new ExpiresHeader(new DateTime('-1 year')), true); |
||
| 358 | } |
||
| 359 | } |
||
| 360 | } |
||
| 361 | |||
| 362 | /** |
||
| 363 | * Fix http encoding header. |
||
| 364 | */ |
||
| 365 | private function fixEncoding() |
||
| 366 | { |
||
| 367 | $encoding = new HttpEncoding($this->content, HttpEncoding::NONE); |
||
| 368 | |||
| 369 | if ($this->headers->has('Content-Encoding')) { |
||
| 370 | $contentEncoding = $this->headers->first('Content-Encoding'); |
||
| 371 | $encoding->setEncoding($contentEncoding->getFieldValue()); |
||
| 372 | } |
||
| 373 | |||
| 374 | return $encoding; |
||
| 375 | } |
||
| 376 | |||
| 377 | /** |
||
| 378 | * Send http headers. |
||
| 379 | */ |
||
| 380 | private function createHeaders() |
||
| 381 | { |
||
| 382 | foreach ($this->headers as $headers) { |
||
| 383 | foreach ($headers as $header) { |
||
| 384 | header((string) $header, false, $this->statusCode); |
||
| 385 | } |
||
| 386 | } |
||
| 387 | } |
||
| 388 | |||
| 389 | /** |
||
| 390 | * Send http cookie headers. |
||
| 391 | */ |
||
| 392 | private function createCookieHeaders() |
||
| 393 | { |
||
| 394 | foreach ($this->cookies as $cookie) { |
||
| 395 | header((string) $cookie, false, $this->statusCode); |
||
| 396 | } |
||
| 397 | } |
||
| 398 | |||
| 399 | /** |
||
| 400 | * Send chunk content. |
||
| 401 | * |
||
| 402 | * @param string $content |
||
| 403 | */ |
||
| 404 | private function sendChunkContent($content) |
||
| 405 | { |
||
| 406 | flush(); |
||
| 407 | ob_flush(); |
||
| 408 | |||
| 409 | foreach (explode("\r\n", $content) as $chunk) { |
||
| 410 | echo sprintf("%x\r\n", strlen($chunk)); |
||
| 411 | echo $chunk . "\r\n"; |
||
| 412 | flush(); |
||
| 413 | ob_flush(); |
||
| 414 | usleep($this->chunkDelayResponse); |
||
| 415 | } |
||
| 416 | } |
||
| 417 | } |
||
| 418 |
In PHP, under loose comparison (like
==, or!=, orswitchconditions), values of different types might be equal.For
stringvalues, the empty string''is a special case, in particular the following results might be unexpected: