Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/aphront/sink/AphrontHTTPSink.php
12249 views
1
<?php
2
3
/**
4
* Abstract class which wraps some sort of output mechanism for HTTP responses.
5
* Normally this is just @{class:AphrontPHPHTTPSink}, which uses "echo" and
6
* "header()" to emit responses.
7
*
8
* @task write Writing Response Components
9
* @task emit Emitting the Response
10
*/
11
abstract class AphrontHTTPSink extends Phobject {
12
13
private $showStackTraces = false;
14
15
final public function setShowStackTraces($show_stack_traces) {
16
$this->showStackTraces = $show_stack_traces;
17
return $this;
18
}
19
20
final public function getShowStackTraces() {
21
return $this->showStackTraces;
22
}
23
24
25
/* -( Writing Response Components )---------------------------------------- */
26
27
28
/**
29
* Write an HTTP status code to the output.
30
*
31
* @param int Numeric HTTP status code.
32
* @return void
33
*/
34
final public function writeHTTPStatus($code, $message = '') {
35
if (!preg_match('/^\d{3}$/', $code)) {
36
throw new Exception(pht("Malformed HTTP status code '%s'!", $code));
37
}
38
39
$code = (int)$code;
40
$this->emitHTTPStatus($code, $message);
41
}
42
43
44
/**
45
* Write HTTP headers to the output.
46
*
47
* @param list<pair> List of <name, value> pairs.
48
* @return void
49
*/
50
final public function writeHeaders(array $headers) {
51
foreach ($headers as $header) {
52
if (!is_array($header) || count($header) !== 2) {
53
throw new Exception(pht('Malformed header.'));
54
}
55
list($name, $value) = $header;
56
57
if (strpos($name, ':') !== false) {
58
throw new Exception(
59
pht(
60
'Declining to emit response with malformed HTTP header name: %s',
61
$name));
62
}
63
64
// Attackers may perform an "HTTP response splitting" attack by making
65
// the application emit certain types of headers containing newlines:
66
//
67
// http://en.wikipedia.org/wiki/HTTP_response_splitting
68
//
69
// PHP has built-in protections against HTTP response-splitting, but they
70
// are of dubious trustworthiness:
71
//
72
// http://news.php.net/php.internals/57655
73
74
if (preg_match('/[\r\n\0]/', $name.$value)) {
75
throw new Exception(
76
pht(
77
'Declining to emit response with unsafe HTTP header: %s',
78
"<'".$name."', '".$value."'>."));
79
}
80
}
81
82
foreach ($headers as $header) {
83
list($name, $value) = $header;
84
$this->emitHeader($name, $value);
85
}
86
}
87
88
89
/**
90
* Write HTTP body data to the output.
91
*
92
* @param string Body data.
93
* @return void
94
*/
95
final public function writeData($data) {
96
$this->emitData($data);
97
}
98
99
100
/**
101
* Write an entire @{class:AphrontResponse} to the output.
102
*
103
* @param AphrontResponse The response object to write.
104
* @return void
105
*/
106
final public function writeResponse(AphrontResponse $response) {
107
$response->willBeginWrite();
108
109
// Build the content iterator first, in case it throws. Ideally, we'd
110
// prefer to handle exceptions before we emit the response status or any
111
// HTTP headers.
112
$data = $response->getContentIterator();
113
114
// This isn't an exceptionally clean separation of concerns, but we need
115
// to add CSP headers for all response types (including both web pages
116
// and dialogs) and can't determine the correct CSP until after we render
117
// the page (because page elements like Recaptcha may add CSP rules).
118
$static = CelerityAPI::getStaticResourceResponse();
119
foreach ($static->getContentSecurityPolicyURIMap() as $kind => $uris) {
120
foreach ($uris as $uri) {
121
$response->addContentSecurityPolicyURI($kind, $uri);
122
}
123
}
124
125
$all_headers = array_merge(
126
$response->getHeaders(),
127
$response->getCacheHeaders());
128
129
$this->writeHTTPStatus(
130
$response->getHTTPResponseCode(),
131
$response->getHTTPResponseMessage());
132
$this->writeHeaders($all_headers);
133
134
// Allow clients an unlimited amount of time to download the response.
135
136
// This allows clients to perform a "slow loris" attack, where they
137
// download a large response very slowly to tie up process slots. However,
138
// concurrent connection limits and "RequestReadTimeout" already prevent
139
// this attack. We could add our own minimum download rate here if we want
140
// to make this easier to configure eventually.
141
142
// For normal page responses, we've fully rendered the page into a string
143
// already so all that's left is writing it to the client.
144
145
// For unusual responses (like large file downloads) we may still be doing
146
// some meaningful work, but in theory that work is intrinsic to streaming
147
// the response.
148
149
set_time_limit(0);
150
151
$abort = false;
152
foreach ($data as $block) {
153
if (!$this->isWritable()) {
154
$abort = true;
155
break;
156
}
157
$this->writeData($block);
158
}
159
160
$response->didCompleteWrite($abort);
161
}
162
163
164
/* -( Emitting the Response )---------------------------------------------- */
165
166
167
abstract protected function emitHTTPStatus($code, $message = '');
168
abstract protected function emitHeader($name, $value);
169
abstract protected function emitData($data);
170
abstract protected function isWritable();
171
172
}
173
174