From: Brian Warner <warner@lothar.com>
Date: Sun, 27 Dec 2009 20:10:43 +0000 (-0500)
Subject: webapi: don't accept zero-length childnames during traversal. Closes #358, #676.
X-Git-Tag: trac-4200~61
X-Git-Url: https://git.rkrishnan.org/components/com_hotproperty/simplejson/encoder.py.html?a=commitdiff_plain;h=499add09e6b1ba62c3dcfc33a0ef719f5a4f1bb9;p=tahoe-lafs%2Ftahoe-lafs.git

webapi: don't accept zero-length childnames during traversal. Closes #358, #676.

This forbids operations that would implicitly create a directory with a
zero-length (empty string) name, like what you'd get if you did "tahoe put
local /oops/blah" (#358) or "POST /uri/CAP//?t=mkdir" (#676). The error
message is fairly friendly too.

Also added code to "tahoe put" to catch this error beforehand and suggest the
correct syntax (i.e. without the leading slash).
---

diff --git a/src/allmydata/interfaces.py b/src/allmydata/interfaces.py
index babacd61..501e3c1d 100644
--- a/src/allmydata/interfaces.py
+++ b/src/allmydata/interfaces.py
@@ -2374,3 +2374,6 @@ class InsufficientVersionError(Exception):
     def __repr__(self):
         return "InsufficientVersionError(need '%s', got %s)" % (self.needed,
                                                                 self.got)
+
+class EmptyPathnameComponentError(Exception):
+    """The webapi disallows empty pathname components."""
diff --git a/src/allmydata/scripts/tahoe_put.py b/src/allmydata/scripts/tahoe_put.py
index 82c8a608..f67ebc5c 100644
--- a/src/allmydata/scripts/tahoe_put.py
+++ b/src/allmydata/scripts/tahoe_put.py
@@ -31,8 +31,10 @@ def put(options):
         #  <none> : unlinked upload
         #  foo : TAHOE_ALIAS/foo
         #  subdir/foo : TAHOE_ALIAS/subdir/foo
+        #  /oops/subdir/foo : DISALLOWED
         #  ALIAS:foo  : aliases[ALIAS]/foo
         #  ALIAS:subdir/foo  : aliases[ALIAS]/subdir/foo
+        #  ALIAS:/oops/subdir/foo : DISALLOWED
         #  DIRCAP:./foo        : DIRCAP/foo
         #  DIRCAP:./subdir/foo : DIRCAP/subdir/foo
         #  MUTABLE-FILE-WRITECAP : filecap
@@ -41,6 +43,11 @@ def put(options):
             url = nodeurl + "uri/%s" % urllib.quote(to_file)
         else:
             rootcap, path = get_alias(aliases, to_file, DEFAULT_ALIAS)
+            if path.startswith("/"):
+                suggestion = to_file.replace("/", "", 1)
+                print >>stderr, "ERROR: The VDRIVE filename must not start with a slash"
+                print >>stderr, "Please try again, perhaps with:", suggestion
+                return 1
             url = nodeurl + "uri/%s/" % urllib.quote(rootcap)
             if path:
                 url += escape_path(path)
diff --git a/src/allmydata/test/test_web.py b/src/allmydata/test/test_web.py
index 2dfa2a9f..fd5e9942 100644
--- a/src/allmydata/test/test_web.py
+++ b/src/allmydata/test/test_web.py
@@ -836,6 +836,14 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
                   "Unable to create directory 'blockingfile': a file was in the way")
         return d
 
+    def test_PUT_NEWFILEURL_emptyname(self):
+        # an empty pathname component (i.e. a double-slash) is disallowed
+        d = self.shouldFail2(error.Error, "test_PUT_NEWFILEURL_emptyname",
+                             "400 Bad Request",
+                             "The webapi does not allow empty pathname components",
+                             self.PUT, self.public_url + "/foo//new.txt", "")
+        return d
+
     def test_DELETE_FILEURL(self):
         d = self.DELETE(self.public_url + "/foo/bar.txt")
         d.addCallback(lambda res:
@@ -1128,6 +1136,22 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
         d.addCallback(self.failUnlessNodeKeysAre, [])
         return d
 
+    def test_POST_NEWDIRURL(self):
+        d = self.POST2(self.public_url + "/foo/newdir?t=mkdir", "")
+        d.addCallback(lambda res:
+                      self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
+        d.addCallback(lambda res: self._foo_node.get(u"newdir"))
+        d.addCallback(self.failUnlessNodeKeysAre, [])
+        return d
+
+    def test_POST_NEWDIRURL_emptyname(self):
+        # an empty pathname component (i.e. a double-slash) is disallowed
+        d = self.shouldFail2(error.Error, "test_POST_NEWDIRURL_emptyname",
+                             "400 Bad Request",
+                             "The webapi does not allow empty pathname components, i.e. a double slash",
+                             self.POST, self.public_url + "//?t=mkdir")
+        return d
+
     def test_POST_NEWDIRURL_initial_children(self):
         (newkids, filecap1, filecap2, filecap3,
          dircap) = self._create_initial_children()
diff --git a/src/allmydata/web/common.py b/src/allmydata/web/common.py
index a9bfa8c2..55c5c64a 100644
--- a/src/allmydata/web/common.py
+++ b/src/allmydata/web/common.py
@@ -8,7 +8,7 @@ from nevow.inevow import IRequest
 from nevow.util import resource_filename
 from allmydata.interfaces import ExistingChildError, NoSuchChildError, \
      FileTooLargeError, NotEnoughSharesError, NoSharesError, \
-     NotDeepImmutableError
+     NotDeepImmutableError, EmptyPathnameComponentError
 from allmydata.mutable.common import UnrecoverableFileError
 from allmydata.util import abbreviate # TODO: consolidate
 
@@ -147,6 +147,9 @@ def should_create_intermediate_directories(req):
 
 def humanize_failure(f):
     # return text, responsecode
+    if f.check(EmptyPathnameComponentError):
+        return ("The webapi does not allow empty pathname components, "
+                "i.e. a double slash", http.BAD_REQUEST)
     if f.check(ExistingChildError):
         return ("There was already a child by that name, and you asked me "
                 "to not replace it.", http.CONFLICT)
diff --git a/src/allmydata/web/directory.py b/src/allmydata/web/directory.py
index a25f8cc4..d38c6457 100644
--- a/src/allmydata/web/directory.py
+++ b/src/allmydata/web/directory.py
@@ -15,7 +15,8 @@ from foolscap.api import fireEventually
 from allmydata.util import base32, time_format
 from allmydata.uri import from_string_dirnode
 from allmydata.interfaces import IDirectoryNode, IFileNode, IFilesystemNode, \
-     IImmutableFileNode, IMutableFileNode, ExistingChildError, NoSuchChildError
+     IImmutableFileNode, IMutableFileNode, ExistingChildError, \
+     NoSuchChildError, EmptyPathnameComponentError
 from allmydata.monitor import Monitor, OperationCancelledError
 from allmydata import dirnode
 from allmydata.web.common import text_plain, WebError, \
@@ -61,6 +62,8 @@ class DirectoryNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
     def childFactory(self, ctx, name):
         req = IRequest(ctx)
         name = name.decode("utf-8")
+        if not name:
+            raise EmptyPathnameComponentError()
         d = self.node.get(name)
         d.addBoth(self.got_child, ctx, name)
         # got_child returns a handler resource: FileNodeHandler or