From 62e8528cc630bea2f0c64e9a9e9309f84fe65a0d Mon Sep 17 00:00:00 2001
From: Brian Warner <warner@lothar.com>
Date: Sat, 7 Jul 2007 22:06:52 -0700
Subject: [PATCH] web: /uri/ must escape slashes, we use bangs for this

---
 docs/webapi.txt                | 10 ++++++++++
 src/allmydata/test/test_web.py | 12 +++++++++---
 src/allmydata/uri.py           |  2 +-
 src/allmydata/webish.py        | 15 ++++++++++-----
 4 files changed, 30 insertions(+), 9 deletions(-)

diff --git a/docs/webapi.txt b/docs/webapi.txt
index dc445a17..b5f6355f 100644
--- a/docs/webapi.txt
+++ b/docs/webapi.txt
@@ -259,6 +259,12 @@ for files and directories which do not yet exist.
   would upload a file (with contents taken from the local filesystem) to a
   new file in a subdirectory of the referenced dirnode.
 
+  Note that since tahoe URIs may contain slashes (in particular, dirnode URIs
+  contain a FURL, which resembles a regular HTTP URL and starts with pb://),
+  when URIs are used in this form, they must be specially quoted. All slashes
+  in the URI must be replaced by '!' characters.
+
+
  PUT NEWFILEURL?t=uri
 
   This attaches a child (either a file or a directory) to the vdrive at the
@@ -277,6 +283,10 @@ for files and directories which do not yet exist.
   URI: unlike the GET /uri/$URI form, you cannot traverse to child nodes by
   appending additional path segments to the URL.
 
+  The $URI provided as a query argument is allowed to contain slashes. The
+  redirection provided will escape the slashes with exclamation points, as
+  described above.
+
 == XMLRPC ==
 
  http://localhost:8011/xmlrpc
diff --git a/src/allmydata/test/test_web.py b/src/allmydata/test/test_web.py
index f20e5e31..372094b0 100644
--- a/src/allmydata/test/test_web.py
+++ b/src/allmydata/test/test_web.py
@@ -69,7 +69,7 @@ class MyDirectoryNode(dirnode.MutableDirectoryNode):
         self._my_files = files
         self._my_client = client
         if uri is None:
-            uri = str(uri_counter.next())
+            uri = "URI:DIR:stuff/%s" % str(uri_counter.next())
         self._uri = str(uri)
         self._my_nodes[self._uri] = self
         self.children = {}
@@ -750,7 +750,7 @@ class Web(unittest.TestCase):
         return res
 
     def test_GET_URI_URL(self): # YES
-        base = "/uri/%s" % self._bar_txt_uri
+        base = "/uri/%s" % self._bar_txt_uri.replace("/","!")
         d = self.GET(base)
         d.addCallback(self.failUnlessIsBarDotTxt)
         d.addCallback(lambda res: self.GET(base+"?filename=bar.txt"))
@@ -760,11 +760,17 @@ class Web(unittest.TestCase):
         return d
 
     def test_GET_URI_URL_dir(self): # YES
-        base = "/uri/%s?t=json" % self._foo_uri
+        base = "/uri/%s?t=json" % self._foo_uri.replace("/","!")
         d = self.GET(base)
         d.addCallback(self.failUnlessIsFooJSON)
         return d
 
+    def test_GET_URI_URL_missing(self):
+        base = "/uri/missing?t=json"
+        d = self.GET(base)
+        d.addBoth(self.should404, "test_GET_URI_URL_missing")
+        return d
+
     def test_PUT_NEWFILEURL_uri(self): # YES
         new_uri = self.makefile(8)
         d = self.PUT("/vdrive/global/foo/new.txt?t=uri", new_uri)
diff --git a/src/allmydata/uri.py b/src/allmydata/uri.py
index 80ff5b3c..f50736d2 100644
--- a/src/allmydata/uri.py
+++ b/src/allmydata/uri.py
@@ -27,7 +27,7 @@ def pack_uri(storage_index, key, uri_extension_hash,
 
 
 def unpack_uri(uri):
-    assert uri.startswith("URI:")
+    assert uri.startswith("URI:"), uri
     d = {}
     (header,
      storage_index_s, key_s, uri_extension_hash_s,
diff --git a/src/allmydata/webish.py b/src/allmydata/webish.py
index 7be89801..19a62c5f 100644
--- a/src/allmydata/webish.py
+++ b/src/allmydata/webish.py
@@ -101,11 +101,12 @@ class Directory(rend.Page):
             delete = "-"
         ctx.fillSlots("delete", delete)
 
+        uri_link = urllib.quote(target.get_uri().replace("/", "!"))
         childdata = [T.a(href="%s?t=json" % name)["JSON"], ", ",
                      T.a(href="%s?t=xml" % name)["XML"], ", ",
                      T.a(href="%s?t=uri" % name)["URI"], ", ",
                      T.a(href="%s?t=readonly-uri" % name)["readonly-URI"], ", ",
-                     T.a(href="/uri/%s" % target.get_uri())["URI-link"],
+                     T.a(href="/uri/%s" % uri_link)["URI-link"],
                      ]
         ctx.fillSlots("data", childdata)
 
@@ -846,19 +847,23 @@ class Root(rend.Page):
             d.addCallback(lambda vd: vd.locateChild(ctx, segments[2:]))
             return d
         elif segments[0] == "uri":
-            if len(segments) == 1:
+            if len(segments) == 1 or segments[1] == '':
                 if "uri" in req.args:
-                    uri = req.args["uri"][0]
+                    uri = req.args["uri"][0].replace("/", "!")
                     there = url.URL.fromContext(ctx)
                     there = there.clear("uri")
                     there = there.child("uri").child(uri)
                     return there, ()
             if len(segments) < 2:
                 return rend.NotFound
-            uri = segments[1]
+            uri = segments[1].replace("!", "/")
             d = vdrive.get_node(uri)
-            d.addCallback(lambda node: VDrive(node, "<from-uri>"))
+            d.addCallback(lambda node: VDrive(node, "from-uri"))
             d.addCallback(lambda vd: vd.locateChild(ctx, segments[2:]))
+            def _trap_KeyError(f):
+                f.trap(KeyError)
+                return rend.FourOhFour(), ()
+            d.addErrback(_trap_KeyError)
             return d
         elif segments[0] == "xmlrpc":
             pass # TODO
-- 
2.45.2