From: Brian Warner 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/pf/content/%22news.html//%22%22.?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): # : 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