From: Brian Warner Date: Thu, 14 Feb 2008 22:45:56 +0000 (-0700) Subject: unicode handling: declare dirnodes to contain unicode child names, update webish... X-Git-Tag: allmydata-tahoe-0.8.0~55 X-Git-Url: https://git.rkrishnan.org/pf/content/en/service/contact.html?a=commitdiff_plain;h=7927495cbe3f354d463e748d6f882a2ee77493b8;p=tahoe-lafs%2Ftahoe-lafs.git unicode handling: declare dirnodes to contain unicode child names, update webish to match --- diff --git a/docs/webapi.txt b/docs/webapi.txt index 5e77a740..deeddec1 100644 --- a/docs/webapi.txt +++ b/docs/webapi.txt @@ -58,6 +58,18 @@ In addition, each directory has a corresponding URL. The Pictures URL is: http://localhost:8123/uri/$PRIVATE_VDRIVE_URI/Pictures +Note that all filenames are assumed to be UTF-8 encoded, so "resume.doc" +(with an acute accent on both E's) would be accessed with: + + http://localhost:8123/uri/$PRIVATE_VDRIVE_URI/r%C3%A9sum%C3%A9.doc + +The filenames inside upload POST forms are interpreted using whatever +character set was provided in the conventional '_charset' field, and defaults +to UTF-8 if not otherwise specified. The JSON representation of each +directory contains native unicode strings. Tahoe directories are specified to +contain unicode filenames, and cannot contain binary strings that are not +representable as such. + c. URIs From the "URIs" chapter in architecture.txt, recall that each file and diff --git a/src/allmydata/dirnode.py b/src/allmydata/dirnode.py index 72d44ca9..fc6debc9 100644 --- a/src/allmydata/dirnode.py +++ b/src/allmydata/dirnode.py @@ -102,8 +102,8 @@ class NewDirectoryNode: # the directory is serialized as a list of netstrings, one per child. # Each child is serialized as a list of four netstrings: (name, # rocap, rwcap, metadata), in which the name,rocap,metadata are in - # cleartext. The rwcap is formatted as: - # pack("16ss32s", iv, AES(H(writekey+iv), plaintextrwcap), mac) + # cleartext. The 'name' is UTF-8 encoded. The rwcap is formatted as: + # pack("16ss32s", iv, AES(H(writekey+iv), plaintextrwcap), mac) assert isinstance(data, str) # an empty directory is serialized as an empty string if data == "": @@ -113,6 +113,7 @@ class NewDirectoryNode: while len(data) > 0: entry, data = split_netstring(data, 1, True) name, rocap, rwcapdata, metadata_s = split_netstring(entry, 4) + name = name.decode("utf-8") if writeable: rwcap = self._decrypt_rwcapdata(rwcapdata) child = self._create_node(rwcap) @@ -129,13 +130,14 @@ class NewDirectoryNode: entries = [] for name in sorted(children.keys()): child, metadata = children[name] + assert isinstance(name, unicode) assert (IFileNode.providedBy(child) or IMutableFileNode.providedBy(child) or IDirectoryNode.providedBy(child)), (name,child) assert isinstance(metadata, dict) rwcap = child.get_uri() # might be RO if the child is not writeable rocap = child.get_readonly_uri() - entry = "".join([netstring(name), + entry = "".join([netstring(name.encode("utf-8")), netstring(rocap), netstring(self._encrypt_rwcap(rwcap)), netstring(simplejson.dumps(metadata))]) @@ -168,6 +170,7 @@ class NewDirectoryNode: def has_child(self, name): """I return a Deferred that fires with a boolean, True if there exists a child of the given name, False if not.""" + assert isinstance(name, unicode) d = self._read() d.addCallback(lambda children: children.has_key(name)) return d @@ -181,16 +184,19 @@ class NewDirectoryNode: def get(self, name): """I return a Deferred that fires with the named child node, which is either an IFileNode or an IDirectoryNode.""" + assert isinstance(name, unicode) d = self._read() d.addCallback(self._get, name) return d def get_metadata_for(self, name): + assert isinstance(name, unicode) d = self._read() d.addCallback(lambda children: children[name][1]) return d def set_metadata_for(self, name, metadata): + assert isinstance(name, unicode) if self.is_readonly(): return defer.fail(NotMutableError()) assert isinstance(metadata, dict) @@ -216,8 +222,12 @@ class NewDirectoryNode: if not path: return defer.succeed(self) - if isinstance(path, (str, unicode)): + if isinstance(path, (list, tuple)): + pass + else: path = path.split("/") + for p in path: + assert isinstance(p, unicode) childname = path[0] remaining_path = path[1:] d = self.get(childname) @@ -237,6 +247,7 @@ class NewDirectoryNode: If this directory node is read-only, the Deferred will errback with a NotMutableError.""" + assert isinstance(name, unicode) return self.set_node(name, self._create_node(child_uri), metadata) def set_uris(self, entries): @@ -248,6 +259,7 @@ class NewDirectoryNode: else: assert len(e) == 3 name, child_uri, metadata = e + assert isinstance(name, unicode) node_entries.append( (name,self._create_node(child_uri),metadata) ) return self.set_nodes(node_entries) @@ -259,6 +271,7 @@ class NewDirectoryNode: If this directory node is read-only, the Deferred will errback with a NotMutableError.""" + assert isinstance(name, unicode) assert IFilesystemNode.providedBy(child), child d = self.set_nodes( [(name, child, metadata)]) d.addCallback(lambda res: child) @@ -277,6 +290,7 @@ class NewDirectoryNode: else: assert len(e) == 3 name, child, new_metadata = e + assert isinstance(name, unicode) if name in children: metadata = children[name][1].copy() else: @@ -303,6 +317,7 @@ class NewDirectoryNode: resulting FileNode to the directory at the given name. I return a Deferred that fires (with the IFileNode of the uploaded file) when the operation completes.""" + assert isinstance(name, unicode) if self.is_readonly(): return defer.fail(NotMutableError()) d = self._client.upload(uploadable) @@ -314,6 +329,7 @@ class NewDirectoryNode: def delete(self, name): """I remove the child at the specific name. I return a Deferred that fires (with the node just removed) when the operation finishes.""" + assert isinstance(name, unicode) if self.is_readonly(): return defer.fail(NotMutableError()) d = self._read() @@ -333,6 +349,7 @@ class NewDirectoryNode: """I create and attach an empty directory at the given name. I return a Deferred that fires (with the new directory node) when the operation finishes.""" + assert isinstance(name, unicode) if self.is_readonly(): return defer.fail(NotMutableError()) d = self._client.create_empty_dirnode() @@ -349,10 +366,12 @@ class NewDirectoryNode: is referenced by name. On the new parent, the child will live under 'new_child_name', which defaults to 'current_child_name'. I return a Deferred that fires when the operation finishes.""" + assert isinstance(current_child_name, unicode) if self.is_readonly() or new_parent.is_readonly(): return defer.fail(NotMutableError()) if new_child_name is None: new_child_name = current_child_name + assert isinstance(new_child_name, unicode) d = self.get(current_child_name) def sn(child): return new_parent.set_node(new_child_name, child) diff --git a/src/allmydata/interfaces.py b/src/allmydata/interfaces.py index 986ed4db..fe030101 100644 --- a/src/allmydata/interfaces.py +++ b/src/allmydata/interfaces.py @@ -587,6 +587,11 @@ class IMutableFileNode(IFileNode, IMutableFilesystemNode): """ class IDirectoryNode(IMutableFilesystemNode): + """I represent a name-to-child mapping, holding the tahoe equivalent of a + directory. All child names are unicode strings, and all children are some + sort of IFilesystemNode (either files or subdirectories). + """ + def get_uri(): """ The dirnode ('1') URI returned by this method can be used in @@ -607,30 +612,33 @@ class IDirectoryNode(IMutableFilesystemNode): def list(): """I return a Deferred that fires with a dictionary mapping child - name to (node, metadata_dict) tuples, in which 'node' is either an - IFileNode or IDirectoryNode, and 'metadata_dict' is a dictionary of - metadata.""" + name (a unicode string) to (node, metadata_dict) tuples, in which + 'node' is either an IFileNode or IDirectoryNode, and 'metadata_dict' + is a dictionary of metadata.""" def has_child(name): """I return a Deferred that fires with a boolean, True if there - exists a child of the given name, False if not.""" + exists a child of the given name, False if not. The child name must + be a unicode string.""" def get(name): """I return a Deferred that fires with a specific named child node, - either an IFileNode or an IDirectoryNode.""" + either an IFileNode or an IDirectoryNode. The child name must be a + unicode string.""" def get_metadata_for(name): """I return a Deferred that fires with the metadata dictionary for a specific named child node. This metadata is stored in the *edge*, not in the child, so it is attached to the parent dirnode rather than the - child dir-or-file-node.""" + child dir-or-file-node. The child name must be a unicode string.""" def set_metadata_for(name, metadata): """I replace any existing metadata for the named child with the new - metadata. This metadata is stored in the *edge*, not in the child, so - it is attached to the parent dirnode rather than the child - dir-or-file-node. I return a Deferred (that fires with this dirnode) - when the operation is complete.""" + metadata. The child name must be a unicode string. This metadata is + stored in the *edge*, not in the child, so it is attached to the + parent dirnode rather than the child dir-or-file-node. I return a + Deferred (that fires with this dirnode) when the operation is + complete.""" def get_child_at_path(path): """Transform a child path into an IDirectoryNode or IFileNode. @@ -640,13 +648,13 @@ class IDirectoryNode(IMutableFilesystemNode): errbacks with IndexError if the node could not be found. The path can be either a single string (slash-separated) or a list of - path-name elements. + path-name elements. All elements must be unicode strings. """ def set_uri(name, child_uri, metadata=None): """I add a child (by URI) at the specific name. I return a Deferred that fires when the operation finishes. I will replace any existing - child of the same name. + child of the same name. The child name must be a unicode string. The child_uri could be for a file, or for a directory (either read-write or read-only, using a URI that came from get_uri() ). @@ -665,14 +673,15 @@ class IDirectoryNode(IMutableFilesystemNode): """Add multiple (name, child_uri) pairs (or (name, child_uri, metadata) triples) to a directory node. Returns a Deferred that fires (with None) when the operation finishes. This is equivalent to - calling set_uri() multiple times, but is much more efficient. + calling set_uri() multiple times, but is much more efficient. All + child names must be unicode strings. """ def set_node(name, child, metadata=None): """I add a child at the specific name. I return a Deferred that fires when the operation finishes. This Deferred will fire with the child node that was just added. I will replace any existing child of the - same name. + same name. The child name must be a unicode string. If metadata= is provided, I will use it as the metadata for the named edge. This will replace any existing metadata. If metadata= is left @@ -688,31 +697,35 @@ class IDirectoryNode(IMutableFilesystemNode): """Add multiple (name, child_node) pairs (or (name, child_node, metadata) triples) to a directory node. Returns a Deferred that fires (with None) when the operation finishes. This is equivalent to - calling set_node() multiple times, but is much more efficient.""" + calling set_node() multiple times, but is much more efficient. All + child names must be unicode strings.""" def add_file(name, uploadable, metadata=None): """I upload a file (using the given IUploadable), then attach the resulting FileNode to the directory at the given name. I set metadata - the same way as set_uri and set_node. + the same way as set_uri and set_node. The child name must be a + unicode string. I return a Deferred that fires (with the IFileNode of the uploaded file) when the operation completes.""" def delete(name): """I remove the child at the specific name. I return a Deferred that - fires when the operation finishes.""" + fires when the operation finishes. The child name must be a unicode + string.""" def create_empty_directory(name): - """I create and attach an empty directory at the given name. I return - a Deferred that fires when the operation finishes.""" + """I create and attach an empty directory at the given name. The + child name must be a unicode string. I return a Deferred that fires + when the operation finishes.""" def move_child_to(current_child_name, new_parent, new_child_name=None): """I take one of my children and move them to a new parent. The child is referenced by name. On the new parent, the child will live under 'new_child_name', which defaults to 'current_child_name'. TODO: what should we do about metadata? I return a Deferred that fires when the - operation finishes.""" + operation finishes. The child name must be a unicode string.""" def build_manifest(): """Return a frozenset of verifier-capability strings for all nodes diff --git a/src/allmydata/test/test_dirnode.py b/src/allmydata/test/test_dirnode.py index 2cca9a49..c97d8dd2 100644 --- a/src/allmydata/test/test_dirnode.py +++ b/src/allmydata/test/test_dirnode.py @@ -83,14 +83,14 @@ class Dirnode(unittest.TestCase, testutil.ShouldFailMixin): d = self.client.create_empty_dirnode() def _created(dn): u = make_mutable_file_uri() - d = dn.set_uri("child", u, {}) + d = dn.set_uri(u"child", u, {}) d.addCallback(lambda res: dn.list()) def _check1(children): - self.failUnless("child" in children) + self.failUnless(u"child" in children) d.addCallback(_check1) d.addCallback(lambda res: self.shouldFail(KeyError, "get bogus", None, - dn.get, "bogus")) + dn.get, u"bogus")) def _corrupt(res): filenode = dn._node si = IURI(filenode.get_uri()).storage_index @@ -126,7 +126,7 @@ class Dirnode(unittest.TestCase, testutil.ShouldFailMixin): d = self.client.create_empty_dirnode() def _created(rw_dn): - d2 = rw_dn.set_uri("child", fileuri) + d2 = rw_dn.set_uri(u"child", fileuri) d2.addCallback(lambda res: rw_dn) return d2 d.addCallback(_created) @@ -138,23 +138,23 @@ class Dirnode(unittest.TestCase, testutil.ShouldFailMixin): self.failUnless(ro_dn.is_mutable()) self.shouldFail(dirnode.NotMutableError, "set_uri ro", None, - ro_dn.set_uri, "newchild", fileuri) + ro_dn.set_uri, u"newchild", fileuri) self.shouldFail(dirnode.NotMutableError, "set_uri ro", None, - ro_dn.set_node, "newchild", filenode) + ro_dn.set_node, u"newchild", filenode) self.shouldFail(dirnode.NotMutableError, "set_uri ro", None, - ro_dn.add_file, "newchild", uploadable) + ro_dn.add_file, u"newchild", uploadable) self.shouldFail(dirnode.NotMutableError, "set_uri ro", None, - ro_dn.delete, "child") + ro_dn.delete, u"child") self.shouldFail(dirnode.NotMutableError, "set_uri ro", None, - ro_dn.create_empty_directory, "newchild") + ro_dn.create_empty_directory, u"newchild") self.shouldFail(dirnode.NotMutableError, "set_uri ro", None, - ro_dn.move_child_to, "child", rw_dn) + ro_dn.move_child_to, u"child", rw_dn) self.shouldFail(dirnode.NotMutableError, "set_uri ro", None, - rw_dn.move_child_to, "child", ro_dn) + rw_dn.move_child_to, u"child", ro_dn) return ro_dn.list() d.addCallback(_ready) def _listed(children): - self.failUnless("child" in children) + self.failUnless(u"child" in children) d.addCallback(_listed) return d @@ -186,16 +186,16 @@ class Dirnode(unittest.TestCase, testutil.ShouldFailMixin): d = n.list() d.addCallback(lambda res: self.failUnlessEqual(res, {})) - d.addCallback(lambda res: n.has_child("missing")) + d.addCallback(lambda res: n.has_child(u"missing")) d.addCallback(lambda res: self.failIf(res)) fake_file_uri = make_mutable_file_uri() m = Marker(fake_file_uri) ffu_v = m.get_verifier() assert isinstance(ffu_v, str) self.expected_manifest.append(ffu_v) - d.addCallback(lambda res: n.set_uri("child", fake_file_uri)) + d.addCallback(lambda res: n.set_uri(u"child", fake_file_uri)) - d.addCallback(lambda res: n.create_empty_directory("subdir")) + d.addCallback(lambda res: n.create_empty_directory(u"subdir")) def _created(subdir): self.failUnless(isinstance(subdir, FakeDirectoryNode)) self.subdir = subdir @@ -207,7 +207,7 @@ class Dirnode(unittest.TestCase, testutil.ShouldFailMixin): d.addCallback(lambda res: n.list()) d.addCallback(lambda children: self.failUnlessEqual(sorted(children.keys()), - sorted(["child", "subdir"]))) + sorted([u"child", u"subdir"]))) d.addCallback(lambda res: n.build_manifest()) def _check_manifest(manifest): @@ -216,116 +216,116 @@ class Dirnode(unittest.TestCase, testutil.ShouldFailMixin): d.addCallback(_check_manifest) def _add_subsubdir(res): - return self.subdir.create_empty_directory("subsubdir") + return self.subdir.create_empty_directory(u"subsubdir") d.addCallback(_add_subsubdir) - d.addCallback(lambda res: n.get_child_at_path("subdir/subsubdir")) + d.addCallback(lambda res: n.get_child_at_path(u"subdir/subsubdir")) d.addCallback(lambda subsubdir: self.failUnless(isinstance(subsubdir, FakeDirectoryNode))) - d.addCallback(lambda res: n.get_child_at_path("")) + d.addCallback(lambda res: n.get_child_at_path(u"")) d.addCallback(lambda res: self.failUnlessEqual(res.get_uri(), n.get_uri())) - d.addCallback(lambda res: n.get_metadata_for("child")) + d.addCallback(lambda res: n.get_metadata_for(u"child")) d.addCallback(lambda metadata: self.failUnlessEqual(sorted(metadata.keys()), ["ctime", "mtime"])) # set_uri + metadata # it should be possible to add a child without any metadata - d.addCallback(lambda res: n.set_uri("c2", fake_file_uri, {})) - d.addCallback(lambda res: n.get_metadata_for("c2")) + d.addCallback(lambda res: n.set_uri(u"c2", fake_file_uri, {})) + d.addCallback(lambda res: n.get_metadata_for(u"c2")) d.addCallback(lambda metadata: self.failUnlessEqual(metadata, {})) # if we don't set any defaults, the child should get timestamps - d.addCallback(lambda res: n.set_uri("c3", fake_file_uri)) - d.addCallback(lambda res: n.get_metadata_for("c3")) + d.addCallback(lambda res: n.set_uri(u"c3", fake_file_uri)) + d.addCallback(lambda res: n.get_metadata_for(u"c3")) d.addCallback(lambda metadata: self.failUnlessEqual(sorted(metadata.keys()), ["ctime", "mtime"])) # or we can add specific metadata at set_uri() time, which # overrides the timestamps - d.addCallback(lambda res: n.set_uri("c4", fake_file_uri, + d.addCallback(lambda res: n.set_uri(u"c4", fake_file_uri, {"key": "value"})) - d.addCallback(lambda res: n.get_metadata_for("c4")) + d.addCallback(lambda res: n.get_metadata_for(u"c4")) d.addCallback(lambda metadata: self.failUnlessEqual(metadata, {"key": "value"})) - d.addCallback(lambda res: n.delete("c2")) - d.addCallback(lambda res: n.delete("c3")) - d.addCallback(lambda res: n.delete("c4")) + d.addCallback(lambda res: n.delete(u"c2")) + d.addCallback(lambda res: n.delete(u"c3")) + d.addCallback(lambda res: n.delete(u"c4")) # set_node + metadata # it should be possible to add a child without any metadata - d.addCallback(lambda res: n.set_node("d2", n, {})) - d.addCallback(lambda res: n.get_metadata_for("d2")) + d.addCallback(lambda res: n.set_node(u"d2", n, {})) + d.addCallback(lambda res: n.get_metadata_for(u"d2")) d.addCallback(lambda metadata: self.failUnlessEqual(metadata, {})) # if we don't set any defaults, the child should get timestamps - d.addCallback(lambda res: n.set_node("d3", n)) - d.addCallback(lambda res: n.get_metadata_for("d3")) + d.addCallback(lambda res: n.set_node(u"d3", n)) + d.addCallback(lambda res: n.get_metadata_for(u"d3")) d.addCallback(lambda metadata: self.failUnlessEqual(sorted(metadata.keys()), ["ctime", "mtime"])) # or we can add specific metadata at set_node() time, which # overrides the timestamps - d.addCallback(lambda res: n.set_node("d4", n, + d.addCallback(lambda res: n.set_node(u"d4", n, {"key": "value"})) - d.addCallback(lambda res: n.get_metadata_for("d4")) + d.addCallback(lambda res: n.get_metadata_for(u"d4")) d.addCallback(lambda metadata: self.failUnlessEqual(metadata, {"key": "value"})) - d.addCallback(lambda res: n.delete("d2")) - d.addCallback(lambda res: n.delete("d3")) - d.addCallback(lambda res: n.delete("d4")) + d.addCallback(lambda res: n.delete(u"d2")) + d.addCallback(lambda res: n.delete(u"d3")) + d.addCallback(lambda res: n.delete(u"d4")) # metadata through set_uris() - d.addCallback(lambda res: n.set_uris([ ("e1", fake_file_uri), - ("e2", fake_file_uri, {}), - ("e3", fake_file_uri, + d.addCallback(lambda res: n.set_uris([ (u"e1", fake_file_uri), + (u"e2", fake_file_uri, {}), + (u"e3", fake_file_uri, {"key": "value"}), ])) - d.addCallback(lambda res: n.get_metadata_for("e1")) + d.addCallback(lambda res: n.get_metadata_for(u"e1")) d.addCallback(lambda metadata: self.failUnlessEqual(sorted(metadata.keys()), ["ctime", "mtime"])) - d.addCallback(lambda res: n.get_metadata_for("e2")) + d.addCallback(lambda res: n.get_metadata_for(u"e2")) d.addCallback(lambda metadata: self.failUnlessEqual(metadata, {})) - d.addCallback(lambda res: n.get_metadata_for("e3")) + d.addCallback(lambda res: n.get_metadata_for(u"e3")) d.addCallback(lambda metadata: self.failUnlessEqual(metadata, {"key": "value"})) - d.addCallback(lambda res: n.delete("e1")) - d.addCallback(lambda res: n.delete("e2")) - d.addCallback(lambda res: n.delete("e3")) + d.addCallback(lambda res: n.delete(u"e1")) + d.addCallback(lambda res: n.delete(u"e2")) + d.addCallback(lambda res: n.delete(u"e3")) # metadata through set_nodes() - d.addCallback(lambda res: n.set_nodes([ ("f1", n), - ("f2", n, {}), - ("f3", n, + d.addCallback(lambda res: n.set_nodes([ (u"f1", n), + (u"f2", n, {}), + (u"f3", n, {"key": "value"}), ])) - d.addCallback(lambda res: n.get_metadata_for("f1")) + d.addCallback(lambda res: n.get_metadata_for(u"f1")) d.addCallback(lambda metadata: self.failUnlessEqual(sorted(metadata.keys()), ["ctime", "mtime"])) - d.addCallback(lambda res: n.get_metadata_for("f2")) + d.addCallback(lambda res: n.get_metadata_for(u"f2")) d.addCallback(lambda metadata: self.failUnlessEqual(metadata, {})) - d.addCallback(lambda res: n.get_metadata_for("f3")) + d.addCallback(lambda res: n.get_metadata_for(u"f3")) d.addCallback(lambda metadata: self.failUnlessEqual(metadata, {"key": "value"})) - d.addCallback(lambda res: n.delete("f1")) - d.addCallback(lambda res: n.delete("f2")) - d.addCallback(lambda res: n.delete("f3")) + d.addCallback(lambda res: n.delete(u"f1")) + d.addCallback(lambda res: n.delete(u"f2")) + d.addCallback(lambda res: n.delete(u"f3")) d.addCallback(lambda res: - n.set_metadata_for("child", + n.set_metadata_for(u"child", {"tags": ["web2.0-compatible"]})) - d.addCallback(lambda n1: n1.get_metadata_for("child")) + d.addCallback(lambda n1: n1.get_metadata_for(u"child")) d.addCallback(lambda metadata: self.failUnlessEqual(metadata, {"tags": ["web2.0-compatible"]})) @@ -339,14 +339,14 @@ class Dirnode(unittest.TestCase, testutil.ShouldFailMixin): # from causing the test to fail, stall for more than a few # hundrededths of a second. d.addCallback(self.stall, 0.1) - d.addCallback(lambda res: n.add_file("timestamps", + d.addCallback(lambda res: n.add_file(u"timestamps", upload.Data("stamp me"))) d.addCallback(self.stall, 0.1) def _stop(res): self._stop_timestamp = time.time() d.addCallback(_stop) - d.addCallback(lambda res: n.get_metadata_for("timestamps")) + d.addCallback(lambda res: n.get_metadata_for(u"timestamps")) def _check_timestamp1(metadata): self.failUnless("ctime" in metadata) self.failUnless("mtime" in metadata) @@ -364,28 +364,28 @@ class Dirnode(unittest.TestCase, testutil.ShouldFailMixin): self._old_mtime = metadata["mtime"] d.addCallback(_check_timestamp1) d.addCallback(self.stall, 2.0) # accomodate low-res timestamps - d.addCallback(lambda res: n.set_node("timestamps", n)) - d.addCallback(lambda res: n.get_metadata_for("timestamps")) + d.addCallback(lambda res: n.set_node(u"timestamps", n)) + d.addCallback(lambda res: n.get_metadata_for(u"timestamps")) def _check_timestamp2(metadata): self.failUnlessEqual(metadata["ctime"], self._old_ctime, "%s != %s" % (metadata["ctime"], self._old_ctime)) self.failUnlessGreaterThan(metadata["mtime"], self._old_mtime) - return n.delete("timestamps") + return n.delete(u"timestamps") d.addCallback(_check_timestamp2) # also make sure we can add/update timestamps on a # previously-existing child that didn't have any, since there are # a lot of 0.7.0-generated edges around out there - d.addCallback(lambda res: n.set_node("no_timestamps", n, {})) - d.addCallback(lambda res: n.set_node("no_timestamps", n)) - d.addCallback(lambda res: n.get_metadata_for("no_timestamps")) + d.addCallback(lambda res: n.set_node(u"no_timestamps", n, {})) + d.addCallback(lambda res: n.set_node(u"no_timestamps", n)) + d.addCallback(lambda res: n.get_metadata_for(u"no_timestamps")) d.addCallback(lambda metadata: self.failUnlessEqual(sorted(metadata.keys()), ["ctime", "mtime"])) - d.addCallback(lambda res: n.delete("no_timestamps")) + d.addCallback(lambda res: n.delete(u"no_timestamps")) - d.addCallback(lambda res: n.delete("subdir")) + d.addCallback(lambda res: n.delete(u"subdir")) d.addCallback(lambda old_child: self.failUnlessEqual(old_child.get_uri(), self.subdir.get_uri())) @@ -393,47 +393,47 @@ class Dirnode(unittest.TestCase, testutil.ShouldFailMixin): d.addCallback(lambda res: n.list()) d.addCallback(lambda children: self.failUnlessEqual(sorted(children.keys()), - sorted(["child"]))) + sorted([u"child"]))) uploadable = upload.Data("some data") - d.addCallback(lambda res: n.add_file("newfile", uploadable)) + d.addCallback(lambda res: n.add_file(u"newfile", uploadable)) d.addCallback(lambda newnode: self.failUnless(IFileNode.providedBy(newnode))) d.addCallback(lambda res: n.list()) d.addCallback(lambda children: self.failUnlessEqual(sorted(children.keys()), - sorted(["child", "newfile"]))) - d.addCallback(lambda res: n.get_metadata_for("newfile")) + sorted([u"child", u"newfile"]))) + d.addCallback(lambda res: n.get_metadata_for(u"newfile")) d.addCallback(lambda metadata: self.failUnlessEqual(sorted(metadata.keys()), ["ctime", "mtime"])) uploadable = upload.Data("some data") - d.addCallback(lambda res: n.add_file("newfile-metadata", + d.addCallback(lambda res: n.add_file(u"newfile-metadata", uploadable, {"key": "value"})) d.addCallback(lambda newnode: self.failUnless(IFileNode.providedBy(newnode))) - d.addCallback(lambda res: n.get_metadata_for("newfile-metadata")) + d.addCallback(lambda res: n.get_metadata_for(u"newfile-metadata")) d.addCallback(lambda metadata: self.failUnlessEqual(metadata, {"key": "value"})) - d.addCallback(lambda res: n.delete("newfile-metadata")) + d.addCallback(lambda res: n.delete(u"newfile-metadata")) - d.addCallback(lambda res: n.create_empty_directory("subdir2")) + d.addCallback(lambda res: n.create_empty_directory(u"subdir2")) def _created2(subdir2): self.subdir2 = subdir2 d.addCallback(_created2) d.addCallback(lambda res: - n.move_child_to("child", self.subdir2)) + n.move_child_to(u"child", self.subdir2)) d.addCallback(lambda res: n.list()) d.addCallback(lambda children: self.failUnlessEqual(sorted(children.keys()), - sorted(["newfile", "subdir2"]))) + sorted([u"newfile", u"subdir2"]))) d.addCallback(lambda res: self.subdir2.list()) d.addCallback(lambda children: self.failUnlessEqual(sorted(children.keys()), - sorted(["child"]))) + sorted([u"child"]))) return d diff --git a/src/allmydata/test/test_system.py b/src/allmydata/test/test_system.py index aaa92d17..a16d289e 100644 --- a/src/allmydata/test/test_system.py +++ b/src/allmydata/test/test_system.py @@ -750,10 +750,10 @@ class SystemTest(testutil.SignalMixin, testutil.PollMixin, unittest.TestCase): log.msg("_created_dirnode(%s)" % (dnode,)) d1 = dnode.list() d1.addCallback(lambda children: self.failUnlessEqual(children, {})) - d1.addCallback(lambda res: dnode.has_child("edgar")) + d1.addCallback(lambda res: dnode.has_child(u"edgar")) d1.addCallback(lambda answer: self.failUnlessEqual(answer, False)) - d1.addCallback(lambda res: dnode.set_node("see recursive", dnode)) - d1.addCallback(lambda res: dnode.has_child("see recursive")) + d1.addCallback(lambda res: dnode.set_node(u"see recursive", dnode)) + d1.addCallback(lambda res: dnode.has_child(u"see recursive")) d1.addCallback(lambda answer: self.failUnlessEqual(answer, True)) d1.addCallback(lambda res: dnode.build_manifest()) d1.addCallback(lambda manifest: @@ -840,10 +840,10 @@ class SystemTest(testutil.SignalMixin, testutil.PollMixin, unittest.TestCase): self._root_directory_uri = new_dirnode.get_uri() return c0.create_node_from_uri(self._root_directory_uri) d.addCallback(_made_root) - d.addCallback(lambda root: root.create_empty_directory("subdir1")) + d.addCallback(lambda root: root.create_empty_directory(u"subdir1")) def _made_subdir1(subdir1_node): self._subdir1_node = subdir1_node - d1 = subdir1_node.add_file("mydata567", ut) + d1 = subdir1_node.add_file(u"mydata567", ut) d1.addCallback(self.log, "publish finished") def _stash_uri(filenode): self.uri = filenode.get_uri() @@ -854,8 +854,8 @@ class SystemTest(testutil.SignalMixin, testutil.PollMixin, unittest.TestCase): def _do_publish2(self, res): ut = upload.Data(self.data) - d = self._subdir1_node.create_empty_directory("subdir2") - d.addCallback(lambda subdir2: subdir2.add_file("mydata992", ut)) + d = self._subdir1_node.create_empty_directory(u"subdir2") + d.addCallback(lambda subdir2: subdir2.add_file(u"mydata992", ut)) return d def log(self, res, msg, **kwargs): @@ -875,14 +875,14 @@ class SystemTest(testutil.SignalMixin, testutil.PollMixin, unittest.TestCase): d.addCallback(self.log, "GOT private directory") def _got_new_dir(privnode): rootnode = self.clients[0].create_node_from_uri(self._root_directory_uri) - d1 = privnode.create_empty_directory("personal") + d1 = privnode.create_empty_directory(u"personal") d1.addCallback(self.log, "made P/personal") - d1.addCallback(lambda node: node.add_file("sekrit data", ut)) + d1.addCallback(lambda node: node.add_file(u"sekrit data", ut)) d1.addCallback(self.log, "made P/personal/sekrit data") - d1.addCallback(lambda res: rootnode.get_child_at_path(["subdir1", "subdir2"])) + d1.addCallback(lambda res: rootnode.get_child_at_path([u"subdir1", u"subdir2"])) def _got_s2(s2node): - d2 = privnode.set_uri("s2-rw", s2node.get_uri()) - d2.addCallback(lambda node: privnode.set_uri("s2-ro", s2node.get_readonly_uri())) + d2 = privnode.set_uri(u"s2-rw", s2node.get_uri()) + d2.addCallback(lambda node: privnode.set_uri(u"s2-ro", s2node.get_readonly_uri())) return d2 d1.addCallback(_got_s2) d1.addCallback(lambda res: privnode) @@ -895,8 +895,8 @@ class SystemTest(testutil.SignalMixin, testutil.PollMixin, unittest.TestCase): c1 = self.clients[1] d = defer.succeed(c1.create_node_from_uri(self._root_directory_uri)) d.addCallback(self.log, "check_publish1 got /") - d.addCallback(lambda root: root.get("subdir1")) - d.addCallback(lambda subdir1: subdir1.get("mydata567")) + d.addCallback(lambda root: root.get(u"subdir1")) + d.addCallback(lambda subdir1: subdir1.get(u"mydata567")) d.addCallback(lambda filenode: filenode.download_to_data()) d.addCallback(self.log, "get finished") def _get_done(data): @@ -907,14 +907,14 @@ class SystemTest(testutil.SignalMixin, testutil.PollMixin, unittest.TestCase): def _check_publish2(self, res): # this one uses the path-based API rootnode = self.clients[1].create_node_from_uri(self._root_directory_uri) - d = rootnode.get_child_at_path("subdir1") + d = rootnode.get_child_at_path(u"subdir1") d.addCallback(lambda dirnode: self.failUnless(IDirectoryNode.providedBy(dirnode))) - d.addCallback(lambda res: rootnode.get_child_at_path("subdir1/mydata567")) + d.addCallback(lambda res: rootnode.get_child_at_path(u"subdir1/mydata567")) d.addCallback(lambda filenode: filenode.download_to_data()) d.addCallback(lambda data: self.failUnlessEqual(data, self.data)) - d.addCallback(lambda res: rootnode.get_child_at_path("subdir1/mydata567")) + d.addCallback(lambda res: rootnode.get_child_at_path(u"subdir1/mydata567")) def _got_filenode(filenode): fnode = self.clients[1].create_node_from_uri(filenode.get_uri()) assert fnode == filenode @@ -925,7 +925,7 @@ class SystemTest(testutil.SignalMixin, testutil.PollMixin, unittest.TestCase): # this one uses the path-based API self._private_node = resnode - d = self._private_node.get_child_at_path("personal") + d = self._private_node.get_child_at_path(u"personal") def _got_personal(personal): self._personal_node = personal return personal @@ -936,12 +936,12 @@ class SystemTest(testutil.SignalMixin, testutil.PollMixin, unittest.TestCase): def get_path(path): return self._private_node.get_child_at_path(path) - d.addCallback(lambda res: get_path("personal/sekrit data")) + d.addCallback(lambda res: get_path(u"personal/sekrit data")) d.addCallback(lambda filenode: filenode.download_to_data()) d.addCallback(lambda data: self.failUnlessEqual(data, self.smalldata)) - d.addCallback(lambda res: get_path("s2-rw")) + d.addCallback(lambda res: get_path(u"s2-rw")) d.addCallback(lambda dirnode: self.failUnless(dirnode.is_mutable())) - d.addCallback(lambda res: get_path("s2-ro")) + d.addCallback(lambda res: get_path(u"s2-ro")) def _got_s2ro(dirnode): self.failUnless(dirnode.is_mutable(), dirnode) self.failUnless(dirnode.is_readonly(), dirnode) @@ -949,29 +949,29 @@ class SystemTest(testutil.SignalMixin, testutil.PollMixin, unittest.TestCase): d1.addCallback(lambda res: dirnode.list()) d1.addCallback(self.log, "dirnode.list") - d1.addCallback(lambda res: self.shouldFail2(NotMutableError, "mkdir(nope)", None, dirnode.create_empty_directory, "nope")) + d1.addCallback(lambda res: self.shouldFail2(NotMutableError, "mkdir(nope)", None, dirnode.create_empty_directory, u"nope")) d1.addCallback(self.log, "doing add_file(ro)") ut = upload.Data("I will disappear, unrecorded and unobserved. The tragedy of my demise is made more poignant by its silence, but this beauty is not for you to ever know.") - d1.addCallback(lambda res: self.shouldFail2(NotMutableError, "add_file(nope)", None, dirnode.add_file, "hope", ut)) + d1.addCallback(lambda res: self.shouldFail2(NotMutableError, "add_file(nope)", None, dirnode.add_file, u"hope", ut)) d1.addCallback(self.log, "doing get(ro)") - d1.addCallback(lambda res: dirnode.get("mydata992")) + d1.addCallback(lambda res: dirnode.get(u"mydata992")) d1.addCallback(lambda filenode: self.failUnless(IFileNode.providedBy(filenode))) d1.addCallback(self.log, "doing delete(ro)") - d1.addCallback(lambda res: self.shouldFail2(NotMutableError, "delete(nope)", None, dirnode.delete, "mydata992")) + d1.addCallback(lambda res: self.shouldFail2(NotMutableError, "delete(nope)", None, dirnode.delete, u"mydata992")) - d1.addCallback(lambda res: self.shouldFail2(NotMutableError, "set_uri(nope)", None, dirnode.set_uri, "hopeless", self.uri)) + d1.addCallback(lambda res: self.shouldFail2(NotMutableError, "set_uri(nope)", None, dirnode.set_uri, u"hopeless", self.uri)) - d1.addCallback(lambda res: self.shouldFail2(KeyError, "get(missing)", "'missing'", dirnode.get, "missing")) + d1.addCallback(lambda res: self.shouldFail2(KeyError, "get(missing)", "'missing'", dirnode.get, u"missing")) personal = self._personal_node - d1.addCallback(lambda res: self.shouldFail2(NotMutableError, "mv from readonly", None, dirnode.move_child_to, "mydata992", personal, "nope")) + d1.addCallback(lambda res: self.shouldFail2(NotMutableError, "mv from readonly", None, dirnode.move_child_to, u"mydata992", personal, u"nope")) d1.addCallback(self.log, "doing move_child_to(ro)2") - d1.addCallback(lambda res: self.shouldFail2(NotMutableError, "mv to readonly", None, personal.move_child_to, "sekrit data", dirnode, "nope")) + d1.addCallback(lambda res: self.shouldFail2(NotMutableError, "mv to readonly", None, personal.move_child_to, u"sekrit data", dirnode, u"nope")) d1.addCallback(self.log, "finished with _got_s2ro") return d1 @@ -982,15 +982,15 @@ class SystemTest(testutil.SignalMixin, testutil.PollMixin, unittest.TestCase): d1 = defer.succeed(None) d1.addCallback(self.log, "mv 'P/personal/sekrit data' to P/sekrit") d1.addCallback(lambda res: - personal.move_child_to("sekrit data",home,"sekrit")) + personal.move_child_to(u"sekrit data",home,u"sekrit")) d1.addCallback(self.log, "mv P/sekrit 'P/sekrit data'") d1.addCallback(lambda res: - home.move_child_to("sekrit", home, "sekrit data")) + home.move_child_to(u"sekrit", home, u"sekrit data")) d1.addCallback(self.log, "mv 'P/sekret data' P/personal/") d1.addCallback(lambda res: - home.move_child_to("sekrit data", personal)) + home.move_child_to(u"sekrit data", personal)) d1.addCallback(lambda res: home.build_manifest()) d1.addCallback(self.log, "manifest") @@ -1319,7 +1319,7 @@ class SystemTest(testutil.SignalMixin, testutil.PollMixin, unittest.TestCase): def _check_put((out,err)): self.failUnless("200 OK" in out) self.failUnlessEqual(err, "") - d = self._private_node.get_child_at_path("test_put/upload.txt") + d = self._private_node.get_child_at_path(u"test_put/upload.txt") d.addCallback(lambda filenode: filenode.download_to_data()) def _check_put2(res): self.failUnlessEqual(res, TESTDATA) @@ -1358,10 +1358,10 @@ class SystemTest(testutil.SignalMixin, testutil.PollMixin, unittest.TestCase): def _check_mv((out,err)): self.failUnless("OK" in out) self.failUnlessEqual(err, "") - d = self.shouldFail2(KeyError, "test_cli._check_rm", "'upload.txt'", self._private_node.get_child_at_path, "test_put/upload.txt") + d = self.shouldFail2(KeyError, "test_cli._check_rm", "'upload.txt'", self._private_node.get_child_at_path, u"test_put/upload.txt") d.addCallback(lambda res: - self._private_node.get_child_at_path("test_put/moved.txt")) + self._private_node.get_child_at_path(u"test_put/moved.txt")) d.addCallback(lambda filenode: filenode.download_to_data()) def _check_mv2(res): self.failUnlessEqual(res, TESTDATA) @@ -1376,7 +1376,7 @@ class SystemTest(testutil.SignalMixin, testutil.PollMixin, unittest.TestCase): def _check_rm((out,err)): self.failUnless("200 OK" in out) self.failUnlessEqual(err, "") - d = self.shouldFail2(KeyError, "test_cli._check_rm", "'moved.txt'", self._private_node.get_child_at_path, "test_put/moved.txt") + d = self.shouldFail2(KeyError, "test_cli._check_rm", "'moved.txt'", self._private_node.get_child_at_path, u"test_put/moved.txt") return d d.addCallback(_check_rm) return d @@ -1443,7 +1443,7 @@ class SystemTest(testutil.SignalMixin, testutil.PollMixin, unittest.TestCase): def _test_checker_3(self, res): # check one file, through FileNode.check() - d = self._private_node.get_child_at_path("personal/sekrit data") + d = self._private_node.get_child_at_path(u"personal/sekrit data") d.addCallback(lambda n: n.check()) def _checked(results): # 'sekrit data' is small, and fits in a LiteralFileNode, so @@ -1453,7 +1453,7 @@ class SystemTest(testutil.SignalMixin, testutil.PollMixin, unittest.TestCase): c0 = self.clients[1] n = c0.create_node_from_uri(self._root_directory_uri) - d.addCallback(lambda res: n.get_child_at_path("subdir1/mydata567")) + d.addCallback(lambda res: n.get_child_at_path(u"subdir1/mydata567")) d.addCallback(lambda n: n.check()) def _checked2(results): # mydata567 is large and lives in a CHK diff --git a/src/allmydata/test/test_web.py b/src/allmydata/test/test_web.py index 315e45e3..487b1bd3 100644 --- a/src/allmydata/test/test_web.py +++ b/src/allmydata/test/test_web.py @@ -97,29 +97,34 @@ class WebMixin(object): self._foo_readonly_uri = foo.get_readonly_uri() # NOTE: we ignore the deferred on all set_uri() calls, because we # know the fake nodes do these synchronously - self.public_root.set_uri("foo", foo.get_uri()) + self.public_root.set_uri(u"foo", foo.get_uri()) self.BAR_CONTENTS, n, self._bar_txt_uri = self.makefile(0) - foo.set_uri("bar.txt", self._bar_txt_uri) + foo.set_uri(u"bar.txt", self._bar_txt_uri) - foo.set_uri("empty", res[3][1].get_uri()) + foo.set_uri(u"empty", res[3][1].get_uri()) sub_uri = res[4][1].get_uri() - foo.set_uri("sub", sub_uri) + foo.set_uri(u"sub", sub_uri) sub = self.s.create_node_from_uri(sub_uri) _ign, n, blocking_uri = self.makefile(1) - foo.set_uri("blockingfile", blocking_uri) + foo.set_uri(u"blockingfile", blocking_uri) + + unicode_filename = u"n\u00fc.txt" # n u-umlaut . t x t + # ok, unicode calls it LATIN SMALL LETTER U WITH DIAERESIS but I + # still think of it as an umlaut + foo.set_uri(unicode_filename, self._bar_txt_uri) _ign, n, baz_file = self.makefile(2) - sub.set_uri("baz.txt", baz_file) + sub.set_uri(u"baz.txt", baz_file) _ign, n, self._bad_file_uri = self.makefile(3) # this uri should not be downloadable del FakeCHKFileNode.all_contents[self._bad_file_uri] rodir = res[5][1] - self.public_root.set_uri("reedownlee", rodir.get_readonly_uri()) - rodir.set_uri("nor", baz_file) + self.public_root.set_uri(u"reedownlee", rodir.get_readonly_uri()) + rodir.set_uri(u"nor", baz_file) # public/ # public/foo/ @@ -132,7 +137,7 @@ class WebMixin(object): # public/reedownlee/nor self.NEWFILE_CONTENTS = "newfile contents\n" - return foo.get_metadata_for("bar.txt") + return foo.get_metadata_for(u"bar.txt") d.addCallback(_then) def _got_metadata(metadata): self._bar_txt_metadata = metadata @@ -148,7 +153,7 @@ class WebMixin(object): return self.s.stopService() def failUnlessIsBarDotTxt(self, res): - self.failUnlessEqual(res, self.BAR_CONTENTS) + self.failUnlessEqual(res, self.BAR_CONTENTS, res) def failUnlessIsBarJSON(self, res): data = simplejson.loads(res) @@ -170,17 +175,20 @@ class WebMixin(object): kidnames = sorted(data[1]["children"]) self.failUnlessEqual(kidnames, - ["bar.txt", "blockingfile", "empty", "sub"]) + [u"bar.txt", u"blockingfile", u"empty", + u"n\u00fc.txt", u"sub"]) kids = data[1]["children"] - self.failUnlessEqual(kids["sub"][0], "dirnode") - self.failUnless("metadata" in kids["sub"][1]) - self.failUnless("ctime" in kids["sub"][1]["metadata"]) - self.failUnless("mtime" in kids["sub"][1]["metadata"]) - self.failUnlessEqual(kids["bar.txt"][0], "filenode") - self.failUnlessEqual(kids["bar.txt"][1]["size"], len(self.BAR_CONTENTS)) - self.failUnlessEqual(kids["bar.txt"][1]["ro_uri"], self._bar_txt_uri) - self.failUnlessEqual(kids["bar.txt"][1]["metadata"]["ctime"], + self.failUnlessEqual(kids[u"sub"][0], "dirnode") + self.failUnless("metadata" in kids[u"sub"][1]) + self.failUnless("ctime" in kids[u"sub"][1]["metadata"]) + self.failUnless("mtime" in kids[u"sub"][1]["metadata"]) + self.failUnlessEqual(kids[u"bar.txt"][0], "filenode") + self.failUnlessEqual(kids[u"bar.txt"][1]["size"], len(self.BAR_CONTENTS)) + self.failUnlessEqual(kids[u"bar.txt"][1]["ro_uri"], self._bar_txt_uri) + self.failUnlessEqual(kids[u"bar.txt"][1]["metadata"]["ctime"], self._bar_txt_metadata["ctime"]) + self.failUnlessEqual(kids[u"n\u00fc.txt"][1]["ro_uri"], + self._bar_txt_uri) def GET(self, urlpath, followRedirect=False): url = self.webish_url + urlpath @@ -208,7 +216,7 @@ class WebMixin(object): if isinstance(value, tuple): filename, value = value form.append('Content-Disposition: form-data; name="%s"; ' - 'filename="%s"' % (name, filename)) + 'filename="%s"' % (name, filename.encode("utf-8"))) else: form.append('Content-Disposition: form-data; name="%s"' % name) form.append('') @@ -399,9 +407,9 @@ class Web(WebMixin, unittest.TestCase): d = self.PUT(self.public_url + "/foo/new.txt", self.NEWFILE_CONTENTS) # TODO: we lose the response code, so we can't check this #self.failUnlessEqual(responsecode, 201) - d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, "new.txt") + d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"new.txt") d.addCallback(lambda res: - self.failUnlessChildContentsAre(self._foo_node, "new.txt", + self.failUnlessChildContentsAre(self._foo_node, u"new.txt", self.NEWFILE_CONTENTS)) return d @@ -409,9 +417,9 @@ class Web(WebMixin, unittest.TestCase): d = self.PUT(self.public_url + "/foo/bar.txt", self.NEWFILE_CONTENTS) # TODO: we lose the response code, so we can't check this #self.failUnlessEqual(responsecode, 200) - d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, "bar.txt") + d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"bar.txt") d.addCallback(lambda res: - self.failUnlessChildContentsAre(self._foo_node, "bar.txt", + self.failUnlessChildContentsAre(self._foo_node, u"bar.txt", self.NEWFILE_CONTENTS)) return d @@ -427,11 +435,11 @@ class Web(WebMixin, unittest.TestCase): def test_PUT_NEWFILEURL_mkdirs(self): d = self.PUT(self.public_url + "/foo/newdir/new.txt", self.NEWFILE_CONTENTS) fn = self._foo_node - d.addCallback(self.failUnlessURIMatchesChild, fn, "newdir/new.txt") - d.addCallback(lambda res: self.failIfNodeHasChild(fn, "new.txt")) - d.addCallback(lambda res: self.failUnlessNodeHasChild(fn, "newdir")) + d.addCallback(self.failUnlessURIMatchesChild, fn, u"newdir/new.txt") + d.addCallback(lambda res: self.failIfNodeHasChild(fn, u"new.txt")) + d.addCallback(lambda res: self.failUnlessNodeHasChild(fn, u"newdir")) d.addCallback(lambda res: - self.failUnlessChildContentsAre(fn, "newdir/new.txt", + self.failUnlessChildContentsAre(fn, u"newdir/new.txt", self.NEWFILE_CONTENTS)) return d @@ -446,7 +454,7 @@ class Web(WebMixin, unittest.TestCase): def test_DELETE_FILEURL(self): d = self.DELETE(self.public_url + "/foo/bar.txt") d.addCallback(lambda res: - self.failIfNodeHasChild(self._foo_node, "bar.txt")) + self.failIfNodeHasChild(self._foo_node, u"bar.txt")) return d def test_DELETE_FILEURL_missing(self): @@ -546,9 +554,9 @@ class Web(WebMixin, unittest.TestCase): f.write(self.NEWFILE_CONTENTS) f.close() d = self.PUT(url, "") - d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, "new.txt") + d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"new.txt") d.addCallback(lambda res: - self.failUnlessChildContentsAre(self._foo_node, "new.txt", + self.failUnlessChildContentsAre(self._foo_node, u"new.txt", self.NEWFILE_CONTENTS)) return d @@ -584,14 +592,14 @@ class Web(WebMixin, unittest.TestCase): d = self.PUT(self.public_url + "/foo/newdir/new.txt?t=upload&localfile=%s" % urllib.quote(localfile), "") d.addCallback(self.failUnlessURIMatchesChild, - self._foo_node, "newdir/new.txt") + self._foo_node, u"newdir/new.txt") d.addCallback(lambda res: - self.failIfNodeHasChild(self._foo_node, "new.txt")) + self.failIfNodeHasChild(self._foo_node, u"new.txt")) d.addCallback(lambda res: - self.failUnlessNodeHasChild(self._foo_node, "newdir")) + self.failUnlessNodeHasChild(self._foo_node, u"newdir")) d.addCallback(lambda res: self.failUnlessChildContentsAre(self._foo_node, - "newdir/new.txt", + u"newdir/new.txt", self.NEWFILE_CONTENTS)) return d @@ -691,16 +699,16 @@ class Web(WebMixin, unittest.TestCase): def test_PUT_NEWDIRURL(self): d = self.PUT(self.public_url + "/foo/newdir?t=mkdir", "") d.addCallback(lambda res: - self.failUnlessNodeHasChild(self._foo_node, "newdir")) - d.addCallback(lambda res: self._foo_node.get("newdir")) + 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_PUT_NEWDIRURL_replace(self): d = self.PUT(self.public_url + "/foo/sub?t=mkdir", "") d.addCallback(lambda res: - self.failUnlessNodeHasChild(self._foo_node, "sub")) - d.addCallback(lambda res: self._foo_node.get("sub")) + self.failUnlessNodeHasChild(self._foo_node, u"sub")) + d.addCallback(lambda res: self._foo_node.get(u"sub")) d.addCallback(self.failUnlessNodeKeysAre, []) return d @@ -711,33 +719,33 @@ class Web(WebMixin, unittest.TestCase): "There was already a child by that name, and you asked me " "to not replace it") d.addCallback(lambda res: - self.failUnlessNodeHasChild(self._foo_node, "sub")) - d.addCallback(lambda res: self._foo_node.get("sub")) - d.addCallback(self.failUnlessNodeKeysAre, ["baz.txt"]) + self.failUnlessNodeHasChild(self._foo_node, u"sub")) + d.addCallback(lambda res: self._foo_node.get(u"sub")) + d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"]) return d def test_PUT_NEWDIRURL_mkdirs(self): d = self.PUT(self.public_url + "/foo/subdir/newdir?t=mkdir", "") d.addCallback(lambda res: - self.failIfNodeHasChild(self._foo_node, "newdir")) + self.failIfNodeHasChild(self._foo_node, u"newdir")) d.addCallback(lambda res: - self.failUnlessNodeHasChild(self._foo_node, "subdir")) + self.failUnlessNodeHasChild(self._foo_node, u"subdir")) d.addCallback(lambda res: - self._foo_node.get_child_at_path("subdir/newdir")) + self._foo_node.get_child_at_path(u"subdir/newdir")) d.addCallback(self.failUnlessNodeKeysAre, []) return d def test_DELETE_DIRURL(self): d = self.DELETE(self.public_url + "/foo") d.addCallback(lambda res: - self.failIfNodeHasChild(self.public_root, "foo")) + self.failIfNodeHasChild(self.public_root, u"foo")) return d def test_DELETE_DIRURL_missing(self): d = self.DELETE(self.public_url + "/foo/missing") d.addBoth(self.should404, "test_DELETE_DIRURL_missing") d.addCallback(lambda res: - self.failUnlessNodeHasChild(self.public_root, "foo")) + self.failUnlessNodeHasChild(self.public_root, u"foo")) return d def test_DELETE_DIRURL_missing2(self): @@ -755,20 +763,22 @@ class Web(WebMixin, unittest.TestCase): def _check(res): names = [path for (path,node) in out] self.failUnlessEqual(sorted(names), - [('foo',), - ('foo','bar.txt'), - ('foo','blockingfile'), - ('foo', 'empty'), - ('foo', 'sub'), - ('foo','sub','baz.txt'), - ('reedownlee',), - ('reedownlee', 'nor'), + [(u'foo',), + (u'foo',u'bar.txt'), + (u'foo',u'blockingfile'), + (u'foo', u'empty'), + (u'foo', u"n\u00fc.txt"), + (u'foo', u'sub'), + (u'foo',u'sub',u'baz.txt'), + (u'reedownlee',), + (u'reedownlee', u'nor'), ]) - subindex = names.index( ('foo', 'sub') ) - bazindex = names.index( ('foo', 'sub', 'baz.txt') ) + subindex = names.index( (u'foo', u'sub') ) + bazindex = names.index( (u'foo', u'sub', u'baz.txt') ) self.failUnless(subindex < bazindex) for path,node in out: - if path[-1] in ('bar.txt', 'blockingfile', 'baz.txt', 'nor'): + if path[-1] in (u'bar.txt', u"n\u00fc.txt", u'blockingfile', + u'baz.txt', u'nor'): self.failUnless(interfaces.IFileNode.providedBy(node)) else: self.failUnless(interfaces.IDirectoryNode.providedBy(node)) @@ -839,18 +849,22 @@ class Web(WebMixin, unittest.TestCase): return d def failUnlessNodeKeysAre(self, node, expected_keys): + for k in expected_keys: + assert isinstance(k, unicode) d = node.list() def _check(children): self.failUnlessEqual(sorted(children.keys()), sorted(expected_keys)) d.addCallback(_check) return d def failUnlessNodeHasChild(self, node, name): + assert isinstance(name, unicode) d = node.list() def _check(children): self.failUnless(name in children) d.addCallback(_check) return d def failIfNodeHasChild(self, node, name): + assert isinstance(name, unicode) d = node.list() def _check(children): self.failIf(name in children) @@ -858,6 +872,7 @@ class Web(WebMixin, unittest.TestCase): return d def failUnlessChildContentsAre(self, node, name, expected_contents): + assert isinstance(name, unicode) d = node.get_child_at_path(name) d.addCallback(lambda node: node.download_to_data()) def _check(contents): @@ -866,6 +881,7 @@ class Web(WebMixin, unittest.TestCase): return d def failUnlessChildURIIs(self, node, name, expected_uri): + assert isinstance(name, unicode) d = node.get_child_at_path(name) def _check(child): self.failUnlessEqual(child.get_uri(), expected_uri.strip()) @@ -873,6 +889,7 @@ class Web(WebMixin, unittest.TestCase): return d def failUnlessURIMatchesChild(self, got_uri, node, name): + assert isinstance(name, unicode) d = node.get_child_at_path(name) def _check(child): self.failUnlessEqual(got_uri.strip(), child.get_uri()) @@ -896,15 +913,15 @@ class Web(WebMixin, unittest.TestCase): d = self.PUT(self.public_url + "/newdir?t=upload&localdir=%s" % urllib.quote(localdir), "") pr = self.public_root - d.addCallback(lambda res: self.failUnlessNodeHasChild(pr, "newdir")) - d.addCallback(lambda res: pr.get("newdir")) + d.addCallback(lambda res: self.failUnlessNodeHasChild(pr, u"newdir")) + d.addCallback(lambda res: pr.get(u"newdir")) d.addCallback(self.failUnlessNodeKeysAre, - ["one", "two", "three", "zap.zip"]) - d.addCallback(lambda res: pr.get_child_at_path("newdir/one")) - d.addCallback(self.failUnlessNodeKeysAre, ["sub"]) - d.addCallback(lambda res: pr.get_child_at_path("newdir/three")) - d.addCallback(self.failUnlessNodeKeysAre, ["foo.txt", "bar.txt"]) - d.addCallback(lambda res: pr.get_child_at_path("newdir/three/bar.txt")) + [u"one", u"two", u"three", u"zap.zip"]) + d.addCallback(lambda res: pr.get_child_at_path(u"newdir/one")) + d.addCallback(self.failUnlessNodeKeysAre, [u"sub"]) + d.addCallback(lambda res: pr.get_child_at_path(u"newdir/three")) + d.addCallback(self.failUnlessNodeKeysAre, [u"foo.txt", u"bar.txt"]) + d.addCallback(lambda res: pr.get_child_at_path(u"newdir/three/bar.txt")) d.addCallback(lambda barnode: barnode.download_to_data()) d.addCallback(lambda contents: self.failUnlessEqual(contents, @@ -945,16 +962,16 @@ class Web(WebMixin, unittest.TestCase): % urllib.quote(localdir), "") fn = self._foo_node - d.addCallback(lambda res: self.failUnlessNodeHasChild(fn, "subdir")) - d.addCallback(lambda res: fn.get_child_at_path("subdir/newdir")) + d.addCallback(lambda res: self.failUnlessNodeHasChild(fn, u"subdir")) + d.addCallback(lambda res: fn.get_child_at_path(u"subdir/newdir")) d.addCallback(self.failUnlessNodeKeysAre, - ["one", "two", "three", "zap.zip"]) - d.addCallback(lambda res: fn.get_child_at_path("subdir/newdir/one")) - d.addCallback(self.failUnlessNodeKeysAre, ["sub"]) - d.addCallback(lambda res: fn.get_child_at_path("subdir/newdir/three")) - d.addCallback(self.failUnlessNodeKeysAre, ["foo.txt", "bar.txt"]) + [u"one", u"two", u"three", u"zap.zip"]) + d.addCallback(lambda res: fn.get_child_at_path(u"subdir/newdir/one")) + d.addCallback(self.failUnlessNodeKeysAre, [u"sub"]) + d.addCallback(lambda res: fn.get_child_at_path(u"subdir/newdir/three")) + d.addCallback(self.failUnlessNodeKeysAre, [u"foo.txt", u"bar.txt"]) d.addCallback(lambda res: - fn.get_child_at_path("subdir/newdir/three/bar.txt")) + fn.get_child_at_path(u"subdir/newdir/three/bar.txt")) d.addCallback(lambda barnode: barnode.download_to_data()) d.addCallback(lambda contents: self.failUnlessEqual(contents, @@ -976,10 +993,26 @@ class Web(WebMixin, unittest.TestCase): d = self.POST(self.public_url + "/foo", t="upload", file=("new.txt", self.NEWFILE_CONTENTS)) fn = self._foo_node - d.addCallback(self.failUnlessURIMatchesChild, fn, "new.txt") + d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt") + d.addCallback(lambda res: + self.failUnlessChildContentsAre(fn, u"new.txt", + self.NEWFILE_CONTENTS)) + return d + + def test_POST_upload_unicode(self): + filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t + target_url = self.public_url + "/foo/" + filename.encode("utf-8") + d = self.POST(self.public_url + "/foo", t="upload", + file=(filename, self.NEWFILE_CONTENTS)) + fn = self._foo_node + d.addCallback(self.failUnlessURIMatchesChild, fn, filename) d.addCallback(lambda res: - self.failUnlessChildContentsAre(fn, "new.txt", + self.failUnlessChildContentsAre(fn, filename, self.NEWFILE_CONTENTS)) + d.addCallback(lambda res: self.GET(target_url)) + d.addCallback(lambda contents: self.failUnlessEqual(contents, + self.NEWFILE_CONTENTS, + contents)) return d def test_POST_upload_no_link(self): @@ -1026,11 +1059,11 @@ class Web(WebMixin, unittest.TestCase): d = self.POST(self.public_url + "/foo", t="upload", mutable="true", file=("new.txt", self.NEWFILE_CONTENTS)) fn = self._foo_node - d.addCallback(self.failUnlessURIMatchesChild, fn, "new.txt") + d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt") d.addCallback(lambda res: - self.failUnlessChildContentsAre(fn, "new.txt", + self.failUnlessChildContentsAre(fn, u"new.txt", self.NEWFILE_CONTENTS)) - d.addCallback(lambda res: self._foo_node.get("new.txt")) + d.addCallback(lambda res: self._foo_node.get(u"new.txt")) def _got(newnode): self.failUnless(IMutableFileNode.providedBy(newnode)) self.failUnless(newnode.is_mutable()) @@ -1044,11 +1077,11 @@ class Web(WebMixin, unittest.TestCase): self.POST(self.public_url + "/foo", t="upload", mutable="true", file=("new.txt", NEWER_CONTENTS))) - d.addCallback(self.failUnlessURIMatchesChild, fn, "new.txt") + d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt") d.addCallback(lambda res: - self.failUnlessChildContentsAre(fn, "new.txt", + self.failUnlessChildContentsAre(fn, u"new.txt", NEWER_CONTENTS)) - d.addCallback(lambda res: self._foo_node.get("new.txt")) + d.addCallback(lambda res: self._foo_node.get(u"new.txt")) def _got2(newnode): self.failUnless(IMutableFileNode.providedBy(newnode)) self.failUnless(newnode.is_mutable()) @@ -1085,9 +1118,9 @@ class Web(WebMixin, unittest.TestCase): d.addCallback(_parse_overwrite_form_and_submit) d.addBoth(self.shouldRedirect, urllib.quote(self.public_url + "/foo/")) d.addCallback(lambda res: - self.failUnlessChildContentsAre(fn, "new.txt", + self.failUnlessChildContentsAre(fn, u"new.txt", EVEN_NEWER_CONTENTS)) - d.addCallback(lambda res: self._foo_node.get("new.txt")) + d.addCallback(lambda res: self._foo_node.get(u"new.txt")) def _got3(newnode): self.failUnless(IMutableFileNode.providedBy(newnode)) self.failUnless(newnode.is_mutable()) @@ -1101,9 +1134,9 @@ class Web(WebMixin, unittest.TestCase): d = self.POST(self.public_url + "/foo", t="upload", file=("bar.txt", self.NEWFILE_CONTENTS)) fn = self._foo_node - d.addCallback(self.failUnlessURIMatchesChild, fn, "bar.txt") + d.addCallback(self.failUnlessURIMatchesChild, fn, u"bar.txt") d.addCallback(lambda res: - self.failUnlessChildContentsAre(fn, "bar.txt", + self.failUnlessChildContentsAre(fn, u"bar.txt", self.NEWFILE_CONTENTS)) return d @@ -1144,7 +1177,7 @@ class Web(WebMixin, unittest.TestCase): d.addBoth(self.shouldRedirect, "/THERE") fn = self._foo_node d.addCallback(lambda res: - self.failUnlessChildContentsAre(fn, "new.txt", + self.failUnlessChildContentsAre(fn, u"new.txt", self.NEWFILE_CONTENTS)) return d @@ -1152,9 +1185,9 @@ class Web(WebMixin, unittest.TestCase): fn = self._foo_node d = self.POST(self.public_url + "/foo", t="upload", name="new.txt", file=self.NEWFILE_CONTENTS) - d.addCallback(self.failUnlessURIMatchesChild, fn, "new.txt") + d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt") d.addCallback(lambda res: - self.failUnlessChildContentsAre(fn, "new.txt", + self.failUnlessChildContentsAre(fn, u"new.txt", self.NEWFILE_CONTENTS)) return d @@ -1169,13 +1202,14 @@ class Web(WebMixin, unittest.TestCase): # make sure that nothing was added d.addCallback(lambda res: self.failUnlessNodeKeysAre(self._foo_node, - ["bar.txt", "blockingfile", - "empty", "sub"])) + [u"bar.txt", u"blockingfile", + u"empty", u"n\u00fc.txt", + u"sub"])) return d def test_POST_mkdir(self): # return value? d = self.POST(self.public_url + "/foo", t="mkdir", name="newdir") - d.addCallback(lambda res: self._foo_node.get("newdir")) + d.addCallback(lambda res: self._foo_node.get(u"newdir")) d.addCallback(self.failUnlessNodeKeysAre, []) return d @@ -1223,7 +1257,7 @@ class Web(WebMixin, unittest.TestCase): def test_POST_mkdir_replace(self): # return value? d = self.POST(self.public_url + "/foo", t="mkdir", name="sub") - d.addCallback(lambda res: self._foo_node.get("sub")) + d.addCallback(lambda res: self._foo_node.get(u"sub")) d.addCallback(self.failUnlessNodeKeysAre, []) return d @@ -1234,8 +1268,8 @@ class Web(WebMixin, unittest.TestCase): "409 Conflict", "There was already a child by that name, and you asked me " "to not replace it") - d.addCallback(lambda res: self._foo_node.get("sub")) - d.addCallback(self.failUnlessNodeKeysAre, ["baz.txt"]) + d.addCallback(lambda res: self._foo_node.get(u"sub")) + d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"]) return d def test_POST_mkdir_no_replace_field(self): # return value? @@ -1245,15 +1279,15 @@ class Web(WebMixin, unittest.TestCase): "409 Conflict", "There was already a child by that name, and you asked me " "to not replace it") - d.addCallback(lambda res: self._foo_node.get("sub")) - d.addCallback(self.failUnlessNodeKeysAre, ["baz.txt"]) + d.addCallback(lambda res: self._foo_node.get(u"sub")) + d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"]) return d def test_POST_mkdir_whendone_field(self): d = self.POST(self.public_url + "/foo", t="mkdir", name="newdir", when_done="/THERE") d.addBoth(self.shouldRedirect, "/THERE") - d.addCallback(lambda res: self._foo_node.get("newdir")) + d.addCallback(lambda res: self._foo_node.get(u"newdir")) d.addCallback(self.failUnlessNodeKeysAre, []) return d @@ -1261,25 +1295,25 @@ class Web(WebMixin, unittest.TestCase): d = self.POST(self.public_url + "/foo?when_done=/THERE", t="mkdir", name="newdir") d.addBoth(self.shouldRedirect, "/THERE") - d.addCallback(lambda res: self._foo_node.get("newdir")) + d.addCallback(lambda res: self._foo_node.get(u"newdir")) d.addCallback(self.failUnlessNodeKeysAre, []) return d def test_POST_put_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.failUnlessURIMatchesChild, self._foo_node, "new.txt") + d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"new.txt") d.addCallback(lambda res: - self.failUnlessChildContentsAre(self._foo_node, "new.txt", + self.failUnlessChildContentsAre(self._foo_node, u"new.txt", contents)) return d def test_POST_put_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.failUnlessURIMatchesChild, self._foo_node, "bar.txt") + d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"bar.txt") d.addCallback(lambda res: - self.failUnlessChildContentsAre(self._foo_node, "bar.txt", + self.failUnlessChildContentsAre(self._foo_node, u"bar.txt", contents)) return d @@ -1313,7 +1347,7 @@ class Web(WebMixin, unittest.TestCase): d = self.POST(self.public_url + "/foo", t="delete", name="bar.txt") d.addCallback(lambda res: self._foo_node.list()) def _check(children): - self.failIf("bar.txt" in children) + self.failIf(u"bar.txt" in children) d.addCallback(_check) return d @@ -1321,9 +1355,9 @@ class Web(WebMixin, unittest.TestCase): d = self.POST(self.public_url + "/foo", t="rename", from_name="bar.txt", to_name='wibble.txt') d.addCallback(lambda res: - self.failIfNodeHasChild(self._foo_node, "bar.txt")) + self.failIfNodeHasChild(self._foo_node, u"bar.txt")) d.addCallback(lambda res: - self.failUnlessNodeHasChild(self._foo_node, "wibble.txt")) + self.failUnlessNodeHasChild(self._foo_node, u"wibble.txt")) d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt")) d.addCallback(self.failUnlessIsBarDotTxt) d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt?t=json")) @@ -1335,9 +1369,9 @@ class Web(WebMixin, unittest.TestCase): d = self.POST(self.public_url + "/foo", t="rename", from_name="bar.txt", to_name='empty') d.addCallback(lambda res: - self.failIfNodeHasChild(self._foo_node, "bar.txt")) + self.failIfNodeHasChild(self._foo_node, u"bar.txt")) d.addCallback(lambda res: - self.failUnlessNodeHasChild(self._foo_node, "empty")) + self.failUnlessNodeHasChild(self._foo_node, u"empty")) d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty")) d.addCallback(self.failUnlessIsBarDotTxt) d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json")) @@ -1384,7 +1418,7 @@ class Web(WebMixin, unittest.TestCase): "to_name= may not contain a slash", ) d.addCallback(lambda res: - self.failUnlessNodeHasChild(self._foo_node, "bar.txt")) + self.failUnlessNodeHasChild(self._foo_node, u"bar.txt")) d.addCallback(lambda res: self.POST(self.public_url, t="rename", from_name="foo/bar.txt", to_name='george.txt')) d.addBoth(self.shouldFail, error.Error, @@ -1393,11 +1427,11 @@ class Web(WebMixin, unittest.TestCase): "from_name= may not contain a slash", ) d.addCallback(lambda res: - self.failUnlessNodeHasChild(self.public_root, "foo")) + self.failUnlessNodeHasChild(self.public_root, u"foo")) d.addCallback(lambda res: - self.failIfNodeHasChild(self.public_root, "george.txt")) + self.failIfNodeHasChild(self.public_root, u"george.txt")) d.addCallback(lambda res: - self.failUnlessNodeHasChild(self._foo_node, "bar.txt")) + self.failUnlessNodeHasChild(self._foo_node, u"bar.txt")) d.addCallback(lambda res: self.GET(self.public_url + "/foo?t=json")) d.addCallback(self.failUnlessIsFooJSON) return d @@ -1406,9 +1440,9 @@ class Web(WebMixin, unittest.TestCase): d = self.POST(self.public_url, t="rename", from_name="foo", to_name='plunk') d.addCallback(lambda res: - self.failIfNodeHasChild(self.public_root, "foo")) + self.failIfNodeHasChild(self.public_root, u"foo")) d.addCallback(lambda res: - self.failUnlessNodeHasChild(self.public_root, "plunk")) + self.failUnlessNodeHasChild(self.public_root, u"plunk")) d.addCallback(lambda res: self.GET(self.public_url + "/plunk?t=json")) d.addCallback(self.failUnlessIsFooJSON) return d @@ -1497,7 +1531,7 @@ class Web(WebMixin, unittest.TestCase): d = self.PUT(self.public_url + "/foo/new.txt?t=uri", new_uri) d.addCallback(lambda res: self.failUnlessEqual(res.strip(), new_uri)) d.addCallback(lambda res: - self.failUnlessChildContentsAre(self._foo_node, "new.txt", + self.failUnlessChildContentsAre(self._foo_node, u"new.txt", contents)) return d @@ -1506,7 +1540,7 @@ class Web(WebMixin, unittest.TestCase): d = self.PUT(self.public_url + "/foo/bar.txt?t=uri", new_uri) d.addCallback(lambda res: self.failUnlessEqual(res.strip(), new_uri)) d.addCallback(lambda res: - self.failUnlessChildContentsAre(self._foo_node, "bar.txt", + self.failUnlessChildContentsAre(self._foo_node, u"bar.txt", contents)) return d diff --git a/src/allmydata/webish.py b/src/allmydata/webish.py index aa0b5893..fdd10577 100644 --- a/src/allmydata/webish.py +++ b/src/allmydata/webish.py @@ -233,6 +233,8 @@ class Directory(rend.Page): def render_row(self, ctx, data): name, (target, metadata) = data + name = name.encode("utf-8") + assert not isinstance(name, unicode) if self._dirnode.is_readonly(): delete = "-" @@ -480,9 +482,10 @@ class WebDownloadTarget: if self._save_to_file is not None: # tell the browser to save the file rather display it # TODO: quote save_to_file properly + filename = self._save_to_file.encode("utf-8") self._req.setHeader("content-disposition", 'attachment; filename="%s"' - % self._save_to_file) + % filename) def write(self, data): self._req.write(data) @@ -526,16 +529,16 @@ class FileDownloader(resource.Resource): def render(self, req): gte = static.getTypeAndEncoding - type, encoding = gte(self._name, - static.File.contentTypes, - static.File.contentEncodings, - defaultType="text/plain") + ctype, encoding = gte(self._name, + static.File.contentTypes, + static.File.contentEncodings, + defaultType="text/plain") save_to_file = None if get_arg(req, "save", False): # TODO: make the API specification clear: should "save=" or # "save=false" count? save_to_file = self._name - wdt = WebDownloadTarget(req, type, encoding, save_to_file) + wdt = WebDownloadTarget(req, ctype, encoding, save_to_file) d = self._filenode.download(wdt) # exceptions during download are handled by the WebDownloadTarget d.addErrback(lambda why: None) @@ -683,6 +686,7 @@ class LocalDirectoryDownloader(resource.Resource, DirnodeWalkerMixin): self._localdir = localdir def _handle(self, path, node, metadata): + path = tuple([p.encode("utf-8") for p in path]) localfile = os.path.join(self._localdir, os.sep.join(path)) if IDirectoryNode.providedBy(node): fileutil.make_dirs(localfile) @@ -807,6 +811,8 @@ class POSTHandler(rend.Page): t = get_arg(req, "t") assert t is not None + charset = get_arg(req, "_charset", "utf-8") + name = get_arg(req, "name", None) if name and "/" in name: req.setResponseCode(http.BAD_REQUEST) @@ -814,6 +820,8 @@ class POSTHandler(rend.Page): return "name= may not contain a slash" if name is not None: name = name.strip() + name = name.decode(charset) + assert isinstance(name, unicode) # we allow the user to delete an empty-named file, but not to create # them, since that's an easy and confusing mistake to make @@ -849,12 +857,16 @@ class POSTHandler(rend.Page): d = self._node.delete(name) d.addCallback(lambda res: "thing deleted") elif t == "rename": - from_name = 'from_name' in req.fields and req.fields["from_name"].value + from_name = get_arg(req, "from_name") if from_name is not None: from_name = from_name.strip() - to_name = 'to_name' in req.fields and req.fields["to_name"].value + from_name = from_name.decode(charset) + assert isinstance(from_name, unicode) + to_name = get_arg(req, "to_name") if to_name is not None: to_name = to_name.strip() + to_name = to_name.decode(charset) + assert isinstance(to_name, unicode) if not from_name or not to_name: raise RuntimeError("rename requires from_name and to_name") if not IDirectoryNode.providedBy(self._node): @@ -877,13 +889,17 @@ class POSTHandler(rend.Page): d.addCallback(lambda res: "thing renamed") elif t == "upload": + contents = req.fields["file"] + name = name or contents.filename + if name is not None: + name = name.strip() + if not name: + # this prohibts empty, missing, and all-whitespace filenames + raise RuntimeError("upload requires a name") + name = name.decode(charset) + assert isinstance(name, unicode) + if "mutable" in req.fields: - contents = req.fields["file"] - name = name or contents.filename - if name is not None: - name = name.strip() - if not name: - raise RuntimeError("upload-mutable requires a name") # SDMF: files are small, and we can only upload data. contents.file.seek(0) data = contents.file.read() @@ -910,12 +926,6 @@ class POSTHandler(rend.Page): return d2 d.addCallback(_checked) else: - contents = req.fields["file"] - name = name or contents.filename - if name is not None: - name = name.strip() - if not name: - raise RuntimeError("upload requires a name") uploadable = FileHandle(contents.file) d = self._check_replacement(name) d.addCallback(lambda res: self._node.add_file(name, uploadable)) @@ -974,13 +984,13 @@ class DELETEHandler(rend.Page): d = self._node.delete(self._name) def _done(res): # what should this return?? - return "%s deleted" % self._name + return "%s deleted" % self._name.encode("utf-8") d.addCallback(_done) def _trap_missing(f): f.trap(KeyError) req.setResponseCode(http.NOT_FOUND) req.setHeader("content-type", "text/plain") - return "no such child %s" % self._name + return "no such child %s" % self._name.encode("utf-8") d.addErrback(_trap_missing) return d @@ -1098,7 +1108,9 @@ class PUTHandler(rend.Page): return d def _upload_localdir(self, node, localdir): - # build up a list of files to upload + # build up a list of files to upload. TODO: for now, these files and + # directories must have UTF-8 encoded filenames: anything else will + # cause the upload to break. all_files = [] all_dirs = [] msg = "No files to upload! %s is empty" % localdir @@ -1112,9 +1124,13 @@ class PUTHandler(rend.Page): relative_root = root[len(localdir)+1:] path = tuple(relative_root.split(os.sep)) for d in dirs: - all_dirs.append(path + (d,)) + this_dir = path + (d,) + this_dir = tuple([p.decode("utf-8") for p in this_dir]) + all_dirs.append(this_dir) for f in files: - all_files.append(path + (f,)) + this_file = path + (f,) + this_file = tuple([p.decode("utf-8") for p in this_file]) + all_files.append(this_file) d = defer.succeed(msg) for dir in all_dirs: if dir: @@ -1190,7 +1206,7 @@ class VDrive(rend.Page): def locateChild(self, ctx, segments): req = inevow.IRequest(ctx) method = req.method - path = segments + path = tuple([seg.decode("utf-8") for seg in segments]) t = get_arg(req, "t", "") localfile = get_arg(req, "localfile", None)