diff --git a/src/aphront/sink/AphrontHTTPSink.php b/src/aphront/sink/AphrontHTTPSink.php index 88773cdff0..875aafa3f0 100644 --- a/src/aphront/sink/AphrontHTTPSink.php +++ b/src/aphront/sink/AphrontHTTPSink.php @@ -1,115 +1,118 @@ emitHTTPStatus($code); } /** * Write HTTP headers to the output. * * @param list List of 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) { list($name, $value) = $header; $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); } /** * Write an entire @{class:AphrontResponse} to the output. * * @param AphrontResponse The response object to write. * @return void */ final public function writeResponse(AphrontResponse $response) { + // Do this first, in case it throws. + $response_string = $response->buildResponseString(); + $all_headers = array_merge( $response->getHeaders(), $response->getCacheHeaders()); $this->writeHTTPStatus($response->getHTTPResponseCode()); $this->writeHeaders($all_headers); - $this->writeData($response->buildResponseString()); + $this->writeData($response_string); } /* -( Emitting the Response )---------------------------------------------- */ abstract protected function emitHTTPStatus($code); abstract protected function emitHeader($name, $value); abstract protected function emitData($data); } diff --git a/webroot/index.php b/webroot/index.php index 1f56a1d0bd..53069daeac 100644 --- a/webroot/index.php +++ b/webroot/index.php @@ -1,193 +1,184 @@ setData( array( 'R' => idx($_SERVER, 'HTTP_REFERER', '-'), 'r' => idx($_SERVER, 'REMOTE_ADDR', '-'), 'M' => idx($_SERVER, 'REQUEST_METHOD', '-'), )); } DarkConsoleXHProfPluginAPI::hookProfiler(); PhutilErrorHandler::setErrorListener( array('DarkConsoleErrorLogPluginAPI', 'handleErrors')); + $sink = new AphrontPHPHTTPSink(); + if (PhabricatorEnv::getEnvConfig('phabricator.setup')) { try { PhabricatorSetup::runSetup(); } catch (Exception $ex) { echo "EXCEPTION!\n"; echo $ex; } return; } phabricator_detect_bad_base_uri(); $host = $_SERVER['HTTP_HOST']; $path = $_REQUEST['__path__']; switch ($host) { default: $config_key = 'aphront.default-application-configuration-class'; $application = PhabricatorEnv::newObjectFromConfig($config_key); break; } - $application->setHost($host); $application->setPath($path); $application->willBuildRequest(); $request = $application->buildRequest(); $write_guard = new AphrontWriteGuard(array($request, 'validateCSRF')); PhabricatorEventEngine::initialize(); $application->setRequest($request); list($controller, $uri_data) = $application->buildController(); if ($access_log) { $access_log->setData( array( 'U' => (string)$request->getRequestURI()->getPath(), 'C' => get_class($controller), )); } // If execution throws an exception and then trying to render that exception // throws another exception, we want to show the original exception, as it is // likely the root cause of the rendering exception. $original_exception = null; try { $response = $controller->willBeginExecution(); if ($access_log) { if ($request->getUser() && $request->getUser()->getPHID()) { $access_log->setData( array( 'u' => $request->getUser()->getUserName(), )); } } if (!$response) { $controller->willProcessRequest($uri_data); $response = $controller->processRequest(); } } catch (AphrontRedirectException $ex) { $response = id(new AphrontRedirectResponse()) ->setURI($ex->getURI()); } catch (Exception $ex) { $original_exception = $ex; $response = $application->handleException($ex); } try { $response = $controller->didProcessRequest($response); $response = $application->willSendResponse($response, $controller); $response->setRequest($request); - $response_string = $response->buildResponseString(); + + $sink->writeResponse($response); + } catch (Exception $ex) { $write_guard->dispose(); if ($access_log) { $access_log->write(); } if ($original_exception) { $ex = new PhutilAggregateException( "Multiple exceptions during processing and rendering.", array( $original_exception, $ex, )); } PhabricatorStartup::didFatal('[Rendering Exception] '.$ex->getMessage()); } $write_guard->dispose(); - // TODO: Share the $sink->writeResponse() pathway here? - - $sink = new AphrontPHPHTTPSink(); - $sink->writeHTTPStatus($response->getHTTPResponseCode()); - - $headers = $response->getCacheHeaders(); - $headers = array_merge($headers, $response->getHeaders()); - - $sink->writeHeaders($headers); - - $sink->writeData($response_string); - if ($access_log) { $request_start = PhabricatorStartup::getStartTime(); $access_log->setData( array( 'c' => $response->getHTTPResponseCode(), 'T' => (int)(1000000 * (microtime(true) - $request_start)), )); $access_log->write(); } if (DarkConsoleXHProfPluginAPI::isProfilerRequested()) { $profile = DarkConsoleXHProfPluginAPI::stopProfiler(); $profile_sample = id(new PhabricatorXHProfSample()) ->setFilePHID($profile); if (empty($_REQUEST['__profile__'])) { $sample_rate = PhabricatorEnv::getEnvConfig('debug.profile-rate'); } else { $sample_rate = 0; } $profile_sample->setSampleRate($sample_rate); if ($access_log) { $profile_sample->setUsTotal($access_log->getData('T')) ->setHostname($access_log->getData('h')) ->setRequestPath($access_log->getData('U')) ->setController($access_log->getData('C')) ->setUserPHID($request->getUser()->getPHID()); } $profile_sample->save(); } } catch (Exception $ex) { PhabricatorStartup::didFatal("[Exception] ".$ex->getMessage()); } function phabricator_detect_bad_base_uri() { $conf = PhabricatorEnv::getEnvConfig('phabricator.base-uri'); $uri = new PhutilURI($conf); switch ($uri->getProtocol()) { case 'http': case 'https': break; default: PhabricatorStartup::didFatal( "'phabricator.base-uri' is set to '{$conf}', which is invalid. ". "The URI must start with 'http://' or 'https://'."); return; } if (strpos($uri->getDomain(), '.') === false) { PhabricatorStartup::didFatal( "'phabricator.base-uri' is set to '{$conf}', which is invalid. The URI ". "must contain a dot ('.'), like 'http://example.com/', not just ". "'http://example/'. Some web browsers will not set cookies on domains ". "with no TLD, and Phabricator requires cookies for login. ". "If you are using localhost, create an entry in the hosts file like ". "'127.0.0.1 example.com', and access the localhost with ". "'http://example.com/'."); } }