Note that this operation does not take its child cap in the form of
separate "rw_uri" and "ro_uri" fields. Therefore, it cannot accept a
- child cap in a format unknown to the webapi server, because the server
- is not able to attenuate an unknown write cap to a read cap.
+ child cap in a format unknown to the webapi server, unless its URI
+ starts with "ro." or "imm.". This restriction is necessary because the
+ server is not able to attenuate an unknown write cap to a read cap.
+ Unknown URIs starting with "ro." or "imm.", on the other hand, are
+ assumed to represent read caps. The client should not prefix a write
+ cap with "ro." or "imm." and pass it to this operation, since that
+ would result in granting the cap's write authority to holders of the
+ directory read cap.
=== Adding multiple files or directories to a parent directory at once ===
This attaches a given read- or write- cap "CHILDCAP" to the designated
directory, with a specified child name. This behaves much like the PUT t=uri
- operation, and is a lot like a UNIX hardlink.
+ operation, and is a lot like a UNIX hardlink. It is subject to the same
+ restrictions as that operation on the use of cap formats unknown to the
+ webapi server.
This will create additional intermediate directories as necessary, although
since it is expected to be triggered by a form that was retrieved by "GET
while position < len(data):
entries, position = split_netstring(data, 1, position)
entry = entries[0]
- (name, ro_uri, rwcapdata, metadata_s), subpos = split_netstring(entry, 4)
+ (name_utf8, ro_uri, rwcapdata, metadata_s), subpos = split_netstring(entry, 4)
if not mutable and len(rwcapdata) > 0:
raise ValueError("the rwcapdata field of a dirnode in an immutable directory was not empty")
- name = name.decode("utf-8")
+ name = name_utf8.decode("utf-8")
rw_uri = ""
if writeable:
rw_uri = self._decrypt_rwcapdata(rwcapdata)
# Since the encryption uses CTR mode, it currently leaks the length of the
# plaintext rw_uri -- and therefore whether it is present, i.e. whether the
- # dirnode is writeable (ticket #925). By stripping spaces in Tahoe >= 1.6.0,
- # we may make it easier for future versions to plug this leak.
+ # dirnode is writeable (ticket #925). By stripping trailing spaces in
+ # Tahoe >= 1.6.0, we may make it easier for future versions to plug this leak.
# ro_uri is treated in the same way for consistency.
# rw_uri and ro_uri will be either None or a non-empty string.
- rw_uri = rw_uri.strip(' ') or None
- ro_uri = ro_uri.strip(' ') or None
+ rw_uri = rw_uri.rstrip(' ') or None
+ ro_uri = ro_uri.rstrip(' ') or None
try:
child = self._create_and_validate_node(rw_uri, ro_uri, name)
children.set_with_aux(name, (child, metadata), auxilliary=entry)
else:
log.msg(format="mutable cap for child '%(name)s' unpacked from an immutable directory",
- name=name.encode("utf-8"),
+ name=name_utf8,
facility="tahoe.webish", level=log.UNUSUAL)
except CapConstraintError, e:
log.msg(format="unmet constraint on cap for child '%(name)s' unpacked from a directory:\n"
- "%(message)s", message=e.args[0], name=name.encode("utf-8"),
+ "%(message)s", message=e.args[0], name=name_utf8,
facility="tahoe.webish", level=log.UNUSUAL)
return children
from allmydata.mutable.filenode import MutableFileNode
from allmydata.mutable.common import UncoordinatedWriteError
from allmydata.util import hashutil, base32
+from allmydata.util.netstring import split_netstring
from allmydata.monitor import Monitor
from allmydata.test.common import make_chk_file_uri, make_mutable_file_uri, \
ErrorMixin
self.set_up_grid()
c = self.g.clients[0]
nm = c.nodemaker
+
setup_py_uri = "URI:CHK:n7r3m6wmomelk4sep3kw5cvduq:os7ijw5c3maek7pg65e5254k2fzjflavtpejjyhshpsxuqzhcwwq:3:20:14861"
one_uri = "URI:LIT:n5xgk" # LIT for "one"
mut_write_uri = "URI:SSK:vfvcbdfbszyrsaxchgevhmmlii:euw4iw7bbnkrrwpzuburbhppuxhc3gwxv26f6imekhz7zyw2ojnq"
bad_future_node = UnknownNode(future_write_uri, None)
bad_kids1 = {u"one": (bad_future_node, {})}
+ # This should fail because we don't know how to diminish the future_write_uri
+ # cap (given in a write slot and not prefixed with "ro." or "imm.") to a readcap.
d.addCallback(lambda ign:
self.shouldFail(MustNotBeUnknownRWError, "bad_kids1",
"cannot attach unknown",
self.set_up_grid()
c = self.g.clients[0]
nm = c.nodemaker
+
setup_py_uri = "URI:CHK:n7r3m6wmomelk4sep3kw5cvduq:os7ijw5c3maek7pg65e5254k2fzjflavtpejjyhshpsxuqzhcwwq:3:20:14861"
one_uri = "URI:LIT:n5xgk" # LIT for "one"
mut_write_uri = "URI:SSK:vfvcbdfbszyrsaxchgevhmmlii:euw4iw7bbnkrrwpzuburbhppuxhc3gwxv26f6imekhz7zyw2ojnq"
d.addCallback(_made_parent)
return d
+ def test_spaces_are_stripped_on_the_way_out(self):
+ self.basedir = "dirnode/Dirnode/test_spaces_are_stripped_on_the_way_out"
+ self.set_up_grid()
+ c = self.g.clients[0]
+ nm = c.nodemaker
+
+ # This test checks that any trailing spaces in URIs are retained in the
+ # encoded directory, but stripped when we get them out of the directory.
+ # See ticket #925 for why we want that.
+
+ stripped_write_uri = "lafs://from_the_future\t"
+ stripped_read_uri = "lafs://readonly_from_the_future\t"
+ spacedout_write_uri = stripped_write_uri + " "
+ spacedout_read_uri = stripped_read_uri + " "
+
+ child = nm.create_from_cap(spacedout_write_uri, spacedout_read_uri)
+ self.failUnlessEqual(child.get_write_uri(), spacedout_write_uri)
+ self.failUnlessEqual(child.get_readonly_uri(), "ro." + spacedout_read_uri)
+
+ kids = {u"child": (child, {})}
+ d = c.create_dirnode(kids)
+
+ def _created(dn):
+ self.failUnless(isinstance(dn, dirnode.DirectoryNode))
+ self.failUnless(dn.is_mutable())
+ self.failIf(dn.is_readonly())
+ dn.raise_error()
+ self.cap = dn.get_cap()
+ self.rootnode = dn
+ return dn._node.download_best_version()
+ d.addCallback(_created)
+
+ def _check_data(data):
+ # Decode the netstring representation of the directory to check that the
+ # spaces are retained when the URIs are stored.
+ position = 0
+ numkids = 0
+ while position < len(data):
+ entries, position = split_netstring(data, 1, position)
+ entry = entries[0]
+ (name_utf8, ro_uri, rwcapdata, metadata_s), subpos = split_netstring(entry, 4)
+ name = name_utf8.decode("utf-8")
+ rw_uri = self.rootnode._decrypt_rwcapdata(rwcapdata)
+ self.failUnless(name in kids)
+ (expected_child, ign) = kids[name]
+ self.failUnlessEqual(rw_uri, expected_child.get_write_uri())
+ self.failUnlessEqual("ro." + ro_uri, expected_child.get_readonly_uri())
+ numkids += 1
+
+ self.failUnlessEqual(numkids, 1)
+ return self.rootnode.list()
+ d.addCallback(_check_data)
+
+ # Now when we use the real directory listing code, the trailing spaces
+ # should have been stripped (and "ro." should have been prepended to the
+ # ro_uri, since it's unknown).
+ def _check_kids(children):
+ self.failUnlessEqual(sorted(children.keys()), [u"child"])
+ child_node, child_metadata = children[u"child"]
+
+ self.failUnlessEqual(child_node.get_write_uri(), stripped_write_uri)
+ self.failUnlessEqual(child_node.get_readonly_uri(), "ro." + stripped_read_uri)
+ d.addCallback(_check_kids)
+
+ d.addCallback(lambda ign: nm.create_from_cap(self.cap.to_string()))
+ d.addCallback(lambda n: n.list())
+ d.addCallback(_check_kids) # again with dirnode recreated from cap
+ return d
+
def test_check(self):
self.basedir = "dirnode/Dirnode/test_check"
self.set_up_grid()
def is_allowed_in_immutable_directory(self):
return False
+ def raise_error(self):
+ pass
+
def modify(self, modifier):
self.data = modifier(self.data, None, True)
return defer.succeed(None)
self.failUnlessEqual(fn1.get_uri(), u.to_string())
self.failUnlessEqual(fn1.get_cap(), u)
self.failUnlessEqual(fn1.get_readcap(), u)
- self.failUnlessEqual(fn1.is_readonly(), True)
- self.failUnlessEqual(fn1.is_mutable(), False)
- self.failUnlessEqual(fn1.is_unknown(), False)
- self.failUnlessEqual(fn1.is_allowed_in_immutable_directory(), True)
+ self.failUnless(fn1.is_readonly())
+ self.failIf(fn1.is_mutable())
+ self.failIf(fn1.is_unknown())
+ self.failUnless(fn1.is_allowed_in_immutable_directory())
self.failUnlessEqual(fn1.get_write_uri(), None)
self.failUnlessEqual(fn1.get_readonly_uri(), u.to_string())
self.failUnlessEqual(fn1.get_size(), 1000)
v = fn1.get_verify_cap()
self.failUnless(isinstance(v, uri.CHKFileVerifierURI))
self.failUnlessEqual(fn1.get_repair_cap(), v)
- self.failUnlessEqual(v.is_readonly(), True)
- self.failUnlessEqual(v.is_mutable(), False)
+ self.failUnless(v.is_readonly())
+ self.failIf(v.is_mutable())
def test_literal_filenode(self):
self.failUnlessEqual(fn1.get_uri(), u.to_string())
self.failUnlessEqual(fn1.get_cap(), u)
self.failUnlessEqual(fn1.get_readcap(), u)
- self.failUnlessEqual(fn1.is_readonly(), True)
- self.failUnlessEqual(fn1.is_mutable(), False)
- self.failUnlessEqual(fn1.is_unknown(), False)
- self.failUnlessEqual(fn1.is_allowed_in_immutable_directory(), True)
+ self.failUnless(fn1.is_readonly())
+ self.failIf(fn1.is_mutable())
+ self.failIf(fn1.is_unknown())
+ self.failUnless(fn1.is_allowed_in_immutable_directory())
self.failUnlessEqual(fn1.get_write_uri(), None)
self.failUnlessEqual(fn1.get_readonly_uri(), u.to_string())
self.failUnlessEqual(fn1.get_size(), len(DATA))
self.failUnlessEqual(n.get_readonly_uri(), u.get_readonly().to_string())
self.failUnlessEqual(n.get_cap(), u)
self.failUnlessEqual(n.get_readcap(), u.get_readonly())
- self.failUnlessEqual(n.is_mutable(), True)
- self.failUnlessEqual(n.is_readonly(), False)
- self.failUnlessEqual(n.is_unknown(), False)
- self.failUnlessEqual(n.is_allowed_in_immutable_directory(), False)
+ self.failUnless(n.is_mutable())
+ self.failIf(n.is_readonly())
+ self.failIf(n.is_unknown())
+ self.failIf(n.is_allowed_in_immutable_directory())
n.raise_error()
n2 = MutableFileNode(None, None, client.get_encoding_parameters(),
self.failUnlessEqual(nro.get_readonly(), nro)
self.failUnlessEqual(nro.get_cap(), u.get_readonly())
self.failUnlessEqual(nro.get_readcap(), u.get_readonly())
- self.failUnlessEqual(nro.is_mutable(), True)
- self.failUnlessEqual(nro.is_readonly(), True)
- self.failUnlessEqual(nro.is_unknown(), False)
- self.failUnlessEqual(nro.is_allowed_in_immutable_directory(), False)
+ self.failUnless(nro.is_mutable())
+ self.failUnless(nro.is_readonly())
+ self.failIf(nro.is_unknown())
+ self.failIf(nro.is_allowed_in_immutable_directory())
nro_u = nro.get_uri()
self.failUnlessEqual(nro_u, nro.get_readonly_uri())
self.failUnlessEqual(nro_u, u.get_readonly().to_string())
def test_POST_set_children_with_hyphen(self):
return self.test_POST_set_children(command_name="set-children")
- def test_POST_put_uri(self):
+ def test_POST_link_uri(self):
contents, n, newuri = self.makefile(8)
d = self.POST(self.public_url + "/foo", t="uri", name="new.txt", uri=newuri)
d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt")
contents))
return d
- def test_POST_put_uri_replace(self):
+ def test_POST_link_uri_replace(self):
contents, n, newuri = self.makefile(8)
d = self.POST(self.public_url + "/foo", t="uri", name="bar.txt", uri=newuri)
d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"bar.txt")
contents))
return d
- def test_POST_put_uri_no_replace_queryarg(self):
+ def test_POST_link_uri_unknown_bad(self):
+ newuri = "lafs://from_the_future"
+ d = self.POST(self.public_url + "/foo", t="uri", name="future.txt", uri=newuri)
+ d.addBoth(self.shouldFail, error.Error,
+ "POST_link_uri_unknown_bad",
+ "400 Bad Request",
+ "unknown cap in a write slot")
+ return d
+
+ def test_POST_link_uri_unknown_ro_good(self):
+ newuri = "ro.lafs://readonly_from_the_future"
+ d = self.POST(self.public_url + "/foo", t="uri", name="future-ro.txt", uri=newuri)
+ d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"future-ro.txt")
+ return d
+
+ def test_POST_link_uri_unknown_imm_good(self):
+ newuri = "imm.lafs://immutable_from_the_future"
+ d = self.POST(self.public_url + "/foo", t="uri", name="future-imm.txt", uri=newuri)
+ d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"future-imm.txt")
+ return d
+
+ def test_POST_link_uri_no_replace_queryarg(self):
contents, n, newuri = self.makefile(8)
d = self.POST(self.public_url + "/foo?replace=false", t="uri",
name="bar.txt", uri=newuri)
d.addBoth(self.shouldFail, error.Error,
- "POST_put_uri_no_replace_queryarg",
+ "POST_link_uri_no_replace_queryarg",
"409 Conflict",
"There was already a child by that name, and you asked me "
"to not replace it")
d.addCallback(self.failUnlessIsBarDotTxt)
return d
- def test_POST_put_uri_no_replace_field(self):
+ def test_POST_link_uri_no_replace_field(self):
contents, n, newuri = self.makefile(8)
d = self.POST(self.public_url + "/foo", t="uri", replace="false",
name="bar.txt", uri=newuri)
d.addBoth(self.shouldFail, error.Error,
- "POST_put_uri_no_replace_field",
+ "POST_link_uri_no_replace_field",
"409 Conflict",
"There was already a child by that name, and you asked me "
"to not replace it")
"to not replace it")
return d
+ def test_PUT_NEWFILEURL_uri_unknown_bad(self):
+ new_uri = "lafs://from_the_future"
+ d = self.PUT(self.public_url + "/foo/put-future.txt?t=uri", new_uri)
+ d.addBoth(self.shouldFail, error.Error,
+ "POST_put_uri_unknown_bad",
+ "400 Bad Request",
+ "unknown cap in a write slot")
+ return d
+
+ def test_PUT_NEWFILEURL_uri_unknown_ro_good(self):
+ new_uri = "ro.lafs://readonly_from_the_future"
+ d = self.PUT(self.public_url + "/foo/put-future-ro.txt?t=uri", new_uri)
+ d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node,
+ u"put-future-ro.txt")
+ return d
+
+ def test_PUT_NEWFILEURL_uri_unknown_imm_good(self):
+ new_uri = "imm.lafs://immutable_from_the_future"
+ d = self.PUT(self.public_url + "/foo/put-future-imm.txt?t=uri", new_uri)
+ d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node,
+ u"put-future-imm.txt")
+ return d
+
def test_PUT_NEWFILE_URI(self):
file_contents = "New file contents here\n"
d = self.PUT("/uri", file_contents)
while position < len(data):
entries, position = split_netstring(data, 1, position)
entry = entries[0]
- (name, ro_uri, rwcapdata, metadata_s), subpos = split_netstring(entry, 4)
- name = name.decode("utf-8")
+ (name_utf8, ro_uri, rwcapdata, metadata_s), subpos = split_netstring(entry, 4)
+ name = name_utf8.decode("utf-8")
self.failUnless(rwcapdata == "")
- ro_uri = ro_uri.strip()
- if name in kids:
- self.failIfEqual(ro_uri, "")
- (expected_child, ign) = kids[name]
- self.failUnlessEqual(ro_uri, expected_child.get_readonly_uri())
- numkids += 1
+ self.failUnless(name in kids)
+ (expected_child, ign) = kids[name]
+ self.failUnlessEqual(ro_uri, expected_child.get_readonly_uri())
+ numkids += 1
self.failUnlessEqual(numkids, 3)
return self.rootnode.list()