2012-02-06 09:59:34 -08:00
|
|
|
<?php
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Abstract class which wraps some sort of output mechanism for HTTP responses.
|
|
|
|
* Normally this is just @{class:AphrontPHPHTTPSink}, which uses "echo" and
|
|
|
|
* "header()" to emit responses.
|
|
|
|
*
|
|
|
|
* Mostly, this class allows us to do install security or metrics hooks in the
|
|
|
|
* output pipeline.
|
|
|
|
*
|
|
|
|
* @task write Writing Response Components
|
|
|
|
* @task emit Emitting the Response
|
|
|
|
*
|
|
|
|
* @group aphront
|
|
|
|
*/
|
|
|
|
abstract class AphrontHTTPSink {
|
|
|
|
|
|
|
|
|
|
|
|
/* -( Writing Response Components )---------------------------------------- */
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Write an HTTP status code to the output.
|
|
|
|
*
|
|
|
|
* @param int Numeric HTTP status code.
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
final public function writeHTTPStatus($code) {
|
|
|
|
if (!preg_match('/^\d{3}$/', $code)) {
|
|
|
|
throw new Exception("Malformed HTTP status code '{$code}'!");
|
|
|
|
}
|
|
|
|
|
|
|
|
$code = (int)$code;
|
|
|
|
$this->emitHTTPStatus($code);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Write HTTP headers to the output.
|
|
|
|
*
|
|
|
|
* @param list<pair> List of <name, value> pairs.
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
final public function writeHeaders(array $headers) {
|
|
|
|
foreach ($headers as $header) {
|
|
|
|
if (!is_array($header) || count($header) !== 2) {
|
|
|
|
throw new Exception('Malformed header.');
|
|
|
|
}
|
|
|
|
list($name, $value) = $header;
|
|
|
|
|
|
|
|
if (strpos($name, ':') !== false) {
|
|
|
|
throw new Exception(
|
|
|
|
"Declining to emit response with malformed HTTP header name: ".
|
|
|
|
$name);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Attackers may perform an "HTTP response splitting" attack by making
|
|
|
|
// the application emit certain types of headers containing newlines:
|
|
|
|
//
|
|
|
|
// http://en.wikipedia.org/wiki/HTTP_response_splitting
|
|
|
|
//
|
|
|
|
// PHP has built-in protections against HTTP response-splitting, but they
|
|
|
|
// are of dubious trustworthiness:
|
|
|
|
//
|
|
|
|
// http://news.php.net/php.internals/57655
|
|
|
|
|
|
|
|
if (preg_match('/[\r\n\0]/', $name.$value)) {
|
|
|
|
throw new Exception(
|
|
|
|
"Declining to emit response with unsafe HTTP header: ".
|
|
|
|
"<'".$name."', '".$value."'>.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($headers as $header) {
|
2012-02-06 13:14:17 -08:00
|
|
|
list($name, $value) = $header;
|
2012-02-06 09:59:34 -08:00
|
|
|
$this->emitHeader($name, $value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Write HTTP body data to the output.
|
|
|
|
*
|
|
|
|
* @param string Body data.
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
final public function writeData($data) {
|
|
|
|
$this->emitData($data);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-02-14 14:51:51 -08:00
|
|
|
/**
|
|
|
|
* Write an entire @{class:AphrontResponse} to the output.
|
|
|
|
*
|
|
|
|
* @param AphrontResponse The response object to write.
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
final public function writeResponse(AphrontResponse $response) {
|
2012-12-25 06:17:45 -08:00
|
|
|
// Do this first, in case it throws.
|
|
|
|
$response_string = $response->buildResponseString();
|
|
|
|
|
2012-02-14 14:51:51 -08:00
|
|
|
$all_headers = array_merge(
|
|
|
|
$response->getHeaders(),
|
|
|
|
$response->getCacheHeaders());
|
|
|
|
|
|
|
|
$this->writeHTTPStatus($response->getHTTPResponseCode());
|
|
|
|
$this->writeHeaders($all_headers);
|
2012-12-25 06:17:45 -08:00
|
|
|
$this->writeData($response_string);
|
2012-02-14 14:51:51 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-02-06 09:59:34 -08:00
|
|
|
/* -( Emitting the Response )---------------------------------------------- */
|
|
|
|
|
|
|
|
|
|
|
|
abstract protected function emitHTTPStatus($code);
|
|
|
|
abstract protected function emitHeader($name, $value);
|
|
|
|
abstract protected function emitData($data);
|
|
|
|
}
|