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.
|
|
|
|
*
|
|
|
|
* @task write Writing Response Components
|
|
|
|
* @task emit Emitting the Response
|
|
|
|
*/
|
2015-06-15 18:02:26 +10:00
|
|
|
abstract class AphrontHTTPSink extends Phobject {
|
2012-02-06 09:59:34 -08:00
|
|
|
|
2019-02-11 13:00:53 -08:00
|
|
|
private $showStackTraces = false;
|
|
|
|
|
|
|
|
final public function setShowStackTraces($show_stack_traces) {
|
|
|
|
$this->showStackTraces = $show_stack_traces;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
final public function getShowStackTraces() {
|
|
|
|
return $this->showStackTraces;
|
|
|
|
}
|
|
|
|
|
2012-02-06 09:59:34 -08:00
|
|
|
|
|
|
|
/* -( Writing Response Components )---------------------------------------- */
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Write an HTTP status code to the output.
|
|
|
|
*
|
|
|
|
* @param int Numeric HTTP status code.
|
|
|
|
* @return void
|
|
|
|
*/
|
Accept and route VCS HTTP requests
Summary:
Mostly ripped from D7391, with some changes:
- Serve repositories at `/diffusion/X/`, with no special `/git/` or `/serve/` URI component.
- This requires a little bit of magic, but I got the magic working for Git, Mercurial and SVN, and it seems reasonable.
- I think having one URI for everything will make it easier for users to understand.
- One downside is that git will clone into `X` by default, but I think that's not a big deal, and we can work around that in the future easily enough.
- Accept HTTP requests for Git, SVN and Mercurial repositories.
- Auth logic is a little different in order to be more consistent with how other things work.
- Instead of AphrontBasicAuthResponse, added "VCSResponse". Mercurial can print strings we send it on the CLI if we're careful, so support that. I did a fair amount of digging and didn't have any luck with git or svn.
- Commands we don't know about are assumed to require "Push" capability by default.
No actual VCS data going over the wire yet.
Test Plan:
Ran a bunch of stuff like this:
$ hg clone http://local.aphront.com:8080/diffusion/P/
abort: HTTP Error 403: This repository is not available over HTTP.
...and got pretty reasonable-seeming errors in all cases. All this can do is produce errors for now.
Reviewers: hach-que, btrahan
Reviewed By: hach-que
CC: aran
Maniphest Tasks: T2230
Differential Revision: https://secure.phabricator.com/D7417
2013-10-26 07:56:17 -07:00
|
|
|
final public function writeHTTPStatus($code, $message = '') {
|
2012-02-06 09:59:34 -08:00
|
|
|
if (!preg_match('/^\d{3}$/', $code)) {
|
2015-05-22 17:27:56 +10:00
|
|
|
throw new Exception(pht("Malformed HTTP status code '%s'!", $code));
|
2012-02-06 09:59:34 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
$code = (int)$code;
|
Accept and route VCS HTTP requests
Summary:
Mostly ripped from D7391, with some changes:
- Serve repositories at `/diffusion/X/`, with no special `/git/` or `/serve/` URI component.
- This requires a little bit of magic, but I got the magic working for Git, Mercurial and SVN, and it seems reasonable.
- I think having one URI for everything will make it easier for users to understand.
- One downside is that git will clone into `X` by default, but I think that's not a big deal, and we can work around that in the future easily enough.
- Accept HTTP requests for Git, SVN and Mercurial repositories.
- Auth logic is a little different in order to be more consistent with how other things work.
- Instead of AphrontBasicAuthResponse, added "VCSResponse". Mercurial can print strings we send it on the CLI if we're careful, so support that. I did a fair amount of digging and didn't have any luck with git or svn.
- Commands we don't know about are assumed to require "Push" capability by default.
No actual VCS data going over the wire yet.
Test Plan:
Ran a bunch of stuff like this:
$ hg clone http://local.aphront.com:8080/diffusion/P/
abort: HTTP Error 403: This repository is not available over HTTP.
...and got pretty reasonable-seeming errors in all cases. All this can do is produce errors for now.
Reviewers: hach-que, btrahan
Reviewed By: hach-que
CC: aran
Maniphest Tasks: T2230
Differential Revision: https://secure.phabricator.com/D7417
2013-10-26 07:56:17 -07:00
|
|
|
$this->emitHTTPStatus($code, $message);
|
2012-02-06 09:59:34 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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) {
|
2015-05-22 17:27:56 +10:00
|
|
|
throw new Exception(pht('Malformed header.'));
|
2012-02-06 09:59:34 -08:00
|
|
|
}
|
|
|
|
list($name, $value) = $header;
|
|
|
|
|
|
|
|
if (strpos($name, ':') !== false) {
|
|
|
|
throw new Exception(
|
2015-05-22 17:27:56 +10:00
|
|
|
pht(
|
|
|
|
'Declining to emit response with malformed HTTP header name: %s',
|
|
|
|
$name));
|
2012-02-06 09:59:34 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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(
|
2015-05-22 17:27:56 +10:00
|
|
|
pht(
|
|
|
|
'Declining to emit response with unsafe HTTP header: %s',
|
|
|
|
"<'".$name."', '".$value."'>."));
|
2012-02-06 09:59:34 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
2016-12-13 14:02:04 -08:00
|
|
|
$response->willBeginWrite();
|
|
|
|
|
2015-03-14 08:29:12 -07:00
|
|
|
// Build the content iterator first, in case it throws. Ideally, we'd
|
|
|
|
// prefer to handle exceptions before we emit the response status or any
|
|
|
|
// HTTP headers.
|
|
|
|
$data = $response->getContentIterator();
|
2012-12-25 06:17:45 -08:00
|
|
|
|
2019-02-13 15:25:20 -08:00
|
|
|
// This isn't an exceptionally clean separation of concerns, but we need
|
|
|
|
// to add CSP headers for all response types (including both web pages
|
|
|
|
// and dialogs) and can't determine the correct CSP until after we render
|
|
|
|
// the page (because page elements like Recaptcha may add CSP rules).
|
|
|
|
$static = CelerityAPI::getStaticResourceResponse();
|
|
|
|
foreach ($static->getContentSecurityPolicyURIMap() as $kind => $uris) {
|
|
|
|
foreach ($uris as $uri) {
|
|
|
|
$response->addContentSecurityPolicyURI($kind, $uri);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-02-14 14:51:51 -08:00
|
|
|
$all_headers = array_merge(
|
|
|
|
$response->getHeaders(),
|
|
|
|
$response->getCacheHeaders());
|
|
|
|
|
Accept and route VCS HTTP requests
Summary:
Mostly ripped from D7391, with some changes:
- Serve repositories at `/diffusion/X/`, with no special `/git/` or `/serve/` URI component.
- This requires a little bit of magic, but I got the magic working for Git, Mercurial and SVN, and it seems reasonable.
- I think having one URI for everything will make it easier for users to understand.
- One downside is that git will clone into `X` by default, but I think that's not a big deal, and we can work around that in the future easily enough.
- Accept HTTP requests for Git, SVN and Mercurial repositories.
- Auth logic is a little different in order to be more consistent with how other things work.
- Instead of AphrontBasicAuthResponse, added "VCSResponse". Mercurial can print strings we send it on the CLI if we're careful, so support that. I did a fair amount of digging and didn't have any luck with git or svn.
- Commands we don't know about are assumed to require "Push" capability by default.
No actual VCS data going over the wire yet.
Test Plan:
Ran a bunch of stuff like this:
$ hg clone http://local.aphront.com:8080/diffusion/P/
abort: HTTP Error 403: This repository is not available over HTTP.
...and got pretty reasonable-seeming errors in all cases. All this can do is produce errors for now.
Reviewers: hach-que, btrahan
Reviewed By: hach-que
CC: aran
Maniphest Tasks: T2230
Differential Revision: https://secure.phabricator.com/D7417
2013-10-26 07:56:17 -07:00
|
|
|
$this->writeHTTPStatus(
|
|
|
|
$response->getHTTPResponseCode(),
|
|
|
|
$response->getHTTPResponseMessage());
|
2012-02-14 14:51:51 -08:00
|
|
|
$this->writeHeaders($all_headers);
|
2015-03-14 08:29:12 -07:00
|
|
|
|
2018-07-30 09:40:47 -07:00
|
|
|
// Allow clients an unlimited amount of time to download the response.
|
|
|
|
|
|
|
|
// This allows clients to perform a "slow loris" attack, where they
|
|
|
|
// download a large response very slowly to tie up process slots. However,
|
|
|
|
// concurrent connection limits and "RequestReadTimeout" already prevent
|
|
|
|
// this attack. We could add our own minimum download rate here if we want
|
|
|
|
// to make this easier to configure eventually.
|
|
|
|
|
|
|
|
// For normal page responses, we've fully rendered the page into a string
|
|
|
|
// already so all that's left is writing it to the client.
|
|
|
|
|
|
|
|
// For unusual responses (like large file downloads) we may still be doing
|
|
|
|
// some meaningful work, but in theory that work is intrinsic to streaming
|
|
|
|
// the response.
|
|
|
|
|
|
|
|
set_time_limit(0);
|
|
|
|
|
2015-03-14 08:29:12 -07:00
|
|
|
$abort = false;
|
|
|
|
foreach ($data as $block) {
|
|
|
|
if (!$this->isWritable()) {
|
|
|
|
$abort = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
$this->writeData($block);
|
|
|
|
}
|
|
|
|
|
|
|
|
$response->didCompleteWrite($abort);
|
2012-02-14 14:51:51 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-02-06 09:59:34 -08:00
|
|
|
/* -( Emitting the Response )---------------------------------------------- */
|
|
|
|
|
|
|
|
|
Accept and route VCS HTTP requests
Summary:
Mostly ripped from D7391, with some changes:
- Serve repositories at `/diffusion/X/`, with no special `/git/` or `/serve/` URI component.
- This requires a little bit of magic, but I got the magic working for Git, Mercurial and SVN, and it seems reasonable.
- I think having one URI for everything will make it easier for users to understand.
- One downside is that git will clone into `X` by default, but I think that's not a big deal, and we can work around that in the future easily enough.
- Accept HTTP requests for Git, SVN and Mercurial repositories.
- Auth logic is a little different in order to be more consistent with how other things work.
- Instead of AphrontBasicAuthResponse, added "VCSResponse". Mercurial can print strings we send it on the CLI if we're careful, so support that. I did a fair amount of digging and didn't have any luck with git or svn.
- Commands we don't know about are assumed to require "Push" capability by default.
No actual VCS data going over the wire yet.
Test Plan:
Ran a bunch of stuff like this:
$ hg clone http://local.aphront.com:8080/diffusion/P/
abort: HTTP Error 403: This repository is not available over HTTP.
...and got pretty reasonable-seeming errors in all cases. All this can do is produce errors for now.
Reviewers: hach-que, btrahan
Reviewed By: hach-que
CC: aran
Maniphest Tasks: T2230
Differential Revision: https://secure.phabricator.com/D7417
2013-10-26 07:56:17 -07:00
|
|
|
abstract protected function emitHTTPStatus($code, $message = '');
|
2012-02-06 09:59:34 -08:00
|
|
|
abstract protected function emitHeader($name, $value);
|
|
|
|
abstract protected function emitData($data);
|
2015-03-14 08:29:12 -07:00
|
|
|
abstract protected function isWritable();
|
2014-07-10 08:12:48 +10:00
|
|
|
|
2012-02-06 09:59:34 -08:00
|
|
|
}
|