]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blobdiff - src/allmydata/web/directory.py
src/allmydata/web/directory.py: fix HTML double-encoding issue for filenames.
[tahoe-lafs/tahoe-lafs.git] / src / allmydata / web / directory.py
index b04cbe758ee58174d512e460b615ed8463c0824c..33ca25991e3d2f767b4cf9d25539b25967405303 100644 (file)
@@ -6,7 +6,7 @@ from zope.interface import implements
 from twisted.internet import defer
 from twisted.internet.interfaces import IPushProducer
 from twisted.python.failure import Failure
-from twisted.web import http, html
+from twisted.web import http
 from nevow import url, rend, inevow, tags as T
 from nevow.inevow import IRequest
 
@@ -25,12 +25,12 @@ from allmydata.web.common import text_plain, WebError, \
      boolean_of_arg, get_arg, get_root, parse_replace_arg, \
      should_create_intermediate_directories, \
      getxmlfile, RenderMixin, humanize_failure, convert_children_json, \
-     parse_mutable_type_arg
+     get_format, get_mutable_type
 from allmydata.web.filenode import ReplaceMeMixin, \
      FileNodeHandler, PlaceHolderNodeHandler
-from allmydata.web.check_results import CheckResults, \
-     CheckAndRepairResults, DeepCheckResults, DeepCheckAndRepairResults, \
-     LiteralCheckResults
+from allmydata.web.check_results import CheckResultsRenderer, \
+     CheckAndRepairResultsRenderer, DeepCheckResultsRenderer, \
+     DeepCheckAndRepairResultsRenderer, LiteralCheckResultsRenderer
 from allmydata.web.info import MoreInfo
 from allmydata.web.operations import ReloadMixin
 from allmydata.web.check_results import json_check_results, \
@@ -107,17 +107,12 @@ class DirectoryNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
                         kids_json = req.content.read()
                         kids = convert_children_json(self.client.nodemaker,
                                                      kids_json)
+                    file_format = get_format(req, None)
                     mutable = True
+                    mt = get_mutable_type(file_format)
                     if t == "mkdir-immutable":
                         mutable = False
 
-                    mt = None
-                    if mutable:
-                        arg = get_arg(req, "mutable-type", None)
-                        mt = parse_mutable_type_arg(arg)
-                        if mt is "invalid":
-                            raise WebError("Unknown type: %s" % arg,
-                                           http.BAD_REQUEST)
                     d = self.node.create_subdirectory(name, kids,
                                                       mutable=mutable,
                                                       mutable_version=mt)
@@ -158,6 +153,15 @@ class DirectoryNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
         req = IRequest(ctx)
         # This is where all of the directory-related ?t=* code goes.
         t = get_arg(req, "t", "").strip()
+
+        # t=info contains variable ophandles, t=rename-form contains the name
+        # of the child being renamed. Neither is allowed an ETag.
+        FIXED_OUTPUT_TYPES =  ["", "json", "uri", "readonly-uri"]
+        if not self.node.is_mutable() and t in FIXED_OUTPUT_TYPES:
+            si = self.node.get_storage_index()
+            if si and req.setETag('DIR:%s-%s' % (base32.b2a(si), t or "")):
+                return ""
+
         if not t:
             # render the directory as HTML, using the docFactory and Nevow's
             # whole templating thing.
@@ -207,9 +211,6 @@ class DirectoryNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
             d = self._POST_mkdir_with_children(req)
         elif t == "mkdir-immutable":
             d = self._POST_mkdir_immutable(req)
-        elif t == "mkdir-p":
-            # TODO: docs, tests
-            d = self._POST_mkdir_p(req)
         elif t == "upload":
             d = self._POST_upload(ctx) # this one needs the context
         elif t == "uri":
@@ -218,6 +219,8 @@ class DirectoryNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
             d = self._POST_unlink(req)
         elif t == "rename":
             d = self._POST_rename(req)
+        elif t == "move":
+            d = self._POST_move(req)
         elif t == "check":
             d = self._POST_check(req)
         elif t == "start-deep-check":
@@ -251,15 +254,9 @@ class DirectoryNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
         name = name.decode("utf-8")
         replace = boolean_of_arg(get_arg(req, "replace", "true"))
         kids = {}
