don't allow trailing slashes

This makes /uri/xxx URIs invalid if they have a trailing slash.
It seems that the former Nevow implementation would allow this, and
some tests (and, notable, "tahoe backup") did rely on using URIs
of this style.
This commit is contained in:
meejah 2020-03-02 17:08:30 -07:00
parent 882c63dab9
commit 018e161f19
4 changed files with 43 additions and 43 deletions

View File

@ -51,8 +51,8 @@ def mkdir(contents, options):
return dircap
def put_child(dirurl, childname, childcap):
assert dirurl[-1] == "/"
url = dirurl + urllib.quote(unicode_to_url(childname)) + "?t=uri"
assert dirurl[-1] != "/"
url = dirurl + "/" + urllib.quote(unicode_to_url(childname)) + "?t=uri"
resp = do_http("PUT", url, childcap)
if resp.status not in (200, 201):
raise HTTPError("Error during put_child", resp)
@ -105,6 +105,9 @@ class BackerUpper(object):
archives_url = to_url + "Archives/"
archives_url = archives_url.rstrip("/")
to_url = to_url.rstrip("/")
# first step: make sure the target directory exists, as well as the
# Archives/ subdirectory.
resp = do_http("GET", archives_url + "?t=json")

View File

@ -328,8 +328,8 @@ class Grid(GridTestMixin, WebErrorMixin, ShouldFailMixin, testutil.ReallyEqualMi
def _stash_root_and_create_file(n):
self.rootnode = n
self.rooturl = "uri/" + urllib.quote(n.get_uri()) + "/"
self.rourl = "uri/" + urllib.quote(n.get_readonly_uri()) + "/"
self.rooturl = "uri/" + urllib.quote(n.get_uri())
self.rourl = "uri/" + urllib.quote(n.get_readonly_uri())
if not immutable:
return self.rootnode.set_node(name, future_node)
d.addCallback(_stash_root_and_create_file)
@ -389,7 +389,7 @@ class Grid(GridTestMixin, WebErrorMixin, ShouldFailMixin, testutil.ReallyEqualMi
d.addCallback(lambda ign: self.GET(expected_info_url))
d.addCallback(_check_info, expect_rw_uri=False, expect_ro_uri=False)
d.addCallback(lambda ign: self.GET("%s%s?t=info" % (self.rooturl, str(name))))
d.addCallback(lambda ign: self.GET("%s/%s?t=info" % (self.rooturl, str(name))))
d.addCallback(_check_info, expect_rw_uri=False, expect_ro_uri=True)
def _check_json(res, expect_rw_uri):
@ -413,7 +413,7 @@ class Grid(GridTestMixin, WebErrorMixin, ShouldFailMixin, testutil.ReallyEqualMi
# TODO: check metadata contents
self.failUnlessIn("metadata", data[1])
d.addCallback(lambda ign: self.GET("%s%s?t=json" % (self.rooturl, str(name))))
d.addCallback(lambda ign: self.GET("%s/%s?t=json" % (self.rooturl, str(name))))
d.addCallback(_check_json, expect_rw_uri=not immutable)
# and make sure that a read-only version of the directory can be
@ -428,7 +428,7 @@ class Grid(GridTestMixin, WebErrorMixin, ShouldFailMixin, testutil.ReallyEqualMi
d.addCallback(lambda ign: self.GET(self.rourl+"?t=json"))
d.addCallback(_check_directory_json, expect_rw_uri=False)
d.addCallback(lambda ign: self.GET("%s%s?t=json" % (self.rourl, str(name))))
d.addCallback(lambda ign: self.GET("%s/%s?t=json" % (self.rourl, str(name))))
d.addCallback(_check_json, expect_rw_uri=False)
# TODO: check that getting t=info from the Info link in the ro directory
@ -495,7 +495,7 @@ class Grid(GridTestMixin, WebErrorMixin, ShouldFailMixin, testutil.ReallyEqualMi
self.failUnlessIn("CHK", cap.to_string())
self.cap = cap
self.rootnode = dn
self.rooturl = "uri/" + urllib.quote(dn.get_uri()) + "/"
self.rooturl = "uri/" + urllib.quote(dn.get_uri())
return download_to_data(dn._node)
d.addCallback(_created)
@ -585,7 +585,7 @@ class Grid(GridTestMixin, WebErrorMixin, ShouldFailMixin, testutil.ReallyEqualMi
d = c0.create_dirnode()
def _stash_root_and_create_file(n):
self.rootnode = n
self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri())
return n.add_file(u"good", upload.Data(DATA, convergence=""))
d.addCallback(_stash_root_and_create_file)
def _stash_uri(fn, which):
@ -759,7 +759,7 @@ class Grid(GridTestMixin, WebErrorMixin, ShouldFailMixin, testutil.ReallyEqualMi
d = c0.create_dirnode()
def _stash_root_and_create_file(n):
self.rootnode = n
self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri())
return n.add_file(u"good", upload.Data(DATA, convergence=""))
d.addCallback(_stash_root_and_create_file)
def _stash_uri(fn, which):
@ -972,7 +972,7 @@ class Grid(GridTestMixin, WebErrorMixin, ShouldFailMixin, testutil.ReallyEqualMi
def _stash_root_and_create_file(n):
self.rootnode = n
self.uris["root"] = n.get_uri()
self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri())
return n.add_file(u"one", upload.Data(DATA, convergence=""))
d.addCallback(_stash_root_and_create_file)
def _stash_uri(fn, which):
@ -1039,8 +1039,8 @@ class Grid(GridTestMixin, WebErrorMixin, ShouldFailMixin, testutil.ReallyEqualMi
DATA = "data" * 100
d = c0.create_dirnode()
def _stash_root(n):
self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
self.fileurls["imaginary"] = self.fileurls["root"] + "imaginary"
self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri())
self.fileurls["imaginary"] = self.fileurls["root"] + "/imaginary"
return n
d.addCallback(_stash_root)
d.addCallback(lambda ign: c0.upload(upload.Data(DATA, convergence="")))
@ -1056,14 +1056,14 @@ class Grid(GridTestMixin, WebErrorMixin, ShouldFailMixin, testutil.ReallyEqualMi
d.addCallback(lambda ign: c0.create_dirnode())
def _mangle_dirnode_1share(n):
u = n.get_uri()
url = self.fileurls["dir-1share"] = "uri/" + urllib.quote(u) + "/"
url = self.fileurls["dir-1share"] = "uri/" + urllib.quote(u)
self.fileurls["dir-1share-json"] = url + "?t=json"
self.delete_shares_numbered(u, range(1,10))
d.addCallback(_mangle_dirnode_1share)
d.addCallback(lambda ign: c0.create_dirnode())
def _mangle_dirnode_0share(n):
u = n.get_uri()
url = self.fileurls["dir-0share"] = "uri/" + urllib.quote(u) + "/"
url = self.fileurls["dir-0share"] = "uri/" + urllib.quote(u)
self.fileurls["dir-0share-json"] = url + "?t=json"
self.delete_shares_numbered(u, range(0,10))
d.addCallback(_mangle_dirnode_0share)
@ -1342,8 +1342,8 @@ class Grid(GridTestMixin, WebErrorMixin, ShouldFailMixin, testutil.ReallyEqualMi
self.dir_si_b32 = base32.b2a(dn.get_storage_index())
self.dir_url_base = "uri/"+dn.get_write_uri()
self.dir_url_json1 = "uri/"+dn.get_write_uri()+"?t=json"
self.dir_url_json2 = "uri/"+dn.get_write_uri()+"/?t=json"
self.dir_url_json_ro = "uri/"+dn.get_readonly_uri()+"/?t=json"
self.dir_url_json2 = "uri/"+dn.get_write_uri()+"?t=json"
self.dir_url_json_ro = "uri/"+dn.get_readonly_uri()+"?t=json"
self.child_url = "uri/"+dn.get_readonly_uri()+"/child"
d.addCallback(_get_dircap)
d.addCallback(lambda ign: self.GET(self.dir_url_base, followRedirect=True))

View File

@ -658,8 +658,10 @@ class WebMixin(testutil.TimezoneMixin):
(response_substring, res.value.response,
which))
else:
self.fail("%s was supposed to raise %s, not get '%s'" %
(which, expected_failure, res))
self.fail(
RuntimeError("%s was supposed to raise %s, not get '%s'" %
(which, expected_failure, res))
)
d.addBoth(done)
return d
@ -1940,7 +1942,7 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixi
@inlineCallbacks
def test_GET_DIRURL_empty(self):
# look at an empty directory
data = yield self.GET(self.public_url + "/foo/empty/")
data = yield self.GET(self.public_url + "/foo/empty")
soup = BeautifulSoup(data, 'html5lib')
self.failUnlessIn("directory is empty", data)
mkdir_inputs = soup.find_all(u"input", {u"type": u"hidden", u"name": u"t", u"value": u"mkdir"})
@ -1954,7 +1956,7 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixi
def test_GET_DIRURL_literal(self):
# look at a literal directory
tiny_litdir_uri = "URI:DIR2-LIT:gqytunj2onug64tufqzdcosvkjetutcjkq5gw4tvm5vwszdgnz5hgyzufqydulbshj5x2lbm" # contains one child which is itself also LIT
data = yield self.GET("/uri/" + tiny_litdir_uri + "/", followRedirect=True)
data = yield self.GET("/uri/" + tiny_litdir_uri, followRedirect=True)
soup = BeautifulSoup(data, 'html5lib')
self.failUnlessIn('(immutable)', data)
file_links = list(
@ -2017,7 +2019,7 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixi
def test_POST_DIRURL_manifest(self):
d = defer.succeed(None)
def getman(ignored, output):
url = self.webish_url + self.public_url + "/foo/?t=start-manifest&ophandle=125"
url = self.webish_url + self.public_url + "/foo?t=start-manifest&ophandle=125"
d = do_http("post", url, allow_redirects=True,
browser_like_redirects=True)
d.addCallback(self.wait_for_operation, "125")
@ -2069,7 +2071,7 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixi
return d
def test_POST_DIRURL_deepsize(self):
url = self.webish_url + self.public_url + "/foo/?t=start-deep-size&ophandle=126"
url = self.webish_url + self.public_url + "/foo?t=start-deep-size&ophandle=126"
d = do_http("post", url, allow_redirects=True,
browser_like_redirects=True)
d.addCallback(self.wait_for_operation, "126")
@ -2098,7 +2100,7 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixi
return d
def test_POST_DIRURL_deepstats(self):
url = self.webish_url + self.public_url + "/foo/?t=start-deep-stats&ophandle=127"
url = self.webish_url + self.public_url + "/foo?t=start-deep-stats&ophandle=127"
d = do_http("post", url,
allow_redirects=True, browser_like_redirects=True)
d.addCallback(self.wait_for_operation, "127")
@ -2127,7 +2129,7 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixi
return d
def test_POST_DIRURL_stream_manifest(self):
d = self.POST(self.public_url + "/foo/?t=stream-manifest")
d = self.POST(self.public_url + "/foo?t=stream-manifest")
def _check(res):
self.failUnless(res.endswith("\n"))
units = [json.loads(t) for t in res[:-1].split("\n")]
@ -2789,7 +2791,7 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixi
# slightly differently
d.addCallback(lambda res:
self.GET(self.public_url + "/foo/",
self.GET(self.public_url + "/foo",
followRedirect=True))
def _check_page(res):
# TODO: assert more about the contents
@ -2807,7 +2809,7 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixi
# look at the JSON form of the enclosing directory
d.addCallback(lambda res:
self.GET(self.public_url + "/foo/?t=json",
self.GET(self.public_url + "/foo?t=json",
followRedirect=True))
def _check_page_json(res):
parsed = json.loads(res)
@ -3021,7 +3023,7 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixi
@inlineCallbacks
def test_POST_DIRURL_check(self):
foo_url = self.public_url + "/foo/"
foo_url = self.public_url + "/foo"
res = yield self.POST(foo_url, t="check")
self.failUnlessIn("Healthy :", res)
@ -3043,7 +3045,7 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixi
@inlineCallbacks
def test_POST_DIRURL_check_and_repair(self):
foo_url = self.public_url + "/foo/"
foo_url = self.public_url + "/foo"
res = yield self.POST(foo_url, t="check", repair="true")
self.failUnlessIn("Healthy :", res)
@ -4520,7 +4522,7 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixi
@inlineCallbacks
def test_ophandle_cancel(self):
url = self.webish_url + self.public_url + "/foo/?t=start-manifest&ophandle=128"
url = self.webish_url + self.public_url + "/foo?t=start-manifest&ophandle=128"
yield do_http("post", url,
allow_redirects=True, browser_like_redirects=True)
res = yield self.GET("/operations/128?t=status&output=JSON")
@ -4539,7 +4541,7 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixi
@inlineCallbacks
def test_ophandle_retainfor(self):
url = self.webish_url + self.public_url + "/foo/?t=start-manifest&ophandle=129&retain-for=60"
url = self.webish_url + self.public_url + "/foo?t=start-manifest&ophandle=129&retain-for=60"
yield do_http("post", url,
allow_redirects=True, browser_like_redirects=True)
res = yield self.GET("/operations/129?t=status&output=JSON&retain-for=0")
@ -4553,7 +4555,7 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixi
@inlineCallbacks
def test_ophandle_release_after_complete(self):
url = self.webish_url + self.public_url + "/foo/?t=start-manifest&ophandle=130"
url = self.webish_url + self.public_url + "/foo?t=start-manifest&ophandle=130"
yield do_http("post", url,
allow_redirects=True, browser_like_redirects=True)
yield self.wait_for_operation(None, "130")
@ -4567,7 +4569,7 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixi
# uncollected ophandles should expire after 4 days
def _make_uncollected_ophandle(ophandle):
url = (self.webish_url + self.public_url +
"/foo/?t=start-manifest&ophandle=%d" % ophandle)
"/foo?t=start-manifest&ophandle=%d" % ophandle)
# When we start the operation, the webapi server will want to
# redirect us to the page for the ophandle, so we get
# confirmation that the operation has started. If the manifest
@ -4605,7 +4607,7 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixi
# collected ophandles should expire after 1 day
def _make_collected_ophandle(ophandle):
url = (self.webish_url + self.public_url +
"/foo/?t=start-manifest&ophandle=%d" % ophandle)
"/foo?t=start-manifest&ophandle=%d" % ophandle)
# By following the initial redirect, we collect the ophandle
# we've just created.
return do_http("post", url,

View File

@ -105,15 +105,10 @@ class DirectoryNodeHandler(ReplaceMeMixin, Resource, object):
# or no further children) renders "this" page
name = name.decode('utf8')
if not name:
# replicating Nevow behavior that complains about "empty
# path segments" .. but twisted.web sends in "name=''"
# for a URL like "/foo/bar/" as well as "/foo//bar"
# (i.e. a trailing slash means "name=''" as well)
if b'//' in req.path:
raise EmptyPathnameComponentError(
u"The webapi does not allow empty pathname components, i.e. a double slash",
)
return self
raise EmptyPathnameComponentError(
u"The webapi does not allow empty pathname components",
)
d = self.node.get(name)
d.addBoth(self._got_child, req, name)
return d