From 5d404db898e1e6dc334c3d125ea12a34ea58b13f Mon Sep 17 00:00:00 2001
From: Brian Warner <warner@lothar.com>
Date: Sun, 13 May 2012 00:41:53 -0700
Subject: [PATCH] webapi: don't allow ETags in t=info or t=rename-form, both
 are variable

t=info contains randomly-generated ophandles, and t=rename-form contains the
name of the child being renamed, so neither is eligible for a
short-circuiting ETag. Enhanced test_web to exercise this. Had to improve
FakeCHKFileNode slightly to let it participate. Refs #443.
---
 src/allmydata/test/common.py   |  2 ++
 src/allmydata/test/test_web.py | 31 +++++++++++++++++++++++++++++++
 src/allmydata/web/directory.py |  5 ++++-
 src/allmydata/web/filenode.py  |  4 +++-
 4 files changed, 40 insertions(+), 2 deletions(-)

diff --git a/src/allmydata/test/common.py b/src/allmydata/test/common.py
index 627765a4..a914baa8 100644
--- a/src/allmydata/test/common.py
+++ b/src/allmydata/test/common.py
@@ -113,6 +113,8 @@ class FakeCHKFileNode:
         except KeyError, le:
             raise NotEnoughSharesError(le, 0, 3)
         return len(data)
+    def get_current_size(self):
+        return defer.succeed(self.get_size())
 
     def read(self, consumer, offset=0, size=None):
         # we don't bother to call registerProducer/unregisterProducer,
diff --git a/src/allmydata/test/test_web.py b/src/allmydata/test/test_web.py
index 81dbb201..1b150828 100644
--- a/src/allmydata/test/test_web.py
+++ b/src/allmydata/test/test_web.py
@@ -930,6 +930,10 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixi
         (newkids, caps) = self._create_immutable_children()
         d = self.POST2(self.public_url + "/foo/newdir?t=mkdir-immutable",
                       simplejson.dumps(newkids))
+        def _stash_immdir_uri(uri):
+            self._immdir_uri = uri
+            return uri
+        d.addCallback(_stash_immdir_uri)
         d.addCallback(_check_etags)
 
         # Check that etags work with immutable files
@@ -951,6 +955,33 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixi
                           self.failUnlessEqual(int(code), http.NOT_MODIFIED))
             return d
         d.addCallback(_check_match)
+
+        def _no_etag(uri, t):
+            target = "/uri/%s?t=%s" % (uri, t)
+            d = self.GET(target, return_response=True, followRedirect=True)
+            d.addCallback(lambda (data, code, headers):
+                          self.failIf("etag" in headers, target))
+            return d
+        def _yes_etag(uri, t):
+            target = "/uri/%s?t=%s" % (uri, t)
+            d = self.GET(target, return_response=True, followRedirect=True)
+            d.addCallback(lambda (data, code, headers):
+                          self.failUnless("etag" in headers, target))
+            return d
+
+        d.addCallback(lambda ign: _yes_etag(self._bar_txt_uri, ""))
+        d.addCallback(lambda ign: _yes_etag(self._bar_txt_uri, "json"))
+        d.addCallback(lambda ign: _yes_etag(self._bar_txt_uri, "uri"))
+        d.addCallback(lambda ign: _yes_etag(self._bar_txt_uri, "readonly-uri"))
+        d.addCallback(lambda ign: _no_etag(self._bar_txt_uri, "info"))
+
+        d.addCallback(lambda ign: _yes_etag(self._immdir_uri, ""))
+        d.addCallback(lambda ign: _yes_etag(self._immdir_uri, "json"))
+        d.addCallback(lambda ign: _yes_etag(self._immdir_uri, "uri"))
+        d.addCallback(lambda ign: _yes_etag(self._immdir_uri, "readonly-uri"))
+        d.addCallback(lambda ign: _no_etag(self._immdir_uri, "info"))
+        d.addCallback(lambda ign: _no_etag(self._immdir_uri, "rename-form"))
+
         return d
 
     # TODO: version of this with a Unicode filename
diff --git a/src/allmydata/web/directory.py b/src/allmydata/web/directory.py
index 80f5aa3d..cf1d4d38 100644
--- a/src/allmydata/web/directory.py
+++ b/src/allmydata/web/directory.py
@@ -154,7 +154,10 @@ class DirectoryNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
         # This is where all of the directory-related ?t=* code goes.
         t = get_arg(req, "t", "").strip()
 
-        if not self.node.is_mutable():
+        # t=info contains variable ophandles, t=rename-form contains the name
+        # of the child being renamed. Neither is allowed an ETag.
+        FIXED_OUTPUT_TYPES =  ["", "json", "uri", "readonly-uri"]
+        if not self.node.is_mutable() and t in FIXED_OUTPUT_TYPES:
             si = self.node.get_storage_index()
             if si and req.setETag('DIR:%s-%s' % (base32.b2a(si), t or "")):
                 return ""
diff --git a/src/allmydata/web/filenode.py b/src/allmydata/web/filenode.py
index 9a1983e6..bce8e90c 100644
--- a/src/allmydata/web/filenode.py
+++ b/src/allmydata/web/filenode.py
@@ -158,7 +158,9 @@ class FileNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
         req = IRequest(ctx)
         t = get_arg(req, "t", "").strip()
 
-        if not self.node.is_mutable():
+        # t=info contains variable ophandles, so is not allowed an ETag.
+        FIXED_OUTPUT_TYPES = ["", "json", "uri", "readonly-uri"]
+        if not self.node.is_mutable() and t in FIXED_OUTPUT_TYPES:
             # if the client already has the ETag then we can
             # short-circuit the whole process.
             si = self.node.get_storage_index()
-- 
2.45.2