-        arg = get_arg(req, "mutable-type", None)
-        mt = parse_mutable_type_arg(arg)
-        if mt is not None and mt is not "invalid":
-            d = self.node.create_subdirectory(name, kids, overwrite=replace,
+        mt = get_mutable_type(get_format(req, None))
+        d = self.node.create_subdirectory(name, kids, overwrite=replace,
                                           mutable_version=mt)
-        elif mt is "invalid":
-            raise WebError("Unknown type: %s" % arg, http.BAD_REQUEST)
-        else:
-            d = self.node.create_subdirectory(name, kids, overwrite=replace)
         d.addCallback(lambda child: child.get_uri()) # TODO: urlencode
         return d
 
@@ -275,15 +272,9 @@ class DirectoryNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
         req.content.seek(0)
         kids_json = req.content.read()
         kids = convert_children_json(self.client.nodemaker, kids_json)
-        arg = get_arg(req, "mutable-type", None)
-        mt = parse_mutable_type_arg(arg)
-        if mt is not None and mt is not "invalid":
-            d = self.node.create_subdirectory(name, kids, overwrite=False,
-                                              mutable_version=mt)
-        elif mt is "invalid":
-            raise WebError("Unknown type: %s" % arg)
-        else:
-            d = self.node.create_subdirectory(name, kids, overwrite=False)
+        mt = get_mutable_type(get_format(req, None))
+        d = self.node.create_subdirectory(name, kids, overwrite=False,
+                                          mutable_version=mt)
         d.addCallback(lambda child: child.get_uri()) # TODO: urlencode
         return d
 
@@ -303,32 +294,6 @@ class DirectoryNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
         d.addCallback(lambda child: child.get_uri()) # TODO: urlencode
         return d
 
-    def _POST_mkdir_p(self, req):
-        path = get_arg(req, "path")
-        if not path:
-            raise WebError("mkdir-p requires a path")
-        path_ = tuple([seg.decode("utf-8") for seg in path.split('/') if seg ])
-        # TODO: replace
-        d = self._get_or_create_directories(self.node, path_)
-        d.addCallback(lambda node: node.get_uri())
-        return d
-
-    def _get_or_create_directories(self, node, path):
-        if not IDirectoryNode.providedBy(node):
-            # unfortunately it is too late to provide the name of the
-            # blocking directory in the error message.
-            raise BlockingFileError("cannot create directory because there "
-                                    "is a file in the way")
-        if not path:
-            return defer.succeed(node)
-        d = node.get(path[0])
-        def _maybe_create(f):
-            f.trap(NoSuchChildError)
-            return node.create_subdirectory(path[0])
-        d.addErrback(_maybe_create)
-        d.addCallback(self._get_or_create_directories, path[1:])
-        return d
-
     def _POST_upload(self, ctx):
         req = IRequest(ctx)
         charset = get_arg(req, "_charset", "utf-8")
@@ -435,10 +400,62 @@ class DirectoryNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
         d.addCallback(lambda res: "thing renamed")
         return d
 
+    def _POST_move(self, req):
+        charset = get_arg(req, "_charset", "utf-8")
+        from_name = get_arg(req, "from_name")
+        if from_name is not None:
+            from_name = from_name.strip()
+            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 to_name:
+            to_name = from_name
+        to_dir = get_arg(req, "to_dir")
+        if to_dir is not None:
+            to_dir = to_dir.strip()
+            to_dir = to_dir.decode(charset)
+            assert isinstance(to_dir, unicode)
+        if not from_name or not to_dir:
+            raise WebError("move requires from_name and to_dir")
+        replace = boolean_of_arg(get_arg(req, "replace", "true"))
+
+        # Disallow slashes in both from_name and to_name, that would only
+        # cause confusion. t=move is only for moving things from the
+        # *current* directory into a second directory named by to_dir=
+        if "/" in from_name:
+            raise WebError("from_name= may not contain a slash",
+                           http.BAD_REQUEST)
+        if "/" in to_name:
+            raise WebError("to_name= may not contain a slash",
+                           http.BAD_REQUEST)
+
+        target_type = get_arg(req, "target_type", "name")
+        if target_type == "name":
+            d = self.node.get_child_at_path(to_dir)
+        elif target_type == "uri":
+            d = defer.succeed(self.client.create_node_from_uri(str(to_dir)))
+        else:
+            raise WebError("invalid target_type parameter", http.BAD_REQUEST)
+
+        def is_target_node_usable(target_node):
+            if not IDirectoryNode.providedBy(target_node):
+                raise WebError("to_dir is not a directory", http.BAD_REQUEST)
+            return target_node
+        d.addCallback(is_target_node_usable)
+        d.addCallback(lambda new_parent:
+                      self.node.move_child_to(from_name, new_parent,
+                                              to_name, replace))
+        d.addCallback(lambda res: "thing moved")
+        return d
+
     def _maybe_literal(self, res, Results_Class):
         if res:
             return Results_Class(self.client, res)
-        return LiteralCheckResults(self.client)
+        return LiteralCheckResultsRenderer(self.client)
 
     def _POST_check(self, req):
         # check this directory
@@ -447,10 +464,10 @@ class DirectoryNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
         add_lease = boolean_of_arg(get_arg(req, "add-lease", "false"))
         if repair:
             d = self.node.check_and_repair(Monitor(), verify, add_lease)
-            d.addCallback(self._maybe_literal, CheckAndRepairResults)
+            d.addCallback(self._maybe_literal, CheckAndRepairResultsRenderer)
         else:
             d = self.node.check(Monitor(), verify, add_lease)
-            d.addCallback(self._maybe_literal, CheckResults)
+            d.addCallback(self._maybe_literal, CheckResultsRenderer)
         return d
 
     def _start_operation(self, monitor, renderer, ctx):
@@ -467,10 +484,10 @@ class DirectoryNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
         add_lease = boolean_of_arg(get_arg(ctx, "add-lease", "false"))
         if repair:
             monitor = self.node.start_deep_check_and_repair(verify, add_lease)
-            renderer = DeepCheckAndRepairResults(self.client, monitor)
+            renderer = DeepCheckAndRepairResultsRenderer(self.client, monitor)
         else:
             monitor = self.node.start_deep_check(verify, add_lease)
-            renderer = DeepCheckResults(self.client, monitor)
+            renderer = DeepCheckResultsRenderer(self.client, monitor)
         return self._start_operation(monitor, renderer, ctx)
 
     def _POST_stream_deep_check(self, ctx):
@@ -574,6 +591,8 @@ def abbreviated_dirnode(dirnode):
     u = from_string_dirnode(dirnode.get_uri())
     return u.abbrev_si()
 
+SPACE = u"\u00A0"*2
+
 class DirectoryAsHTML(rend.Page):
     # The remainder of this class is to render the directory into
     # human+browser -oriented HTML.
@@ -692,7 +711,7 @@ class DirectoryAsHTML(rend.Page):
                 T.input(type='hidden', name='t', value='rename-form'),
                 T.input(type='hidden', name='name', value=name),
                 T.input(type='hidden', name='when_done', value="."),
-                T.input(type='submit', value='rename', name="rename"),
+                T.input(type='submit', value='rename/move', name="rename"),
                 ]
 
         ctx.fillSlots("unlink", unlink)
