diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 0d89225cc1..81d4780b5d 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -58,6 +58,7 @@ phutil_register_library_map(array( 'AphrontPageView' => 'view/page/base', 'AphrontPagerView' => 'view/control/pager', 'AphrontPanelView' => 'view/layout/panel', + 'AphrontPlainTextResponse' => 'aphront/response/plaintext', 'AphrontQueryAccessDeniedException' => 'storage/exception/accessdenied', 'AphrontQueryConnectionException' => 'storage/exception/connection', 'AphrontQueryConnectionLostException' => 'storage/exception/connectionlost', @@ -830,6 +831,7 @@ phutil_register_library_map(array( 'AphrontPageView' => 'AphrontView', 'AphrontPagerView' => 'AphrontView', 'AphrontPanelView' => 'AphrontView', + 'AphrontPlainTextResponse' => 'AphrontResponse', 'AphrontQueryAccessDeniedException' => 'AphrontQueryRecoverableException', 'AphrontQueryConnectionException' => 'AphrontQueryException', 'AphrontQueryConnectionLostException' => 'AphrontQueryRecoverableException', diff --git a/src/aphront/request/AphrontRequest.php b/src/aphront/request/AphrontRequest.php index b8bb0536b8..9efc167981 100644 --- a/src/aphront/request/AphrontRequest.php +++ b/src/aphront/request/AphrontRequest.php @@ -26,6 +26,7 @@ class AphrontRequest { const TYPE_AJAX = '__ajax__'; const TYPE_FORM = '__form__'; + const TYPE_CONDUIT = '__conduit__'; private $host; private $path; @@ -170,6 +171,10 @@ class AphrontRequest { return $this->getExists(self::TYPE_AJAX); } + final public function isConduit() { + return $this->getExists(self::TYPE_CONDUIT); + } + public static function getCSRFTokenName() { return '__csrf__'; } diff --git a/src/aphront/response/base/AphrontResponse.php b/src/aphront/response/base/AphrontResponse.php index 0dfcf8689e..2cb1e3afaa 100644 --- a/src/aphront/response/base/AphrontResponse.php +++ b/src/aphront/response/base/AphrontResponse.php @@ -1,7 +1,7 @@ formatEpochTimestampForHTTPHeader($this->lastModified)); } + $headers[] = array( + // IE has a feature where it may override an explicit Content-Type + // declaration by inferring a content type. This can be a security risk + // and we always explicitly transmit the correct Content-Type header, so + // prevent IE from using inferred content types. + array('X-Content-Type-Options', 'nosniff'), + ); + return $headers; } diff --git a/src/aphront/response/file/AphrontFileResponse.php b/src/aphront/response/file/AphrontFileResponse.php index 3f9a16a102..91916e1f52 100644 --- a/src/aphront/response/file/AphrontFileResponse.php +++ b/src/aphront/response/file/AphrontFileResponse.php @@ -1,7 +1,7 @@ getMimeType()), - // Without this, IE can decide that we surely meant "text/html" when - // delivering another content type since, you know, it looks like it's - // probably an HTML document. This closes the security hole that policy - // creates. - array('X-Content-Type-Options', 'nosniff'), ); if (strlen($this->getDownload())) { diff --git a/src/aphront/response/plaintext/AphrontPlainTextResponse.php b/src/aphront/response/plaintext/AphrontPlainTextResponse.php new file mode 100644 index 0000000000..bf1e30e352 --- /dev/null +++ b/src/aphront/response/plaintext/AphrontPlainTextResponse.php @@ -0,0 +1,41 @@ +content = $content; + return $this; + } + + public function buildResponseString() { + return $this->content; + } + + public function getHeaders() { + $headers = array( + array('Content-Type', 'text/plain'), + ); + + return array_merge(parent::getHeaders(), $headers); + } + +} diff --git a/src/aphront/response/plaintext/__init__.php b/src/aphront/response/plaintext/__init__.php new file mode 100644 index 0000000000..2b347b3c64 --- /dev/null +++ b/src/aphront/response/plaintext/__init__.php @@ -0,0 +1,12 @@ +setURI('/'); } + if ($request->isConduit()) { + + // A common source of errors in Conduit client configuration is getting + // the request path wrong. The client will end up here, so make some + // effort to give them a comprehensible error message. + + $request_path = $this->getRequest()->getPath(); + $conduit_path = '/api/'; + $example_path = '/api/conduit.ping'; + + $message = + "ERROR: You are making a Conduit API request to '{$request_path}', ". + "but the correct HTTP request path to use in order to access a ". + "Conduit method is '{$conduit_path}' (for example, ". + "'{$example_path}'). Check your configuration."; + + return id(new AphrontPlainTextResponse())->setContent($message); + } + $next_uri = $this->getRequest()->getPath(); if ($next_uri == '/login/') { $next_uri = '/'; diff --git a/src/applications/auth/controller/login/__init__.php b/src/applications/auth/controller/login/__init__.php index d452c764bd..9eb25e964c 100644 --- a/src/applications/auth/controller/login/__init__.php +++ b/src/applications/auth/controller/login/__init__.php @@ -6,6 +6,7 @@ +phutil_require_module('phabricator', 'aphront/response/plaintext'); phutil_require_module('phabricator', 'aphront/response/redirect'); phutil_require_module('phabricator', 'applications/auth/controller/base'); phutil_require_module('phabricator', 'applications/auth/oauth/provider/base');