@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Don't cache resources we can't generate properly

Summary:
Fixes T10843. In a multi-server setup, we can do this:

- Two servers, A and B.
- You push an update.
- A gets pushed first.
- After A has been pushed, but before B has been pushed, a user loads a page from A.
- It generates resource URIs like `/stuff/new/package.css`.
- Those requests hit B.
- B doesn't have the new resources yet.
- It responds with old resources.
- Your CDN caches things. You now have a poisoned CDN: old data is saved in a new URL.

To try to avoid this with as little work as possible and generally make it hard to get wrong, check the URL hash against the hash we would generate.

If they don't match, serve our best guess at the resource, but don't cache it. This should make things mostly keep working during the push, but prevent caches from becoming poisoned, and everyone should get a working version of everything after the push finishes.

Test Plan:
- `curl`'d a resource, got a cacheable one.
- Changed the hash a little, `curl`'d again. This time: valid resource, but not cacheable.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10843

Differential Revision: https://secure.phabricator.com/D15775

+30 -10
+5
src/applications/celerity/CelerityResourceMap.php
··· 208 208 } 209 209 210 210 211 + public function getHashForName($name) { 212 + return idx($this->nameMap, $name); 213 + } 214 + 215 + 211 216 /** 212 217 * Return the absolute URI for a resource, identified by hash. 213 218 * This method is fairly low-level and ignores packaging.
+5 -1
src/applications/celerity/controller/CelerityPhabricatorResourceController.php
··· 31 31 return new Aphront400Response(); 32 32 } 33 33 34 - return $this->serveResource($this->path); 34 + return $this->serveResource( 35 + array( 36 + 'path' => $this->path, 37 + 'hash' => $this->hash, 38 + )); 35 39 } 36 40 37 41 protected function buildResourceTransformer() {
+20 -9
src/applications/celerity/controller/CelerityResourceController.php
··· 24 24 25 25 abstract public function getCelerityResourceMap(); 26 26 27 - protected function serveResource($path, $package_hash = null) { 27 + protected function serveResource(array $spec) { 28 + $path = $spec['path']; 29 + $hash = idx($spec, 'hash'); 30 + 28 31 // Sanity checking to keep this from exposing anything sensitive, since it 29 32 // ultimately boils down to disk reads. 30 33 if (preg_match('@(//|\.\.)@', $path)) { ··· 40 43 41 44 $dev_mode = PhabricatorEnv::getEnvConfig('phabricator.developer-mode'); 42 45 43 - if (AphrontRequest::getHTTPHeader('If-Modified-Since') && !$dev_mode) { 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 + $this->isCacheableResourceType($type); 55 + if (AphrontRequest::getHTTPHeader('If-Modified-Since') && $is_cacheable) { 44 56 // Return a "304 Not Modified". We don't care about the value of this 45 57 // field since we never change what resource is served by a given URI. 46 58 return $this->makeResponseCacheable(new Aphront304Response()); 47 59 } 48 60 49 - $is_cacheable = (!$dev_mode) && 50 - $this->isCacheableResourceType($type); 51 - 52 61 $cache = null; 53 62 $data = null; 54 - if ($is_cacheable) { 63 + if ($is_cacheable && !$dev_mode) { 55 64 $cache = PhabricatorCaches::getImmutableCache(); 56 65 57 66 $request_path = $this->getRequest()->getPath(); ··· 61 70 } 62 71 63 72 if ($data === null) { 64 - $map = $this->getCelerityResourceMap(); 65 - 66 73 if ($map->isPackageResource($path)) { 67 74 $resource_names = $map->getResourceNamesForPackageName($path); 68 75 if (!$resource_names) { ··· 117 124 $response->addAllowOrigin('*'); 118 125 } 119 126 120 - return $this->makeResponseCacheable($response); 127 + if ($is_cacheable) { 128 + $response = $this->makeResponseCacheable($response); 129 + } 130 + 131 + return $response; 121 132 } 122 133 123 134 public static function getSupportedResourceTypes() {