Path: blob/master/src/aphront/sink/AphrontHTTPSink.php
12249 views
<?php12/**3* Abstract class which wraps some sort of output mechanism for HTTP responses.4* Normally this is just @{class:AphrontPHPHTTPSink}, which uses "echo" and5* "header()" to emit responses.6*7* @task write Writing Response Components8* @task emit Emitting the Response9*/10abstract class AphrontHTTPSink extends Phobject {1112private $showStackTraces = false;1314final public function setShowStackTraces($show_stack_traces) {15$this->showStackTraces = $show_stack_traces;16return $this;17}1819final public function getShowStackTraces() {20return $this->showStackTraces;21}222324/* -( Writing Response Components )---------------------------------------- */252627/**28* Write an HTTP status code to the output.29*30* @param int Numeric HTTP status code.31* @return void32*/33final public function writeHTTPStatus($code, $message = '') {34if (!preg_match('/^\d{3}$/', $code)) {35throw new Exception(pht("Malformed HTTP status code '%s'!", $code));36}3738$code = (int)$code;39$this->emitHTTPStatus($code, $message);40}414243/**44* Write HTTP headers to the output.45*46* @param list<pair> List of <name, value> pairs.47* @return void48*/49final public function writeHeaders(array $headers) {50foreach ($headers as $header) {51if (!is_array($header) || count($header) !== 2) {52throw new Exception(pht('Malformed header.'));53}54list($name, $value) = $header;5556if (strpos($name, ':') !== false) {57throw new Exception(58pht(59'Declining to emit response with malformed HTTP header name: %s',60$name));61}6263// Attackers may perform an "HTTP response splitting" attack by making64// the application emit certain types of headers containing newlines:65//66// http://en.wikipedia.org/wiki/HTTP_response_splitting67//68// PHP has built-in protections against HTTP response-splitting, but they69// are of dubious trustworthiness:70//71// http://news.php.net/php.internals/576557273if (preg_match('/[\r\n\0]/', $name.$value)) {74throw new Exception(75pht(76'Declining to emit response with unsafe HTTP header: %s',77"<'".$name."', '".$value."'>."));78}79}8081foreach ($headers as $header) {82list($name, $value) = $header;83$this->emitHeader($name, $value);84}85}868788/**89* Write HTTP body data to the output.90*91* @param string Body data.92* @return void93*/94final public function writeData($data) {95$this->emitData($data);96}979899/**100* Write an entire @{class:AphrontResponse} to the output.101*102* @param AphrontResponse The response object to write.103* @return void104*/105final public function writeResponse(AphrontResponse $response) {106$response->willBeginWrite();107108// Build the content iterator first, in case it throws. Ideally, we'd109// prefer to handle exceptions before we emit the response status or any110// HTTP headers.111$data = $response->getContentIterator();112113// This isn't an exceptionally clean separation of concerns, but we need114// to add CSP headers for all response types (including both web pages115// and dialogs) and can't determine the correct CSP until after we render116// the page (because page elements like Recaptcha may add CSP rules).117$static = CelerityAPI::getStaticResourceResponse();118foreach ($static->getContentSecurityPolicyURIMap() as $kind => $uris) {119foreach ($uris as $uri) {120$response->addContentSecurityPolicyURI($kind, $uri);121}122}123124$all_headers = array_merge(125$response->getHeaders(),126$response->getCacheHeaders());127128$this->writeHTTPStatus(129$response->getHTTPResponseCode(),130$response->getHTTPResponseMessage());131$this->writeHeaders($all_headers);132133// Allow clients an unlimited amount of time to download the response.134135// This allows clients to perform a "slow loris" attack, where they136// download a large response very slowly to tie up process slots. However,137// concurrent connection limits and "RequestReadTimeout" already prevent138// this attack. We could add our own minimum download rate here if we want139// to make this easier to configure eventually.140141// For normal page responses, we've fully rendered the page into a string142// already so all that's left is writing it to the client.143144// For unusual responses (like large file downloads) we may still be doing145// some meaningful work, but in theory that work is intrinsic to streaming146// the response.147148set_time_limit(0);149150$abort = false;151foreach ($data as $block) {152if (!$this->isWritable()) {153$abort = true;154break;155}156$this->writeData($block);157}158159$response->didCompleteWrite($abort);160}161162163/* -( Emitting the Response )---------------------------------------------- */164165166abstract protected function emitHTTPStatus($code, $message = '');167abstract protected function emitHeader($name, $value);168abstract protected function emitData($data);169abstract protected function isWritable();170171}172173174