]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/commitdiff
unicode handling: declare dirnodes to contain unicode child names, update webish...
authorBrian Warner <warner@allmydata.com>
Thu, 14 Feb 2008 22:45:56 +0000 (15:45 -0700)
committerBrian Warner <warner@allmydata.com>
Thu, 14 Feb 2008 22:45:56 +0000 (15:45 -0700)
docs/webapi.txt
src/allmydata/dirnode.py
src/allmydata/interfaces.py
src/allmydata/test/test_dirnode.py
src/allmydata/test/test_system.py
src/allmydata/test/test_web.py
src/allmydata/webish.py

index 5e77a740145d3bc8ce75ac94d62af46994454e28..deeddec14eb713bd01f2538624b51641aed3bac0 100644 (file)
@@ -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
index 72d44ca9402a768c872624903a7e6bd77474ac8d..fc6debc902b4765585d51be3e2bef51c8295c3da 100644 (file)
@@ -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)
index 986ed4db3fb770bd7ea70836d37da244981b994b..fe030101cefed2c362df5a658dde7f1f748abf6b 100644 (file)
@@ -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
index 2cca9a49093766b478edb62a589a9b20f57073ca..c97d8dd2171681e76e33ea79b5ab0d57b9094f88 100644 (file)
@@ -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
 
index aaa92d17cf92597d5270e6e622dce07ecde2b811..a16d289eac5c0b5d9a056e127244c6453ff06fc6 100644 (file)
@@ -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
index 315e45e308f5ce459dc3c006dd8651a154ee2e66..487b1bd3425f8a19a1528dc287225ffed3cc35e8 100644 (file)
@@ -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
 
index aa0b58937f99e66ab22f102014e3e39d3b3f0f27..fdd105778d3c160bb6e2106e55ee99767ac97fdf 100644 (file)
@@ -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)