diff --git a/src/infrastructure/celerity/CelerityResourceController.php b/src/infrastructure/celerity/CelerityResourceController.php index 8c72134875..0f60885c55 100644 --- a/src/infrastructure/celerity/CelerityResourceController.php +++ b/src/infrastructure/celerity/CelerityResourceController.php @@ -1,99 +1,94 @@ getRootDirectory().$to_resource; - } - protected function serveResource($path, $package_hash = null) { // 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 = $this->getSupportedResourceTypes(); if (empty($type_map[$type])) { throw new Exception("Only static resources may be served."); } if (AphrontRequest::getHTTPHeader('If-Modified-Since') && !PhabricatorEnv::getEnvConfig('phabricator.developer-mode')) { // 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()); } + $map = CelerityResourceMap::getInstance(); + if ($package_hash) { - $map = CelerityResourceMap::getInstance(); - $paths = $map->resolvePackage($package_hash); - if (!$paths) { + $resource_names = $map->getResourceNamesForPackageHash($package_hash); + if (!$resource_names) { return new Aphront404Response(); } try { $data = array(); - foreach ($paths as $package_path) { - $disk_path = $this->getDiskPath($package_path); - $data[] = Filesystem::readFile($disk_path); + foreach ($resource_names as $resource_name) { + $data[] = $map->getResourceDataForName($resource_name); } $data = implode("\n\n", $data); } catch (Exception $ex) { return new Aphront404Response(); } } else { try { - $disk_path = $this->getDiskPath($path); - $data = Filesystem::readFile($disk_path); + $data = $map->getResourceDataForName($path); } catch (Exception $ex) { return new Aphront404Response(); } } $xformer = $this->buildResourceTransformer(); if ($xformer) { $data = $xformer->transformResource($path, $data); } $response = new AphrontFileResponse(); $response->setContent($data); $response->setMimeType($type_map[$type]); return $this->makeResponseCacheable($response); } protected function getSupportedResourceTypes() { return array( 'css' => 'text/css; charset=utf-8', 'js' => 'text/javascript; charset=utf-8', 'png' => 'image/png', 'gif' => 'image/gif', 'jpg' => 'image/jpg', 'swf' => 'application/x-shockwave-flash', ); } private function makeResponseCacheable(AphrontResponse $response) { $response->setCacheDurationInSeconds(60 * 60 * 24 * 30); $response->setLastModified(time()); return $response; } } diff --git a/src/infrastructure/celerity/CelerityResourceMap.php b/src/infrastructure/celerity/CelerityResourceMap.php index ddcf5e038b..3e52e58de6 100644 --- a/src/infrastructure/celerity/CelerityResourceMap.php +++ b/src/infrastructure/celerity/CelerityResourceMap.php @@ -1,204 +1,210 @@ resourceMap = $resource_map; return $this; } public 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->resourceMap[$symbol])) { throw new Exception( "Attempting to resolve unknown Celerity resource, '{$symbol}'."); } $info = $this->resourceMap[$symbol]; foreach ($info['requires'] as $requires) { if (!empty($map[$requires])) { continue; } $this->resolveResource($map, $requires); } $map[$symbol] = $info; } public function setPackageMap($package_map) { $this->packageMap = $package_map; return $this; } public function packageResources(array $resolved_map) { $packaged = array(); $handled = array(); foreach ($resolved_map as $symbol => $info) { if (isset($handled[$symbol])) { continue; } if (empty($this->packageMap['reverse'][$symbol])) { $packaged[$symbol] = $info; } else { $package = $this->packageMap['reverse'][$symbol]; $package_info = $this->packageMap['packages'][$package]; $packaged[$package_info['name']] = $package_info; foreach ($package_info['symbols'] as $packaged_symbol) { $handled[$packaged_symbol] = true; } } } return $packaged; } - public function resolvePackage($package_hash) { + public function getResourceDataForName($resource_name) { + $root = phutil_get_library_root('phabricator'); + $root = dirname($root).'/webroot/'; + return Filesystem::readFile($root.$resource_name); + } + + public function getResourceNamesForPackageHash($package_hash) { $package = idx($this->packageMap['packages'], $package_hash); if (!$package) { return null; } $paths = array(); foreach ($package['symbols'] as $symbol) { $paths[] = $this->resourceMap[$symbol]['disk']; } return $paths; } private function lookupSymbolInformation($symbol) { return idx($this->resourceMap, $symbol); } private function lookupFileInformation($path) { if (empty($this->reverseMap)) { $this->reverseMap = array(); foreach ($this->resourceMap as $symbol => $data) { $data['provides'] = $symbol; $this->reverseMap[$data['disk']] = $data; } } return idx($this->reverseMap, $path); } /** * 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 getModifiedTimeForSymbol($symbol) { $info = $this->lookupSymbolInformation($symbol); if ($info) { $root = dirname(phutil_get_library_root('phabricator')).'/webroot'; return (int)filemtime($root.$info['disk']); } return 0; } /** * Return the fully-qualified, 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 Fully-qualified resource URI, or null if the symbol * is unknown. */ public function getFullyQualifiedURIForSymbol($symbol) { $info = $this->lookupSymbolInformation($symbol); if ($info) { return idx($info, 'uri'); } return null; } /** * Return the fully-qualified, 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 Fully-qualified resource URI, or null if the name * is unknown. */ public function getFullyQualifiedURIForName($name) { $info = $this->lookupFileInformation($name); if ($info) { return idx($info, 'uri'); } return null; } /** * Return the resource symbols required by a named resource. * * @param string Resource name to lookup. * @return list|null List of required symbols, or null if the name * is unknown. */ public function getRequiredSymbolsForName($name) { $info = $this->lookupFileInformation($name); if ($info) { return idx($info, 'requires', array()); } return null; } /** * 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) { $info = $this->lookupSymbolInformation($symbol); if ($info) { return idx($info, 'disk'); } return null; } }