@@ -731,8 +750,7 @@ class DirectoryAsHTML(rend.Page):
             # page that doesn't know about the directory at all
             dlurl = "%s/file/%s/@@named=/%s" % (root, quoted_uri, nameurl)
 
-            ctx.fillSlots("filename",
-                          T.a(href=dlurl)[html.escape(name)])
+            ctx.fillSlots("filename", T.a(href=dlurl)[name])
             ctx.fillSlots("type", "SSK")
 
             ctx.fillSlots("size", "?")
@@ -742,8 +760,7 @@ class DirectoryAsHTML(rend.Page):
         elif IImmutableFileNode.providedBy(target):
             dlurl = "%s/file/%s/@@named=/%s" % (root, quoted_uri, nameurl)
 
-            ctx.fillSlots("filename",
-                          T.a(href=dlurl)[html.escape(name)])
+            ctx.fillSlots("filename", T.a(href=dlurl)[name])
             ctx.fillSlots("type", "FILE")
 
             ctx.fillSlots("size", target.get_size())
@@ -753,8 +770,7 @@ class DirectoryAsHTML(rend.Page):
         elif IDirectoryNode.providedBy(target):
             # directory
             uri_link = "%s/uri/%s/" % (root, urllib.quote(target_uri))
-            ctx.fillSlots("filename",
-                          T.a(href=uri_link)[html.escape(name)])
+            ctx.fillSlots("filename", T.a(href=uri_link)[name])
             if not target.is_mutable():
                 dirtype = "DIR-IMM"
             elif target.is_readonly():
