Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/applications/celerity/CelerityResourceMap.php b/src/applications/celerity/CelerityResourceMap.php
index 03ccfe607d..e53b656aaf 100644
--- a/src/applications/celerity/CelerityResourceMap.php
+++ b/src/applications/celerity/CelerityResourceMap.php
@@ -1,261 +1,266 @@
<?php
/**
* Interface to the static resource map, which is a graph of available
* resources, resource dependencies, and packaging information. You generally do
* not need to invoke it directly; instead, you call higher-level Celerity APIs
* and it uses the resource map to satisfy your requests.
*/
final class CelerityResourceMap extends Phobject {
private static $instances = array();
private $resources;
private $symbolMap;
private $requiresMap;
private $packageMap;
private $nameMap;
private $hashMap;
private $componentMap;
public function __construct(CelerityResources $resources) {
$this->resources = $resources;
$map = $resources->loadMap();
$this->symbolMap = idx($map, 'symbols', array());
$this->requiresMap = idx($map, 'requires', array());
$this->packageMap = idx($map, 'packages', array());
$this->nameMap = idx($map, 'names', array());
// We derive these reverse maps at runtime.
$this->hashMap = array_flip($this->nameMap);
$this->componentMap = array();
foreach ($this->packageMap as $package_name => $symbols) {
foreach ($symbols as $symbol) {
$this->componentMap[$symbol] = $package_name;
}
}
}
public static function getNamedInstance($name) {
if (empty(self::$instances[$name])) {
$resources_list = CelerityPhysicalResources::getAll();
if (empty($resources_list[$name])) {
throw new Exception(
pht(
'No resource source exists with name "%s"!',
$name));
}
$instance = new CelerityResourceMap($resources_list[$name]);
self::$instances[$name] = $instance;
}
return self::$instances[$name];
}
public function getNameMap() {
return $this->nameMap;
}
public function getSymbolMap() {
return $this->symbolMap;
}
public function getRequiresMap() {
return $this->requiresMap;
}
public function getPackageMap() {
return $this->packageMap;
}
public function getPackagedNamesForSymbols(array $symbols) {
$resolved = $this->resolveResources($symbols);
return $this->packageResources($resolved);
}
private function resolveResources(array $symbols) {
$map = array();
foreach ($symbols as $symbol) {
if (!empty($map[$symbol])) {
continue;
}
$this->resolveResource($map, $symbol);
}
return $map;
}
private function resolveResource(array &$map, $symbol) {
if (empty($this->symbolMap[$symbol])) {
throw new Exception(
pht(
'Attempting to resolve unknown resource, "%s".',
$symbol));
}
$hash = $this->symbolMap[$symbol];
$map[$symbol] = $hash;
if (isset($this->requiresMap[$hash])) {
$requires = $this->requiresMap[$hash];
} else {
$requires = array();
}
foreach ($requires as $required_symbol) {
if (!empty($map[$required_symbol])) {
continue;
}
$this->resolveResource($map, $required_symbol);
}
}
private function packageResources(array $resolved_map) {
$packaged = array();
$handled = array();
foreach ($resolved_map as $symbol => $hash) {
if (isset($handled[$symbol])) {
continue;
}
if (empty($this->componentMap[$symbol])) {
$packaged[] = $this->hashMap[$hash];
} else {
$package_name = $this->componentMap[$symbol];
$packaged[] = $package_name;
$package_symbols = $this->packageMap[$package_name];
foreach ($package_symbols as $package_symbol) {
$handled[$package_symbol] = true;
}
}
}
return $packaged;
}
public function getResourceDataForName($resource_name) {
return $this->resources->getResourceData($resource_name);
}
public function getResourceNamesForPackageName($package_name) {
$package_symbols = idx($this->packageMap, $package_name);
if (!$package_symbols) {
return null;
}
$resource_names = array();
foreach ($package_symbols as $symbol) {
$resource_names[] = $this->hashMap[$this->symbolMap[$symbol]];
}
return $resource_names;
}
/**
* Get the epoch timestamp of the last modification time of a symbol.
*
* @param string Resource symbol to lookup.
* @return int Epoch timestamp of last resource modification.
*/
public function getModifiedTimeForName($name) {
if ($this->isPackageResource($name)) {
$names = array();
foreach ($this->packageMap[$name] as $symbol) {
$names[] = $this->getResourceNameForSymbol($symbol);
}
} else {
$names = array($name);
}
$mtime = 0;
foreach ($names as $name) {
$mtime = max($mtime, $this->resources->getResourceModifiedTime($name));
}
return $mtime;
}
/**
* Return the absolute URI for the resource associated with a symbol. This
* method is fairly low-level and ignores packaging.
*
* @param string Resource symbol to lookup.
* @return string|null Resource URI, or null if the symbol is unknown.
*/
public function getURIForSymbol($symbol) {
$hash = idx($this->symbolMap, $symbol);
return $this->getURIForHash($hash);
}
/**
* Return the absolute URI for the resource associated with a resource name.
* This method is fairly low-level and ignores packaging.
*
* @param string Resource name to lookup.
* @return string|null Resource URI, or null if the name is unknown.
*/
public function getURIForName($name) {
$hash = idx($this->nameMap, $name);
return $this->getURIForHash($hash);
}
+ public function getHashForName($name) {
+ return idx($this->nameMap, $name);
+ }
+
+
/**
* Return the absolute URI for a resource, identified by hash.
* This method is fairly low-level and ignores packaging.
*
* @param string Resource hash to lookup.
* @return string|null Resource URI, or null if the hash is unknown.
*/
private function getURIForHash($hash) {
if ($hash === null) {
return null;
}
return $this->resources->getResourceURI($hash, $this->hashMap[$hash]);
}
/**
* Return the resource symbols required by a named resource.
*
* @param string Resource name to lookup.
* @return list<string>|null List of required symbols, or null if the name
* is unknown.
*/
public function getRequiredSymbolsForName($name) {
$hash = idx($this->nameMap, $name);
if ($hash === null) {
return null;
}
return idx($this->requiresMap, $hash, array());
}
/**
* Return the resource name for a given symbol.
*
* @param string Resource symbol to lookup.
* @return string|null Resource name, or null if the symbol is unknown.
*/
public function getResourceNameForSymbol($symbol) {
$hash = idx($this->symbolMap, $symbol);
return idx($this->hashMap, $hash);
}
public function isPackageResource($name) {
return isset($this->packageMap[$name]);
}
public function getResourceTypeForName($name) {
return $this->resources->getResourceType($name);
}
}
diff --git a/src/applications/celerity/controller/CelerityPhabricatorResourceController.php b/src/applications/celerity/controller/CelerityPhabricatorResourceController.php
index c5f878b61e..5751996db8 100644
--- a/src/applications/celerity/controller/CelerityPhabricatorResourceController.php
+++ b/src/applications/celerity/controller/CelerityPhabricatorResourceController.php
@@ -1,53 +1,57 @@
<?php
/**
* Delivers CSS and JS resources to the browser. This controller handles all
* `/res/` requests, and manages caching, package construction, and resource
* preprocessing.
*/
final class CelerityPhabricatorResourceController
extends CelerityResourceController {
private $path;
private $hash;
private $library;
private $postprocessorKey;
public function getCelerityResourceMap() {
return CelerityResourceMap::getNamedInstance($this->library);
}
public function handleRequest(AphrontRequest $request) {
$this->path = $request->getURIData('path');
$this->hash = $request->getURIData('hash');
$this->library = $request->getURIData('library');
$this->postprocessorKey = $request->getURIData('postprocessor');
// Check that the resource library exists before trying to serve resources
// from it.
try {
$this->getCelerityResourceMap();
} catch (Exception $ex) {
return new Aphront400Response();
}
- return $this->serveResource($this->path);
+ return $this->serveResource(
+ array(
+ 'path' => $this->path,
+ 'hash' => $this->hash,
+ ));
}
protected function buildResourceTransformer() {
$minify_on = PhabricatorEnv::getEnvConfig('celerity.minify');
$developer_on = PhabricatorEnv::getEnvConfig('phabricator.developer-mode');
$should_minify = ($minify_on && !$developer_on);
return id(new CelerityResourceTransformer())
->setMinify($should_minify)
->setPostprocessorKey($this->postprocessorKey)
->setCelerityMap($this->getCelerityResourceMap());
}
protected function getCacheKey($path) {
return parent::getCacheKey($path.';'.$this->postprocessorKey);
}
}
diff --git a/src/applications/celerity/controller/CelerityResourceController.php b/src/applications/celerity/controller/CelerityResourceController.php
index 4e6feeac6c..debb37c1ca 100644
--- a/src/applications/celerity/controller/CelerityResourceController.php
+++ b/src/applications/celerity/controller/CelerityResourceController.php
@@ -1,173 +1,184 @@
<?php
abstract class CelerityResourceController extends PhabricatorController {
protected function buildResourceTransformer() {
return null;
}
public function shouldRequireLogin() {
return false;
}
public function shouldRequireEnabledUser() {
return false;
}
public function shouldAllowPartialSessions() {
return true;
}
public function shouldAllowLegallyNonCompliantUsers() {
return true;
}
abstract public function getCelerityResourceMap();
- protected function serveResource($path, $package_hash = null) {
+ protected function serveResource(array $spec) {
+ $path = $spec['path'];
+ $hash = idx($spec, 'hash');
+
// Sanity checking to keep this from exposing anything sensitive, since it
// ultimately boils down to disk reads.
if (preg_match('@(//|\.\.)@', $path)) {
return new Aphront400Response();
}
$type = CelerityResourceTransformer::getResourceType($path);
$type_map = self::getSupportedResourceTypes();
if (empty($type_map[$type])) {
throw new Exception(pht('Only static resources may be served.'));
}
$dev_mode = PhabricatorEnv::getEnvConfig('phabricator.developer-mode');
- if (AphrontRequest::getHTTPHeader('If-Modified-Since') && !$dev_mode) {
+ $map = $this->getCelerityResourceMap();
+ $expect_hash = $map->getHashForName($path);
+
+ // Test if the URI hash is correct for our current resource map. If it
+ // is not, refuse to cache this resource. This avoids poisoning caches
+ // and CDNs if we're getting a request for a new resource to an old node
+ // shortly after a push.
+ $is_cacheable = ($hash === $expect_hash) &&
+ $this->isCacheableResourceType($type);
+ if (AphrontRequest::getHTTPHeader('If-Modified-Since') && $is_cacheable) {
// Return a "304 Not Modified". We don't care about the value of this
// field since we never change what resource is served by a given URI.
return $this->makeResponseCacheable(new Aphront304Response());
}
- $is_cacheable = (!$dev_mode) &&
- $this->isCacheableResourceType($type);
-
$cache = null;
$data = null;
- if ($is_cacheable) {
+ if ($is_cacheable && !$dev_mode) {
$cache = PhabricatorCaches::getImmutableCache();
$request_path = $this->getRequest()->getPath();
$cache_key = $this->getCacheKey($request_path);
$data = $cache->getKey($cache_key);
}
if ($data === null) {
- $map = $this->getCelerityResourceMap();
-
if ($map->isPackageResource($path)) {
$resource_names = $map->getResourceNamesForPackageName($path);
if (!$resource_names) {
return new Aphront404Response();
}
try {
$data = array();
foreach ($resource_names as $resource_name) {
$data[] = $map->getResourceDataForName($resource_name);
}
$data = implode("\n\n", $data);
} catch (Exception $ex) {
return new Aphront404Response();
}
} else {
try {
$data = $map->getResourceDataForName($path);
} catch (Exception $ex) {
return new Aphront404Response();
}
}
$xformer = $this->buildResourceTransformer();
if ($xformer) {
$data = $xformer->transformResource($path, $data);
}
if ($cache) {
$cache->setKey($cache_key, $data);
}
}
$response = new AphrontFileResponse();
$response->setContent($data);
$response->setMimeType($type_map[$type]);
// NOTE: This is a piece of magic required to make WOFF fonts work in
// Firefox and IE. Possibly we should generalize this more.
$cross_origin_types = array(
'woff' => true,
'woff2' => true,
'eot' => true,
);
if (isset($cross_origin_types[$type])) {
// We could be more tailored here, but it's not currently trivial to
// generate a comprehensive list of valid origins (an install may have
// arbitrarily many Phame blogs, for example), and we lose nothing by
// allowing access from anywhere.
$response->addAllowOrigin('*');
}
- return $this->makeResponseCacheable($response);
+ if ($is_cacheable) {
+ $response = $this->makeResponseCacheable($response);
+ }
+
+ return $response;
}
public static function getSupportedResourceTypes() {
return array(
'css' => 'text/css; charset=utf-8',
'js' => 'text/javascript; charset=utf-8',
'png' => 'image/png',
'svg' => 'image/svg+xml',
'gif' => 'image/gif',
'jpg' => 'image/jpeg',
'swf' => 'application/x-shockwave-flash',
'woff' => 'font/woff',
'woff2' => 'font/woff2',
'eot' => 'font/eot',
'ttf' => 'font/ttf',
'mp3' => 'audio/mpeg',
);
}
private function makeResponseCacheable(AphrontResponse $response) {
$response->setCacheDurationInSeconds(60 * 60 * 24 * 30);
$response->setLastModified(time());
$response->setCanCDN(true);
return $response;
}
/**
* Is it appropriate to cache the data for this resource type in the fast
* immutable cache?
*
* Generally, text resources (which are small, and expensive to process)
* are cached, while other types of resources (which are large, and cheap
* to process) are not.
*
* @param string Resource type.
* @return bool True to enable caching.
*/
private function isCacheableResourceType($type) {
$types = array(
'js' => true,
'css' => true,
);
return isset($types[$type]);
}
protected function getCacheKey($path) {
return 'celerity:'.PhabricatorHash::digestToLength($path, 64);
}
}

File Metadata

Mime Type
text/x-diff
Expires
Tue, Mar 17, 12:54 AM (1 d, 15 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
963923
Default Alt Text
(15 KB)

Event Timeline