Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/celerity/controller/CelerityResourceController.php
12256 views
1
<?php
2
3
abstract class CelerityResourceController extends PhabricatorController {
4
5
protected function buildResourceTransformer() {
6
return null;
7
}
8
9
public function shouldRequireLogin() {
10
return false;
11
}
12
13
public function shouldRequireEnabledUser() {
14
return false;
15
}
16
17
public function shouldAllowPartialSessions() {
18
return true;
19
}
20
21
public function shouldAllowLegallyNonCompliantUsers() {
22
return true;
23
}
24
25
abstract public function getCelerityResourceMap();
26
27
protected function serveResource(array $spec) {
28
$path = $spec['path'];
29
$hash = idx($spec, 'hash');
30
31
// Sanity checking to keep this from exposing anything sensitive, since it
32
// ultimately boils down to disk reads.
33
if (preg_match('@(//|\.\.)@', $path)) {
34
return new Aphront400Response();
35
}
36
37
$type = CelerityResourceTransformer::getResourceType($path);
38
$type_map = self::getSupportedResourceTypes();
39
40
if (empty($type_map[$type])) {
41
throw new Exception(pht('Only static resources may be served.'));
42
}
43
44
$dev_mode = PhabricatorEnv::getEnvConfig('phabricator.developer-mode');
45
46
$map = $this->getCelerityResourceMap();
47
$expect_hash = $map->getHashForName($path);
48
49
// Test if the URI hash is correct for our current resource map. If it
50
// is not, refuse to cache this resource. This avoids poisoning caches
51
// and CDNs if we're getting a request for a new resource to an old node
52
// shortly after a push.
53
$is_cacheable = ($hash === $expect_hash);
54
$is_locally_cacheable = $this->isLocallyCacheableResourceType($type);
55
if (AphrontRequest::getHTTPHeader('If-Modified-Since') && $is_cacheable) {
56
// Return a "304 Not Modified". We don't care about the value of this
57
// field since we never change what resource is served by a given URI.
58
return $this->makeResponseCacheable(new Aphront304Response());
59
}
60
61
$cache = null;
62
$data = null;
63
if ($is_cacheable && $is_locally_cacheable && !$dev_mode) {
64
$cache = PhabricatorCaches::getImmutableCache();
65
66
$request_path = $this->getRequest()->getPath();
67
$cache_key = $this->getCacheKey($request_path);
68
69
$data = $cache->getKey($cache_key);
70
}
71
72
if ($data === null) {
73
if ($map->isPackageResource($path)) {
74
$resource_names = $map->getResourceNamesForPackageName($path);
75
if (!$resource_names) {
76
return new Aphront404Response();
77
}
78
79
try {
80
$data = array();
81
foreach ($resource_names as $resource_name) {
82
$data[] = $map->getResourceDataForName($resource_name);
83
}
84
$data = implode("\n\n", $data);
85
} catch (Exception $ex) {
86
return new Aphront404Response();
87
}
88
} else {
89
try {
90
$data = $map->getResourceDataForName($path);
91
} catch (Exception $ex) {
92
return new Aphront404Response();
93
}
94
}
95
96
$xformer = $this->buildResourceTransformer();
97
if ($xformer) {
98
$data = $xformer->transformResource($path, $data);
99
}
100
101
if ($cache) {
102
$cache->setKey($cache_key, $data);
103
}
104
}
105
106
$response = id(new AphrontFileResponse())
107
->setMimeType($type_map[$type]);
108
109
// The "Content-Security-Policy" header has no effect on the actual
110
// resources, only on the main request. Disable it on the resource
111
// responses to limit confusion.
112
$response->setDisableContentSecurityPolicy(true);
113
114
$range = AphrontRequest::getHTTPHeader('Range');
115
116
if ($range !== null && strlen($range)) {
117
$response->setContentLength(strlen($data));
118
119
list($range_begin, $range_end) = $response->parseHTTPRange($range);
120
121
if ($range_begin !== null) {
122
if ($range_end !== null) {
123
$data = substr($data, $range_begin, ($range_end - $range_begin));
124
} else {
125
$data = substr($data, $range_begin);
126
}
127
}
128
129
$response->setContentIterator(array($data));
130
} else {
131
$response
132
->setContent($data)
133
->setCompressResponse(true);
134
}
135
136
137
// NOTE: This is a piece of magic required to make WOFF fonts work in
138
// Firefox and IE. Possibly we should generalize this more.
139
140
$cross_origin_types = array(
141
'woff' => true,
142
'woff2' => true,
143
'eot' => true,
144
);
145
146
if (isset($cross_origin_types[$type])) {
147
// We could be more tailored here, but it's not currently trivial to
148
// generate a comprehensive list of valid origins (an install may have
149
// arbitrarily many Phame blogs, for example), and we lose nothing by
150
// allowing access from anywhere.
151
$response->addAllowOrigin('*');
152
}
153
154
if ($is_cacheable) {
155
$response = $this->makeResponseCacheable($response);
156
}
157
158
return $response;
159
}
160
161
public static function getSupportedResourceTypes() {
162
return array(
163
'css' => 'text/css; charset=utf-8',
164
'js' => 'text/javascript; charset=utf-8',
165
'png' => 'image/png',
166
'svg' => 'image/svg+xml',
167
'gif' => 'image/gif',
168
'jpg' => 'image/jpeg',
169
'swf' => 'application/x-shockwave-flash',
170
'woff' => 'font/woff',
171
'woff2' => 'font/woff2',
172
'eot' => 'font/eot',
173
'ttf' => 'font/ttf',
174
'mp3' => 'audio/mpeg',
175
'ico' => 'image/x-icon',
176
);
177
}
178
179
private function makeResponseCacheable(AphrontResponse $response) {
180
$response->setCacheDurationInSeconds(60 * 60 * 24 * 30);
181
$response->setLastModified(time());
182
$response->setCanCDN(true);
183
184
return $response;
185
}
186
187
188
/**
189
* Is it appropriate to cache the data for this resource type in the fast
190
* immutable cache?
191
*
192
* Generally, text resources (which are small, and expensive to process)
193
* are cached, while other types of resources (which are large, and cheap
194
* to process) are not.
195
*
196
* @param string Resource type.
197
* @return bool True to enable caching.
198
*/
199
private function isLocallyCacheableResourceType($type) {
200
$types = array(
201
'js' => true,
202
'css' => true,
203
);
204
205
return isset($types[$type]);
206
}
207
208
protected function getCacheKey($path) {
209
return 'celerity:'.PhabricatorHash::digestToLength($path, 64);
210
}
211
212
}
213
214