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).
This commit is contained in:
Brian Warner 2009-12-27 15:10:43 -05:00
parent 2e0a61a953
commit 499add09e6
5 changed files with 42 additions and 2 deletions

View File

@ -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."""

View File

@ -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)

View File

@ -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()

View File

@ -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)

View File

@ -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