@@ -778,7 +794,7 @@ class DirectoryAsHTML(rend.Page):
 
         else:
             # unknown
-            ctx.fillSlots("filename", html.escape(name))
+            ctx.fillSlots("filename", name)
             if target.get_write_uri() is not None:
                 unknowntype = "?"
             elif not self.node.is_mutable() or target.is_alleged_immutable():
@@ -796,7 +812,7 @@ class DirectoryAsHTML(rend.Page):
 
         return ctx.tag
 
-    # XXX: Duplicated from root.py.
+    # XXX: similar to render_upload_form and render_mkdir_form in root.py.
     def render_forms(self, ctx, data):
         forms = []
 
@@ -805,79 +821,62 @@ class DirectoryAsHTML(rend.Page):
         if self.dirnode_children is None:
             return T.div["No upload forms: directory is unreadable"]
 
-        mdmf_directory_input = T.input(type='radio', name='mutable-type',
-                                       id='mutable-directory-mdmf',
-                                       value='mdmf')
-        sdmf_directory_input = T.input(type='radio', name='mutable-type',
-                                       id='mutable-directory-sdmf',
-                                       value='sdmf', checked='checked')
-        mkdir = T.form(action=".", method="post",
-                       enctype="multipart/form-data")[
+        mkdir_sdmf = T.input(type='radio', name='format',
+                             value='sdmf', id='mkdir-sdmf',
+                             checked='checked')
+        mkdir_mdmf = T.input(type='radio', name='format',
+                             value='mdmf', id='mkdir-mdmf')
+
+        mkdir_form = T.form(action=".", method="post",
+                            enctype="multipart/form-data")[
             T.fieldset[
             T.input(type="hidden", name="t", value="mkdir"),
             T.input(type="hidden", name="when_done", value="."),
             T.legend(class_="freeform-form-label")["Create a new directory in this directory"],
-            "New directory name: ",
-            T.input(type="text", name="name"), " ",
-            T.label(for_='mutable-directory-sdmf')["SDMF"],
-            sdmf_directory_input,
-            T.label(for_='mutable-directory-mdmf')["MDMF"],
-            mdmf_directory_input,
-            T.input(type="submit", value="Create"),
+            "New directory name:"+SPACE,
+            T.input(type="text", name="name"), SPACE,
+            T.input(type="submit", value="Create"), SPACE*2,
+            mkdir_sdmf, T.label(for_='mutable-directory-sdmf')[" SDMF"], SPACE,
+            mkdir_mdmf, T.label(for_='mutable-directory-mdmf')[" MDMF (experimental)"],
             ]]
-        forms.append(T.div(class_="freeform-form")[mkdir])
-
-        # Build input elements for mutable file type. We do this outside
-        # of the list so we can check the appropriate format, based on
-        # the default configured in the client (which reflects the
-        # default configured in tahoe.cfg)
-        if self.default_mutable_format == MDMF_VERSION:
-            mdmf_input = T.input(type='radio', name='mutable-type',
-                                 id='mutable-type-mdmf', value='mdmf',
-                                 checked='checked')
-        else:
-            mdmf_input = T.input(type='radio', name='mutable-type',
-                                 id='mutable-type-mdmf', value='mdmf')
-
-        if self.default_mutable_format == SDMF_VERSION:
-            sdmf_input = T.input(type='radio', name='mutable-type',
-                                 id='mutable-type-sdmf', value='sdmf',
-                                 checked="checked")
-        else:
-            sdmf_input = T.input(type='radio', name='mutable-type',
-                                 id='mutable-type-sdmf', value='sdmf')
-
-        upload = T.form(action=".", method="post",
-                        enctype="multipart/form-data")[
+        forms.append(T.div(class_="freeform-form")[mkdir_form])
+
+        upload_chk  = T.input(type='radio', name='format',
+                              value='chk', id='upload-chk',
+                              checked='checked')
+        upload_sdmf = T.input(type='radio', name='format',
+                              value='sdmf', id='upload-sdmf')
+        upload_mdmf = T.input(type='radio', name='format',
+                              value='mdmf', id='upload-mdmf')
+
+        upload_form = T.form(action=".", method="post",
+                             enctype="multipart/form-data")[
             T.fieldset[
             T.input(type="hidden", name="t", value="upload"),
             T.input(type="hidden", name="when_done", value="."),
             T.legend(class_="freeform-form-label")["Upload a file to this directory"],
-            "Choose a file to upload: ",
-            T.input(type="file", name="file", class_="freeform-input-file"),
-            " ",
-            T.input(type="submit", value="Upload"),
-            " Mutable?:",
-            T.input(type="checkbox", name="mutable"),
-            sdmf_input, T.label(for_="mutable-type-sdmf")["SDMF"],
-            mdmf_input,
-            T.label(for_="mutable-type-mdmf")["MDMF (experimental)"],
+            "Choose a file to upload:"+SPACE,
+            T.input(type="file", name="file", class_="freeform-input-file"), SPACE,
+            T.input(type="submit", value="Upload"),                          SPACE*2,
+            upload_chk,  T.label(for_="upload-chk") [" Immutable"],          SPACE,
+            upload_sdmf, T.label(for_="upload-sdmf")[" SDMF"],               SPACE,
+            upload_mdmf, T.label(for_="upload-mdmf")[" MDMF (experimental)"],
             ]]
-        forms.append(T.div(class_="freeform-form")[upload])
+        forms.append(T.div(class_="freeform-form")[upload_form])
 
-        mount = T.form(action=".", method="post",
-                        enctype="multipart/form-data")[
+        attach_form = T.form(action=".", method="post",
+                             enctype="multipart/form-data")[
             T.fieldset[
             T.input(type="hidden", name="t", value="uri"),
             T.input(type="hidden", name="when_done", value="."),
             T.legend(class_="freeform-form-label")["Add a link to a file or directory which is already in Tahoe-LAFS."],
-            "New child name: ",
-            T.input(type="text", name="name"), " ",
-            "URI of new child: ",
-            T.input(type="text", name="uri"), " ",
+            "New child name:"+SPACE,
+            T.input(type="text", name="name"), SPACE*2,
+            "URI of new child:"+SPACE,
+            T.input(type="text", name="uri"), SPACE,
             T.input(type="submit", value="Attach"),
             ]]
-        forms.append(T.div(class_="freeform-form")[mount])
+        forms.append(T.div(class_="freeform-form")[attach_form])
         return forms
 
     def render_results(self, ctx, data):
@@ -897,16 +896,16 @@ def DirectoryJSONMetadata(ctx, dirnode):
                 kiddata = ("filenode", {'size': childnode.get_size(),
                                         'mutable': childnode.is_mutable(),
                                         })
-                if childnode.is_mutable() and \
-                    childnode.get_version() is not None:
+                if childnode.is_mutable():
                     mutable_type = childnode.get_version()
                     assert mutable_type in (SDMF_VERSION, MDMF_VERSION)
-
                     if mutable_type == MDMF_VERSION:
-                        mutable_type = "mdmf"
+                        file_format = "MDMF"
                     else:
-                        mutable_type = "sdmf"
-                    kiddata[1]['mutable-type'] = mutable_type
+                        file_format = "SDMF"
+                else:
+                    file_format = "CHK"
+                kiddata[1]['format'] = file_format
 
             elif IDirectoryNode.providedBy(childnode):
                 kiddata = ("dirnode", {'mutable': childnode.is_mutable()})
@@ -975,7 +974,6 @@ class RenameForm(rend.Page):
         ctx.tag.attributes['value'] = name
         return ctx.tag
 
-
 class ManifestResults(rend.Page, ReloadMixin):
     docFactory = getxmlfile("manifest.xhtml")