+from twisted.web import http, server
from zope.interface import Interface
-from nevow import loaders
+from nevow import loaders, appserver
+from nevow.inevow import IRequest
from nevow.util import resource_filename
+from allmydata.interfaces import ExistingChildError
class IClient(Interface):
pass
return loaders.xmlfile(resource_filename('allmydata.web', '%s' % name))
def boolean_of_arg(arg):
+ # TODO: ""
assert arg.lower() in ("true", "t", "1", "false", "f", "0", "on", "off")
return arg.lower() in ("true", "t", "1", "on")
if r > 1000:
return "%.1fkB" % (r/1000)
return "%dB" % r
+
+def text_plain(text, ctx):
+ req = IRequest(ctx)
+ req.setHeader("content-type", "text/plain")
+ req.setHeader("content-length", len(text))
+ return text
+
+class WebError(Exception):
+ def __init__(self, text, code=http.BAD_REQUEST):
+ self.text = text
+ self.code = code
+
+# XXX: to make UnsupportedMethod return 501 NOT_IMPLEMENTED instead of 500
+# Internal Server Error, we either need to do that ICanHandleException trick,
+# or make sure that childFactory returns a WebErrorResource (and never an
+# actual exception). The latter is growing increasingly annoying.
+
+def should_create_intermediate_directories(req):
+ t = get_arg(req, "t", "").strip()
+ return bool(req.method in ("PUT", "POST") and
+ t not in ("delete", "rename", "rename-form", "check"))
+
+
+class MyExceptionHandler(appserver.DefaultExceptionHandler):
+ def simple(self, ctx, text, code=http.BAD_REQUEST):
+ req = IRequest(ctx)
+ req.setResponseCode(code)
+ req.setHeader("content-type", "text/plain;charset=utf-8")
+ if isinstance(text, unicode):
+ text = text.encode("utf-8")
+ req.write(text)
+ req.finishRequest(False)
+
+ def renderHTTP_exception(self, ctx, f):
+ if f.check(ExistingChildError):
+ return self.simple(ctx,
+ "There was already a child by that "
+ "name, and you asked me to not "
+ "replace it.",
+ http.CONFLICT)
+ elif f.check(WebError):
+ return self.simple(ctx, f.value.text, f.value.code)
+ elif f.check(server.UnsupportedMethod):
+ # twisted.web.server.Request.render() has support for transforming
+ # this into an appropriate 501 NOT_IMPLEMENTED or 405 NOT_ALLOWED
+ # return code, but nevow does not.
+ req = IRequest(ctx)
+ method = req.method
+ return self.simple(ctx,
+ "I don't know how to treat a %s request." % method,
+ http.NOT_IMPLEMENTED)
+ super = appserver.DefaultExceptionHandler
+ return super.renderHTTP_exception(self, ctx, f)
--- /dev/null
+
+import simplejson
+import urllib
+import time
+
+from twisted.internet import defer
+from twisted.python.failure import Failure
+from twisted.web import http, html
+from nevow import url, rend, tags as T
+from nevow.inevow import IRequest
+
+from foolscap.eventual import fireEventually
+
+from allmydata.util import log, base32
+from allmydata.uri import from_string_verifier, from_string_dirnode, \
+ CHKFileVerifierURI
+from allmydata.interfaces import IDirectoryNode, IFileNode, IMutableFileNode, \
+ ExistingChildError
+from allmydata.web.common import text_plain, WebError, IClient, \
+ boolean_of_arg, get_arg, should_create_intermediate_directories, \
+ getxmlfile
+from allmydata.web.filenode import ReplaceMeMixin, \
+ FileNodeHandler, PlaceHolderNodeHandler
+
+class BlockingFileError(Exception):
+ # TODO: catch and transform
+ """We cannot auto-create a parent directory, because there is a file in
+ the way"""
+
+def make_handler_for(node, parentnode=None, name=None):
+ if parentnode:
+ assert IDirectoryNode.providedBy(parentnode)
+ if IFileNode.providedBy(node):
+ return FileNodeHandler(node, parentnode, name)
+ if IMutableFileNode.providedBy(node):
+ return FileNodeHandler(node, parentnode, name)
+ if IDirectoryNode.providedBy(node):
+ return DirectoryNodeHandler(node, parentnode, name)
+ raise WebError("Cannot provide handler for '%s'" % node)
+
+class DirectoryNodeHandler(rend.Page, ReplaceMeMixin):
+ addSlash = True
+
+ def __init__(self, node, parentnode=None, name=None):
+ rend.Page.__init__(self)
+ assert node
+ self.node = node
+ self.parentnode = parentnode
+ self.name = name
+
+ def childFactory(self, ctx, name):
+ req = IRequest(ctx)
+ name = name.decode("utf-8")
+ d = self.node.get(name)
+ d.addBoth(self.got_child, ctx, name)
+ # got_child returns a handler resource: FileNodeHandler or
+ # DirectoryNodeHandler
+ return d
+
+ def got_child(self, node_or_failure, ctx, name):
+ DEBUG = False
+ if DEBUG: print "GOT_CHILD", name, node_or_failure
+ req = IRequest(ctx)
+ method = req.method
+ nonterminal = len(req.postpath) > 1
+ t = get_arg(req, "t", "").strip()
+ if isinstance(node_or_failure, Failure):
+ f = node_or_failure
+ f.trap(KeyError)
+ # No child by this name. What should we do about it?
+ if DEBUG: print "no child", name
+ if DEBUG: print "postpath", req.postpath
+ if nonterminal:
+ if DEBUG: print " intermediate"
+ if should_create_intermediate_directories(req):
+ # create intermediate directories
+ if DEBUG: print " making intermediate directory"
+ d = self.node.create_empty_directory(name)
+ d.addCallback(make_handler_for, self.node, name)
+ return d
+ else:
+ if DEBUG: print " terminal"
+ # terminal node
+ if (method,t) in [ ("POST","mkdir"), ("PUT","mkdir") ]:
+ if DEBUG: print " making final directory"
+ # final directory
+ d = self.node.create_empty_directory(name)
+ d.addCallback(make_handler_for, self.node, name)
+ return d
+ if (method,t) in ( ("PUT",""), ("PUT","uri"), ):
+ if DEBUG: print " PUT, making leaf placeholder"
+ # we were trying to find the leaf filenode (to put a new
+ # file in its place), and it didn't exist. That's ok,
+ # since that's the leaf node that we're about to create.
+ # We make a dummy one, which will respond to the PUT
+ # request by replacing itself.
+ return PlaceHolderNodeHandler(self.node, name)
+ if DEBUG: print " 404"
+ # otherwise, we just return a no-such-child error
+ return rend.FourOhFour()
+
+ node = node_or_failure
+ if nonterminal and should_create_intermediate_directories(req):
+ if not IDirectoryNode.providedBy(node):
+ # we would have put a new directory here, but there was a
+ # file in the way.
+ if DEBUG: print "blocking"
+ raise WebError("Unable to create directory '%s': "
+ "a file was in the way" % name,
+ http.CONFLICT)
+ if DEBUG: print "good child"
+ return make_handler_for(node, self.node, name)
+
+ def renderHTTP(self, ctx):
+ # This is where all of the ?t=* actions are implemented.
+ request = IRequest(ctx)
+
+ # if we were using regular twisted.web Resources (and the regular
+ # twisted.web.server.Request object) then we could implement
+ # render_PUT and render_GET. But Nevow's request handler
+ # (NevowRequest.gotPageContext) goes directly to renderHTTP. Copy
+ # some code from the Resource.render method that Nevow bypasses, to
+ # do the same thing.
+ m = getattr(self, 'render_' + request.method, None)
+ if not m:
+ from twisted.web.server import UnsupportedMethod
+ raise UnsupportedMethod(getattr(self, 'allowedMethods', ()))
+ return m(ctx)
+
+ def render_DELETE(self, ctx):
+ assert self.parentnode and self.name
+ d = self.parentnode.delete(self.name)
+ d.addCallback(lambda res: self.node.get_uri())
+ return d
+
+ def render_GET(self, ctx):
+ client = IClient(ctx)
+ req = IRequest(ctx)
+ # This is where all of the directory-related ?t=* code goes.
+ t = get_arg(req, "t", "").strip()
+ if not t:
+ # render the directory as HTML, using the docFactory and Nevow's
+ # whole templating thing.
+ return DirectoryAsHTML(self.node)
+
+ if t == "json":
+ return DirectoryJSONMetadata(ctx, self.node)
+ if t == "uri":
+ return DirectoryURI(ctx, self.node)
+ if t == "readonly-uri":
+ return DirectoryReadonlyURI(ctx, self.node)
+ if t == "manifest":
+ return Manifest(self.node)
+ if t == "deep-size":
+ return DeepSize(ctx, self.node)
+ if t == "deep-stats":
+ return DeepStats(ctx, self.node)
+ if t == 'rename-form':
+ return RenameForm(self.node)
+
+ raise WebError("GET directory: bad t=%s" % t)
+
+ def render_PUT(self, ctx):
+ req = IRequest(ctx)
+ t = get_arg(req, "t", "").strip()
+ replace = boolean_of_arg(get_arg(req, "replace", "true"))
+ if t == "mkdir":
+ # our job was done by the traversal/create-intermediate-directory
+ # process that got us here.
+ return text_plain(self.node.get_uri(), ctx) # TODO: urlencode
+ if t == "uri":
+ if not replace:
+ # they're trying to set_uri and that name is already occupied
+ # (by us).
+ raise ExistingChildError()
+ d = self.parentnode.replace_me_with_a_childcap(ctx, replace)
+ # TODO: results
+ return d
+
+ raise WebError("PUT to a directory")
+
+ def render_POST(self, ctx):
+ req = IRequest(ctx)
+ t = get_arg(req, "t", "").strip()
+ if t == "mkdir":
+ d = self._POST_mkdir(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":
+ d = self._POST_uri(req)
+ elif t == "delete":
+ d = self._POST_delete(req)
+ elif t == "rename":
+ d = self._POST_rename(req)
+ elif t == "check":
+ d = self._POST_check(req)
+ elif t == "set_children":
+ # TODO: docs
+ d = self._POST_set_children(req)
+ else:
+ raise WebError("POST to a directory with bad t=%s" % t)
+
+ when_done = get_arg(req, "when_done", None)
+ if when_done:
+ d.addCallback(lambda res: url.URL.fromString(when_done))
+ return d
+
+ def _POST_mkdir(self, req):
+ name = get_arg(req, "name", "")
+ if not name:
+ # our job is done, it was handled by the code in got_child
+ # which created the final directory (i.e. us)
+ return defer.succeed(self.node.get_uri()) # TODO: urlencode
+ name = name.decode("utf-8")
+ replace = boolean_of_arg(get_arg(req, "replace", "true"))
+ d = self.node.create_empty_directory(name, overwrite=replace)
+ 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(KeyError)
+ return node.create_empty_directory(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")
+ contents = req.fields["file"]
+ name = get_arg(req, "name")
+ 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 WebError("upload requires a name")
+ name = name.decode(charset)
+ if "/" in name:
+ raise WebError("name= may not contain a slash", http.BAD_REQUEST)
+ assert isinstance(name, unicode)
+
+ # since POST /uri/path/file?t=upload is equivalent to
+ # POST /uri/path/dir?t=upload&name=foo, just do the same thing that
+ # childFactory would do. Things are cleaner if we only do a subset of
+ # them, though, so we don't do: d = self.childFactory(ctx, name)
+
+ d = self.node.get(name)
+ def _maybe_got_node(node_or_failure):
+ if isinstance(node_or_failure, Failure):
+ f = node_or_failure
+ f.trap(KeyError)
+ # create a placeholder
+ return PlaceHolderNodeHandler(self.node, name)
+ else:
+ node = node_or_failure
+ return make_handler_for(node, self.node, name)
+ d.addBoth(_maybe_got_node)
+ # now we have a placeholder or a filenodehandler, and we can just
+ # delegate to it. We could return the resource back out of
+ # DirectoryNodeHandler.renderHTTP, and nevow would recurse into it,
+ # but the addCallback() that handles when_done= would break.
+ d.addCallback(lambda child: child.renderHTTP(ctx))
+ return d
+
+ def _POST_uri(self, req):
+ childcap = get_arg(req, "uri")
+ if not childcap:
+ raise WebError("set-uri requires a uri")
+ name = get_arg(req, "name")
+ if not name:
+ raise WebError("set-uri requires a name")
+ charset = get_arg(req, "_charset", "utf-8")
+ name = name.decode(charset)
+ replace = boolean_of_arg(get_arg(req, "replace", "true"))
+ d = self.node.set_uri(name, childcap, overwrite=replace)
+ d.addCallback(lambda res: childcap)
+ return d
+
+ def _POST_delete(self, req):
+ name = get_arg(req, "name")
+ if name is None:
+ # apparently an <input type="hidden" name="name" value="">
+ # won't show up in the resulting encoded form.. the 'name'
+ # field is completely missing. So to allow deletion of an
+ # empty file, we have to pretend that None means ''. The only
+ # downide of this is a slightly confusing error message if
+ # someone does a POST without a name= field. For our own HTML
+ # thisn't a big deal, because we create the 'delete' POST
+ # buttons ourselves.
+ name = ''
+ charset = get_arg(req, "_charset", "utf-8")
+ name = name.decode(charset)
+ d = self.node.delete(name)
+ d.addCallback(lambda res: "thing deleted")
+ return d
+
+ def _POST_rename(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 from_name or not to_name:
+ raise WebError("rename requires from_name and to_name")
+ for k,v in [ ('from_name', from_name), ('to_name', to_name) ]:
+ if v and "/" in v:
+ raise WebError("%s= may not contain a slash" % k,
+ http.BAD_REQUEST)
+
+ replace = boolean_of_arg(get_arg(req, "replace", "true"))
+ d = self.node.move_child_to(from_name, self.node, to_name, replace)
+ d.addCallback(lambda res: "thing renamed")
+ return d
+
+ def _POST_check(self, req):
+ # check this directory
+ d = self.node.check()
+ def _done(res):
+ log.msg("checked %s, results %s" % (self.node, res),
+ facility="tahoe.webish", level=log.NOISY)
+ return str(res)
+ d.addCallback(_done)
+ # TODO: results
+ return d
+
+ def _POST_set_children(self, req):
+ replace = boolean_of_arg(get_arg(req, "replace", "true"))
+ req.content.seek(0)
+ body = req.content.read()
+ try:
+ children = simplejson.loads(body)
+ except ValueError, le:
+ le.args = tuple(le.args + (body,))
+ # TODO test handling of bad JSON
+ raise
+ cs = []
+ for name, (file_or_dir, mddict) in children.iteritems():
+ cap = str(mddict.get('rw_uri') or mddict.get('ro_uri'))
+ cs.append((name, cap, mddict.get('metadata')))
+ d = self.node.set_children(cs, replace)
+ d.addCallback(lambda res: "Okay so I did it.")
+ # TODO: results
+ return d
+
+def abbreviated_dirnode(dirnode):
+ u = from_string_dirnode(dirnode.get_uri())
+ si = u.get_filenode_uri().storage_index
+ si_s = base32.b2a(si)
+ return si_s[:6]
+
+class DirectoryAsHTML(rend.Page):
+ # The remainder of this class is to render the directory into
+ # human+browser -oriented HTML.
+ docFactory = getxmlfile("directory.xhtml")
+
+ def __init__(self, node):
+ rend.Page.__init__(self)
+ self.node = node
+
+ def render_title(self, ctx, data):
+ si_s = abbreviated_dirnode(self.node)
+ header = ["Directory SI=%s" % si_s]
+ return ctx.tag[header]
+
+ def render_header(self, ctx, data):
+ si_s = abbreviated_dirnode(self.node)
+ header = ["Directory SI=%s" % si_s]
+ if self.node.is_readonly():
+ header.append(" (readonly)")
+ return ctx.tag[header]
+
+ def render_welcome(self, ctx, data):
+ depth = len(IRequest(ctx).path) + 2
+ link = "/".join([".."] * depth)
+ return T.div[T.a(href=link)["Return to Welcome page"]]
+
+ def data_children(self, ctx, data):
+ d = self.node.list()
+ d.addCallback(lambda dict: sorted(dict.items()))
+ def _stall_some(items):
+ # Deferreds don't optimize out tail recursion, and the way
+ # Nevow's flattener handles Deferreds doesn't take this into
+ # account. As a result, large lists of Deferreds that fire in the
+ # same turn (i.e. the output of defer.succeed) will cause a stack
+ # overflow. To work around this, we insert a turn break after
+ # every 100 items, using foolscap's fireEventually(). This gives
+ # the stack a chance to be popped. It would also work to put
+ # every item in its own turn, but that'd be a lot more
+ # inefficient. This addresses ticket #237, for which I was never
+ # able to create a failing unit test.
+ output = []
+ for i,item in enumerate(items):
+ if i % 100 == 0:
+ output.append(fireEventually(item))
+ else:
+ output.append(item)
+ return output
+ d.addCallback(_stall_some)
+ return d
+
+ def render_row(self, ctx, data):
+ name, (target, metadata) = data
+ name = name.encode("utf-8")
+ assert not isinstance(name, unicode)
+
+ if self.node.is_readonly():
+ delete = "-"
+ rename = "-"
+ else:
+ # this creates a button which will cause our child__delete method
+ # to be invoked, which deletes the file and then redirects the
+ # browser back to this directory
+ delete = T.form(action=url.here, method="post")[
+ T.input(type='hidden', name='t', value='delete'),
+ T.input(type='hidden', name='name', value=name),
+ T.input(type='hidden', name='when_done', value=url.here),
+ T.input(type='submit', value='del', name="del"),
+ ]
+
+ rename = T.form(action=url.here, method="get")[
+ 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=url.here),
+ T.input(type='submit', value='rename', name="rename"),
+ ]
+
+ ctx.fillSlots("delete", delete)
+ ctx.fillSlots("rename", rename)
+ check = T.form(action=url.here.child(name), method="post")[
+ T.input(type='hidden', name='t', value='check'),
+ T.input(type='hidden', name='when_done', value=url.here),
+ T.input(type='submit', value='check', name="check"),
+ ]
+ ctx.fillSlots("overwrite",
+ self.build_overwrite_form(ctx, name, target))
+ ctx.fillSlots("check", check)
+
+ times = []
+ TIME_FORMAT = "%H:%M:%S %d-%b-%Y"
+ if "ctime" in metadata:
+ ctime = time.strftime(TIME_FORMAT,
+ time.localtime(metadata["ctime"]))
+ times.append("c: " + ctime)
+ if "mtime" in metadata:
+ mtime = time.strftime(TIME_FORMAT,
+ time.localtime(metadata["mtime"]))
+ if times:
+ times.append(T.br())
+ times.append("m: " + mtime)
+ ctx.fillSlots("times", times)
+
+ assert (IFileNode.providedBy(target)
+ or IDirectoryNode.providedBy(target)
+ or IMutableFileNode.providedBy(target)), target
+
+ quoted_uri = urllib.quote(target.get_uri())
+
+ if IMutableFileNode.providedBy(target):
+ # to prevent javascript in displayed .html files from stealing a
+ # secret directory URI from the URL, send the browser to a URI-based
+ # page that doesn't know about the directory at all
+ dlurl = "/file/%s/@@named=/%s" % (quoted_uri, urllib.quote(name))
+
+ ctx.fillSlots("filename",
+ T.a(href=dlurl)[html.escape(name)])
+ ctx.fillSlots("type", "SSK")
+
+ ctx.fillSlots("size", "?")
+
+ text_plain_url = "/file/%s/@@named=/foo.txt" % quoted_uri
+ text_plain_tag = T.a(href=text_plain_url)["text/plain"]
+
+ elif IFileNode.providedBy(target):
+ dlurl = "/file/%s/@@named=/%s" % (quoted_uri, urllib.quote(name))
+
+ ctx.fillSlots("filename",
+ T.a(href=dlurl)[html.escape(name)])
+ ctx.fillSlots("type", "FILE")
+
+ ctx.fillSlots("size", target.get_size())
+
+ text_plain_url = "/file/%s/@@named=/foo.txt" % quoted_uri
+ text_plain_tag = T.a(href=text_plain_url)["text/plain"]
+
+
+ elif IDirectoryNode.providedBy(target):
+ # directory
+ uri_link = "/uri/" + urllib.quote(target.get_uri())
+ ctx.fillSlots("filename",
+ T.a(href=uri_link)[html.escape(name)])
+ if target.is_readonly():
+ dirtype = "DIR-RO"
+ else:
+ dirtype = "DIR"
+ ctx.fillSlots("type", dirtype)
+ ctx.fillSlots("size", "-")
+ text_plain_tag = None
+
+ childdata = [T.a(href="%s?t=json" % name)["JSON"], ", ",
+ T.a(href="%s?t=uri" % name)["URI"], ", ",
+ T.a(href="%s?t=readonly-uri" % name)["readonly-URI"],
+ ]
+ if text_plain_tag:
+ childdata.extend([", ", text_plain_tag])
+
+ ctx.fillSlots("data", childdata)
+
+ try:
+ checker = IClient(ctx).getServiceNamed("checker")
+ except KeyError:
+ checker = None
+ if checker:
+ d = defer.maybeDeferred(checker.checker_results_for,
+ target.get_verifier())
+ def _got(checker_results):
+ recent_results = reversed(checker_results[-5:])
+ if IFileNode.providedBy(target):
+ results = ("[" +
+ ", ".join(["%d/%d" % (found, needed)
+ for (when,
+ (needed, total, found, sharemap))
+ in recent_results]) +
+ "]")
+ elif IDirectoryNode.providedBy(target):
+ results = ("[" +
+ "".join([{True:"+",False:"-"}[res]
+ for (when, res) in recent_results]) +
+ "]")
+ else:
+ results = "%d results" % len(checker_results)
+ return results
+ d.addCallback(_got)
+ results = d
+ else:
+ results = "--"
+ # TODO: include a link to see more results, including timestamps
+ # TODO: use a sparkline
+ ctx.fillSlots("checker_results", results)
+
+ return ctx.tag
+
+ def render_forms(self, ctx, data):
+ if self.node.is_readonly():
+ return T.div["No upload forms: directory is read-only"]
+ mkdir = 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=url.here),
+ T.legend(class_="freeform-form-label")["Create a new directory"],
+ "New directory name: ",
+ T.input(type="text", name="name"), " ",
+ T.input(type="submit", value="Create"),
+ ]]
+
+ upload = 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=url.here),
+ 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"),
+ ]]
+
+ mount = 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=url.here),
+ T.legend(class_="freeform-form-label")["Attach a file or directory"
+ " (by URI) to this"
+ " directory"],
+ "New child name: ",
+ T.input(type="text", name="name"), " ",
+ "URI of new child: ",
+ T.input(type="text", name="uri"), " ",
+ T.input(type="submit", value="Attach"),
+ ]]
+ return [T.div(class_="freeform-form")[mkdir],
+ T.div(class_="freeform-form")[upload],
+ T.div(class_="freeform-form")[mount],
+ ]
+
+ def build_overwrite_form(self, ctx, name, target):
+ if IMutableFileNode.providedBy(target) and not target.is_readonly():
+ action = "/uri/" + urllib.quote(target.get_uri())
+ overwrite = T.form(action=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=url.here),
+ T.legend(class_="freeform-form-label")["Overwrite"],
+ "Choose new file: ",
+ T.input(type="file", name="file", class_="freeform-input-file"),
+ " ",
+ T.input(type="submit", value="Overwrite")
+ ]]
+ return [T.div(class_="freeform-form")[overwrite],]
+ else:
+ return []
+
+ def render_results(self, ctx, data):
+ req = IRequest(ctx)
+ return get_arg(req, "results", "")
+
+
+def DirectoryJSONMetadata(ctx, dirnode):
+ d = dirnode.list()
+ def _got(children):
+ kids = {}
+ for name, (childnode, metadata) in children.iteritems():
+ if IFileNode.providedBy(childnode):
+ kiduri = childnode.get_uri()
+ kiddata = ("filenode",
+ {'ro_uri': kiduri,
+ 'size': childnode.get_size(),
+ 'metadata': metadata,
+ })
+ else:
+ assert IDirectoryNode.providedBy(childnode), (childnode,
+ children,)
+ kiddata = ("dirnode",
+ {'ro_uri': childnode.get_readonly_uri(),
+ 'metadata': metadata,
+ })
+ if not childnode.is_readonly():
+ kiddata[1]['rw_uri'] = childnode.get_uri()
+ kids[name] = kiddata
+ contents = { 'children': kids,
+ 'ro_uri': dirnode.get_readonly_uri(),
+ }
+ if not dirnode.is_readonly():
+ contents['rw_uri'] = dirnode.get_uri()
+ data = ("dirnode", contents)
+ return simplejson.dumps(data, indent=1)
+ d.addCallback(_got)
+ d.addCallback(text_plain, ctx)
+ return d
+
+def DirectoryURI(ctx, dirnode):
+ return text_plain(dirnode.get_uri(), ctx)
+
+def DirectoryReadonlyURI(ctx, dirnode):
+ return text_plain(dirnode.get_readonly_uri(), ctx)
+
+class RenameForm(rend.Page):
+ addSlash = True
+ docFactory = getxmlfile("rename-form.xhtml")
+
+ def render_title(self, ctx, data):
+ return ctx.tag["Directory SI=%s" % abbreviated_dirnode(self.original)]
+
+ def render_header(self, ctx, data):
+ header = ["Rename "
+ "in directory SI=%s" % abbreviated_dirnode(self.original),
+ ]
+
+ if self.original.is_readonly():
+ header.append(" (readonly!)")
+ header.append(":")
+ return ctx.tag[header]
+
+ def render_when_done(self, ctx, data):
+ return T.input(type="hidden", name="when_done", value=url.here)
+
+ def render_get_name(self, ctx, data):
+ req = IRequest(ctx)
+ name = get_arg(req, "name", "")
+ ctx.tag.attributes['value'] = name
+ return ctx.tag
+
+
+class Manifest(rend.Page):
+ docFactory = getxmlfile("manifest.xhtml")
+
+ def render_title(self, ctx):
+ return T.title["Manifest of SI=%s" % abbreviated_dirnode(self.original)]
+
+ def render_header(self, ctx):
+ return T.p["Manifest of SI=%s" % abbreviated_dirnode(self.original)]
+
+ def data_items(self, ctx, data):
+ return self.original.build_manifest()
+
+ def render_row(self, ctx, refresh_cap):
+ ctx.fillSlots("refresh_capability", refresh_cap)
+ return ctx.tag
+
+def DeepSize(ctx, dirnode):
+ d = dirnode.build_manifest()
+ def _measure_size(manifest):
+ total = 0
+ for verifiercap in manifest:
+ u = from_string_verifier(verifiercap)
+ if isinstance(u, CHKFileVerifierURI):
+ total += u.size
+ return str(total)
+ d.addCallback(_measure_size)
+ d.addCallback(text_plain, ctx)
+ return d
+
+def DeepStats(ctx, dirnode):
+ d = dirnode.deep_stats()
+ d.addCallback(simplejson.dumps, indent=1)
+ d.addCallback(text_plain, ctx)
+ return d
--- /dev/null
+
+import simplejson
+
+from zope.interface import implements
+from twisted.internet.interfaces import IConsumer
+from twisted.web import http, static, resource, server
+from twisted.internet import defer
+from nevow import url, rend
+from nevow.inevow import IRequest
+
+from allmydata.upload import FileHandle
+from allmydata.interfaces import IDownloadTarget, ExistingChildError
+from allmydata.mutable.common import MODE_READ
+from allmydata.util import log
+
+from allmydata.web.common import text_plain, WebError, IClient, \
+ boolean_of_arg, get_arg, should_create_intermediate_directories
+
+class ReplaceMeMixin:
+
+ def replace_me_with_a_child(self, ctx, replace):
+ # a new file is being uploaded in our place.
+ req = IRequest(ctx)
+ client = IClient(ctx)
+ uploadable = FileHandle(req.content, convergence=client.convergence)
+ d = self.parentnode.add_file(self.name, uploadable, overwrite=replace)
+ def _done(filenode):
+ log.msg("webish upload complete",
+ facility="tahoe.webish", level=log.NOISY)
+ if self.node:
+ # we've replaced an existing file (or modified a mutable
+ # file), so the response code is 200
+ req.setResponseCode(http.OK)
+ else:
+ # we've created a new file, so the code is 201
+ req.setResponseCode(http.CREATED)
+ return filenode.get_uri()
+ d.addCallback(_done)
+ return d
+
+ def replace_me_with_a_childcap(self, ctx, replace):
+ req = IRequest(ctx)
+ req.content.seek(0)
+ childcap = req.content.read()
+ client = IClient(ctx)
+ childnode = client.create_node_from_uri(childcap)
+ d = self.parentnode.set_node(self.name, childnode, overwrite=replace)
+ d.addCallback(lambda res: childnode.get_uri())
+ return d
+
+ def _read_data_from_formpost(self, req):
+ # SDMF: files are small, and we can only upload data, so we read
+ # the whole file into memory before uploading.
+ contents = req.fields["file"]
+ contents.file.seek(0)
+ data = contents.file.read()
+ return data
+
+ def replace_me_with_a_formpost(self, ctx, replace):
+ # create a new file, maybe mutable, maybe immutable
+ req = IRequest(ctx)
+ client = IClient(ctx)
+ mutable = boolean_of_arg(get_arg(req, "mutable", "false"))
+
+ if mutable:
+ data = self._read_data_from_formpost(req)
+ d = client.create_mutable_file(data)
+ def _uploaded(newnode):
+ d2 = self.parentnode.set_node(self.name, newnode,
+ overwrite=replace)
+ d2.addCallback(lambda res: newnode.get_uri())
+ return d2
+ d.addCallback(_uploaded)
+ return d
+ # create an immutable file
+ contents = req.fields["file"]
+ uploadable = FileHandle(contents.file, convergence=client.convergence)
+ d = self.parentnode.add_file(self.name, uploadable, overwrite=replace)
+ d.addCallback(lambda newnode: newnode.get_uri())
+ return d
+
+class PlaceHolderNodeHandler(rend.Page, ReplaceMeMixin):
+ def __init__(self, parentnode, name):
+ rend.Page.__init__(self)
+ assert parentnode
+ self.parentnode = parentnode
+ self.name = name
+ self.node = None
+
+ def childFactory(self, ctx, name):
+ req = IRequest(ctx)
+ if should_create_intermediate_directories(req):
+ raise WebError("Cannot create directory '%s', because its "
+ "parent is a file, not a directory" % name,
+ http.CONFLICT)
+ raise WebError("Files have no children, certainly not named '%s'"
+ % name, http.CONFLICT)
+
+
+ def renderHTTP(self, ctx):
+ # This is where all of the ?t=* actions are implemented.
+ request = IRequest(ctx)
+
+ # if we were using regular twisted.web Resources (and the regular
+ # twisted.web.server.Request object) then we could implement
+ # render_PUT and render_GET. But Nevow's request handler
+ # (NevowRequest.gotPageContext) goes directly to renderHTTP. Copy
+ # some code from the Resource.render method that Nevow bypasses, to
+ # do the same thing.
+ m = getattr(self, 'render_' + request.method, None)
+ if not m:
+ from twisted.web.server import UnsupportedMethod
+ raise UnsupportedMethod(getattr(self, 'allowedMethods', ()))
+ return m(ctx)
+
+ def render_PUT(self, ctx):
+ req = IRequest(ctx)
+ t = get_arg(req, "t", "").strip()
+ replace = boolean_of_arg(get_arg(req, "replace", "true"))
+ assert self.parentnode and self.name
+ if not t:
+ return self.replace_me_with_a_child(ctx, replace)
+ if t == "uri":
+ return self.replace_me_with_a_childcap(ctx, replace)
+
+ raise WebError("PUT to a file: bad t=%s" % t)
+
+ def render_POST(self, ctx):
+ req = IRequest(ctx)
+ t = get_arg(req, "t", "").strip()
+ replace = boolean_of_arg(get_arg(req, "replace", "true"))
+ if t == "mkdir":
+ d = self.parentnode.create_empty_directory(self.name, replace)
+ d.addCallback(lambda node: node.get_uri())
+ d.addCallback(text_plain, ctx)
+ elif t == "upload":
+ # like PUT, but get the file data from an HTML form's input field.
+ # We could get here from POST /uri/mutablefilecap?t=upload,
+ # or POST /uri/path/file?t=upload, or
+ # POST /uri/path/dir?t=upload&name=foo . All have the same
+ # behavior, we just ignore any name= argument
+ d = self.replace_me_with_a_formpost(ctx, replace)
+ else:
+ raise WebError("POST to a file: bad t=%s" % t)
+
+ when_done = get_arg(req, "when_done", None)
+ if when_done:
+ d.addCallback(lambda res: url.URL.fromString(when_done))
+ return d
+
+
+class FileNodeHandler(rend.Page, ReplaceMeMixin):
+ def __init__(self, node, parentnode=None, name=None):
+ rend.Page.__init__(self)
+ assert node
+ self.node = node
+ self.parentnode = parentnode
+ self.name = name
+
+ def childFactory(self, ctx, name):
+ req = IRequest(ctx)
+ if should_create_intermediate_directories(req):
+ raise WebError("Cannot create directory '%s', because its "
+ "parent is a file, not a directory" % name)
+ raise WebError("Files have no children, certainly not named '%s'"
+ % name)
+
+
+ def renderHTTP(self, ctx):
+ # This is where all of the ?t=* actions are implemented.
+ request = IRequest(ctx)
+
+ # if we were using regular twisted.web Resources (and the regular
+ # twisted.web.server.Request object) then we could implement
+ # render_PUT and render_GET. But Nevow's request handler
+ # (NevowRequest.gotPageContext) goes directly to renderHTTP. Copy
+ # some code from the Resource.render method that Nevow bypasses, to
+ # do the same thing.
+ m = getattr(self, 'render_' + request.method, None)
+ if not m:
+ from twisted.web.server import UnsupportedMethod
+ raise UnsupportedMethod(getattr(self, 'allowedMethods', ()))
+ return m(ctx)
+
+ def render_GET(self, ctx):
+ req = IRequest(ctx)
+ t = get_arg(req, "t", "").strip()
+ if not t:
+ # just get the contents
+ filename = get_arg(req, "filename", self.name) or "unknown"
+ save_to_file = boolean_of_arg(get_arg(req, "save", "False"))
+ return FileDownloader(self.node, filename, save_to_file)
+ if t == "json":
+ return FileJSONMetadata(ctx, self.node)
+ if t == "uri":
+ return FileURI(ctx, self.node)
+ if t == "readonly-uri":
+ return FileReadOnlyURI(ctx, self.node)
+ raise WebError("GET file: bad t=%s" % t)
+
+ def render_HEAD(self, ctx):
+ req = IRequest(ctx)
+ t = get_arg(req, "t", "").strip()
+ if t:
+ raise WebError("GET file: bad t=%s" % t)
+ if self.node.is_mutable():
+ # update the servermap to get the size of this file without
+ # downloading the full contents.
+ d = self.node.get_servermap(MODE_READ)
+ def _got_servermap(smap):
+ ver = smap.best_recoverable_version()
+ if not ver:
+ raise WebError("Unable to recover this file",
+ http.NOT_FOUND)
+ length = smap.size_of_version(ver)
+ return length
+ d.addCallback(_got_servermap)
+ # otherwise, we can get the size from the URI
+ else:
+ d = defer.succeed(self.node.get_size())
+ def _got_length(length):
+ req.setHeader("content-length", length)
+ return ""
+ d.addCallback(_got_length)
+ return d
+
+ def render_PUT(self, ctx):
+ req = IRequest(ctx)
+ t = get_arg(req, "t", "").strip()
+ replace = boolean_of_arg(get_arg(req, "replace", "true"))
+ if not t:
+ if self.node.is_mutable():
+ return self.replace_my_contents(ctx)
+ if not replace:
+ # this is the early trap: if someone else modifies the
+ # directory while we're uploading, the add_file(overwrite=)
+ # call in replace_me_with_a_child will do the late trap.
+ raise ExistingChildError()
+ assert self.parentnode and self.name
+ return self.replace_me_with_a_child(ctx, replace)
+ if t == "uri":
+ if not replace:
+ raise ExistingChildError()
+ assert self.parentnode and self.name
+ return self.replace_me_with_a_childcap(ctx, replace)
+
+ raise WebError("PUT to a file: bad t=%s" % t)
+
+ def render_POST(self, ctx):
+ req = IRequest(ctx)
+ t = get_arg(req, "t", "").strip()
+ replace = boolean_of_arg(get_arg(req, "replace", "true"))
+ if t == "check":
+ d = self._POST_check(req)
+ elif t == "upload":
+ # like PUT, but get the file data from an HTML form's input field
+ # We could get here from POST /uri/mutablefilecap?t=upload,
+ # or POST /uri/path/file?t=upload, or
+ # POST /uri/path/dir?t=upload&name=foo . All have the same
+ # behavior, we just ignore any name= argument
+ if self.node.is_mutable():
+ d = self.replace_my_contents_with_a_formpost(ctx)
+ else:
+ if not replace:
+ raise ExistingChildError()
+ assert self.parentnode and self.name
+ d = self.replace_me_with_a_formpost(ctx, replace)
+ else:
+ raise WebError("POST to file: bad t=%s" % t)
+
+ when_done = get_arg(req, "when_done", None)
+ if when_done:
+ d.addCallback(lambda res: url.URL.fromString(when_done))
+ return d
+
+ def _POST_check(self, req):
+ d = self.node.check()
+ def _done(res):
+ log.msg("checked %s, results %s" % (self.node, res),
+ facility="tahoe.webish", level=log.NOISY)
+ return str(res)
+ d.addCallback(_done)
+ # TODO: results
+ return d
+
+ def render_DELETE(self, ctx):
+ assert self.parentnode and self.name
+ d = self.parentnode.delete(self.name)
+ d.addCallback(lambda res: self.node.get_uri())
+ return d
+
+ def replace_my_contents(self, ctx):
+ req = IRequest(ctx)
+ req.content.seek(0)
+ new_contents = req.content.read()
+ d = self.node.overwrite(new_contents)
+ d.addCallback(lambda res: self.node.get_uri())
+ return d
+
+ def replace_my_contents_with_a_formpost(self, ctx):
+ # we have a mutable file. Get the data from the formpost, and replace
+ # the mutable file's contents with it.
+ req = IRequest(ctx)
+ new_contents = self._read_data_from_formpost(req)
+ d = self.node.overwrite(new_contents)
+ d.addCallback(lambda res: self.node.get_uri())
+ return d
+
+
+class WebDownloadTarget:
+ implements(IDownloadTarget, IConsumer)
+ def __init__(self, req, content_type, content_encoding, save_to_filename):
+ self._req = req
+ self._content_type = content_type
+ self._content_encoding = content_encoding
+ self._opened = False
+ self._producer = None
+ self._save_to_filename = save_to_filename
+
+ def registerProducer(self, producer, streaming):
+ self._req.registerProducer(producer, streaming)
+ def unregisterProducer(self):
+ self._req.unregisterProducer()
+
+ def open(self, size):
+ self._opened = True
+ self._req.setHeader("content-type", self._content_type)
+ if self._content_encoding:
+ self._req.setHeader("content-encoding", self._content_encoding)
+ self._req.setHeader("content-length", str(size))
+ if self._save_to_filename is not None:
+ # tell the browser to save the file rather display it
+ # TODO: indicate charset of filename= properly
+ filename = self._save_to_filename.encode("utf-8")
+ self._req.setHeader("content-disposition",
+ 'attachment; filename="%s"'
+ % filename)
+
+ def write(self, data):
+ self._req.write(data)
+ def close(self):
+ self._req.finish()
+
+ def fail(self, why):
+ if self._opened:
+ # The content-type is already set, and the response code
+ # has already been sent, so we can't provide a clean error
+ # indication. We can emit text (which a browser might interpret
+ # as something else), and if we sent a Size header, they might
+ # notice that we've truncated the data. Keep the error message
+ # small to improve the chances of having our error response be
+ # shorter than the intended results.
+ #
+ # We don't have a lot of options, unfortunately.
+ self._req.write("problem during download\n")
+ else:
+ # We haven't written anything yet, so we can provide a sensible
+ # error message.
+ msg = str(why.type)
+ msg.replace("\n", "|")
+ self._req.setResponseCode(http.GONE, msg)
+ self._req.setHeader("content-type", "text/plain")
+ # TODO: HTML-formatted exception?
+ self._req.write(str(why))
+ self._req.finish()
+
+ def register_canceller(self, cb):
+ pass
+ def finish(self):
+ pass
+
+class FileDownloader(resource.Resource):
+ # since we override the rendering process (to let the tahoe Downloader
+ # drive things), we must inherit from regular old twisted.web.resource
+ # instead of nevow.rend.Page . Nevow will use adapters to wrap a
+ # nevow.appserver.OldResourceAdapter around any
+ # twisted.web.resource.IResource that it is given. TODO: it looks like
+ # that wrapper would allow us to return a Deferred from render(), which
+ # might could simplify the implementation of WebDownloadTarget.
+
+ def __init__(self, filenode, filename, save_to_file):
+ resource.Resource.__init__(self)
+ self.filenode = filenode
+ self.filename = filename
+ self.save_to_file = save_to_file
+ def render(self, req):
+ gte = static.getTypeAndEncoding
+ ctype, encoding = gte(self.filename,
+ static.File.contentTypes,
+ static.File.contentEncodings,
+ defaultType="text/plain")
+ save_to_filename = None
+ if self.save_to_file:
+ save_to_filename = self.filename
+ wdt = WebDownloadTarget(req, ctype, encoding, save_to_filename)
+ d = self.filenode.download(wdt)
+ # exceptions during download are handled by the WebDownloadTarget
+ d.addErrback(lambda why: None)
+ return server.NOT_DONE_YET
+
+def FileJSONMetadata(ctx, filenode):
+ file_uri = filenode.get_uri()
+ data = ("filenode",
+ {'ro_uri': file_uri,
+ 'size': filenode.get_size(),
+ })
+ return text_plain(simplejson.dumps(data, indent=1), ctx)
+
+def FileURI(ctx, filenode):
+ return text_plain(filenode.get_uri(), ctx)
+
+def FileReadOnlyURI(ctx, filenode):
+ if filenode.is_readonly():
+ return text_plain(filenode.get_uri(), ctx)
+ return text_plain(filenode.get_readonly().get_uri(), ctx)
+
+class FileNodeDownloadHandler(FileNodeHandler):
+ def childFactory(self, ctx, name):
+ return FileNodeDownloadHandler(self.node, name=name)
--- /dev/null
+
+import time
+
+from twisted.internet import address
+from twisted.web import http
+from nevow import rend, url, tags as T
+from nevow.inevow import IRequest
+from nevow.static import File as nevow_File # TODO: merge with static.File?
+from nevow.util import resource_filename
+from formless import webform
+
+import allmydata # to display import path
+from allmydata import get_package_versions_string
+from allmydata import provisioning
+from allmydata.util import idlib
+from allmydata.interfaces import IFileNode
+from allmydata.web import filenode, directory, unlinked, status
+from allmydata.web.common import abbreviate_size, IClient, getxmlfile, \
+ WebError, get_arg
+
+
+
+class URIHandler(rend.Page):
+ # I live at /uri . There are several operations defined on /uri itself,
+ # mostly involed with creation of unlinked files and directories.
+
+ def renderHTTP(self, ctx):
+ request = IRequest(ctx)
+
+ # if we were using regular twisted.web Resources (and the regular
+ # twisted.web.server.Request object) then we could implement
+ # render_PUT and render_GET. But Nevow's request handler
+ # (NevowRequest.gotPageContext) goes directly to renderHTTP. Copy
+ # some code from the Resource.render method that Nevow bypasses, to
+ # do the same thing.
+ m = getattr(self, 'render_' + request.method, None)
+ if not m:
+ from twisted.web.server import UnsupportedMethod
+ raise UnsupportedMethod(getattr(self, 'allowedMethods', ()))
+ return m(ctx)
+
+ def render_GET(self, ctx):
+ req = IRequest(ctx)
+ uri = get_arg(req, "uri", None)
+ if uri is None:
+ raise WebError("GET /uri requires uri=")
+ there = url.URL.fromContext(ctx)
+ there = there.clear("uri")
+ # I thought about escaping the childcap that we attach to the URL
+ # here, but it seems that nevow does that for us.
+ there = there.child(uri)
+ return there
+
+ def render_PUT(self, ctx):
+ req = IRequest(ctx)
+ # either "PUT /uri" to create an unlinked file, or
+ # "PUT /uri?t=mkdir" to create an unlinked directory
+ t = get_arg(req, "t", "").strip()
+ if t == "":
+ mutable = bool(get_arg(req, "mutable", "").strip())
+ if mutable:
+ return unlinked.PUTUnlinkedSSK(ctx)
+ else:
+ return unlinked.PUTUnlinkedCHK(ctx)
+ if t == "mkdir":
+ return unlinked.PUTUnlinkedCreateDirectory(ctx)
+ errmsg = ("/uri accepts only PUT, PUT?t=mkdir, POST?t=upload, "
+ "and POST?t=mkdir")
+ raise WebError(errmsg, http.BAD_REQUEST)
+
+ def render_POST(self, ctx):
+ # "POST /uri?t=upload&file=newfile" to upload an
+ # unlinked file or "POST /uri?t=mkdir" to create a
+ # new directory
+ req = IRequest(ctx)
+ t = get_arg(req, "t", "").strip()
+ if t in ("", "upload"):
+ mutable = bool(get_arg(req, "mutable", "").strip())
+ if mutable:
+ return unlinked.POSTUnlinkedSSK(ctx)
+ else:
+ return unlinked.POSTUnlinkedCHK(ctx)
+ if t == "mkdir":
+ return unlinked.POSTUnlinkedCreateDirectory(ctx)
+ errmsg = ("/uri accepts only PUT, PUT?t=mkdir, POST?t=upload, "
+ "and POST?t=mkdir")
+ raise WebError(errmsg, http.BAD_REQUEST)
+
+ def childFactory(self, ctx, name):
+ # 'name' is expected to be a URI
+ client = IClient(ctx)
+ try:
+ node = client.create_node_from_uri(name)
+ return directory.make_handler_for(node)
+ except (TypeError, AssertionError):
+ raise WebError("'%s' is not a valid file- or directory- cap"
+ % name)
+
+class FileHandler(rend.Page):
+ # I handle /file/$FILECAP[/IGNORED] , which provides a URL from which a
+ # file can be downloaded correctly by tools like "wget".
+
+ def childFactory(self, ctx, name):
+ req = IRequest(ctx)
+ if req.method not in ("GET", "HEAD"):
+ raise WebError("/file can only be used with GET or HEAD")
+ # 'name' must be a file URI
+ client = IClient(ctx)
+ try:
+ node = client.create_node_from_uri(name)
+ except (TypeError, AssertionError):
+ raise WebError("'%s' is not a valid file- or directory- cap"
+ % name)
+ if not IFileNode.providedBy(node):
+ raise WebError("'%s' is not a file-cap" % name)
+ return filenode.FileNodeDownloadHandler(node)
+
+ def renderHTTP(self, ctx):
+ raise WebError("/file must be followed by a file-cap and a name",
+ http.NOT_FOUND)
+
+class Root(rend.Page):
+
+ addSlash = True
+ docFactory = getxmlfile("welcome.xhtml")
+
+ child_uri = URIHandler()
+ child_file = FileHandler()
+ child_named = FileHandler()
+
+ child_webform_css = webform.defaultCSS
+ child_tahoe_css = nevow_File(resource_filename('allmydata.web', 'tahoe.css'))
+
+ child_provisioning = provisioning.ProvisioningTool()
+ child_status = status.Status()
+ child_helper_status = status.HelperStatus()
+ child_statistics = status.Statistics()
+
+ def data_version(self, ctx, data):
+ return get_package_versions_string()
+ def data_import_path(self, ctx, data):
+ return str(allmydata)
+ def data_my_nodeid(self, ctx, data):
+ return idlib.nodeid_b2a(IClient(ctx).nodeid)
+
+ def render_services(self, ctx, data):
+ ul = T.ul()
+ client = IClient(ctx)
+ try:
+ ss = client.getServiceNamed("storage")
+ allocated_s = abbreviate_size(ss.allocated_size())
+ allocated = "about %s allocated" % allocated_s
+ sizelimit = "no size limit"
+ if ss.sizelimit is not None:
+ sizelimit = "size limit is %s" % abbreviate_size(ss.sizelimit)
+ ul[T.li["Storage Server: %s, %s" % (allocated, sizelimit)]]
+ except KeyError:
+ ul[T.li["Not running storage server"]]
+
+ try:
+ h = client.getServiceNamed("helper")
+ stats = h.get_stats()
+ active_uploads = stats["chk_upload_helper.active_uploads"]
+ ul[T.li["Helper: %d active uploads" % (active_uploads,)]]
+ except KeyError:
+ ul[T.li["Not running helper"]]
+
+ return ctx.tag[ul]
+
+ def data_introducer_furl(self, ctx, data):
+ return IClient(ctx).introducer_furl
+ def data_connected_to_introducer(self, ctx, data):
+ if IClient(ctx).connected_to_introducer():
+ return "yes"
+ return "no"
+
+ def data_helper_furl(self, ctx, data):
+ try:
+ uploader = IClient(ctx).getServiceNamed("uploader")
+ except KeyError:
+ return None
+ furl, connected = uploader.get_helper_info()
+ return furl
+ def data_connected_to_helper(self, ctx, data):
+ try:
+ uploader = IClient(ctx).getServiceNamed("uploader")
+ except KeyError:
+ return "no" # we don't even have an Uploader
+ furl, connected = uploader.get_helper_info()
+ if connected:
+ return "yes"
+ return "no"
+
+ def data_known_storage_servers(self, ctx, data):
+ ic = IClient(ctx).introducer_client
+ servers = [c
+ for c in ic.get_all_connectors().values()
+ if c.service_name == "storage"]
+ return len(servers)
+
+ def data_connected_storage_servers(self, ctx, data):
+ ic = IClient(ctx).introducer_client
+ return len(ic.get_all_connections_for("storage"))
+
+ def data_services(self, ctx, data):
+ ic = IClient(ctx).introducer_client
+ c = [ (service_name, nodeid, rsc)
+ for (nodeid, service_name), rsc
+ in ic.get_all_connectors().items() ]
+ c.sort()
+ return c
+
+ def render_service_row(self, ctx, data):
+ (service_name, nodeid, rsc) = data
+ ctx.fillSlots("peerid", "%s %s" % (idlib.nodeid_b2a(nodeid),
+ rsc.nickname))
+ if rsc.rref:
+ rhost = rsc.remote_host
+ if nodeid == IClient(ctx).nodeid:
+ rhost_s = "(loopback)"
+ elif isinstance(rhost, address.IPv4Address):
+ rhost_s = "%s:%d" % (rhost.host, rhost.port)
+ else:
+ rhost_s = str(rhost)
+ connected = "Yes: to " + rhost_s
+ since = rsc.last_connect_time
+ else:
+ connected = "No"
+ since = rsc.last_loss_time
+
+ TIME_FORMAT = "%H:%M:%S %d-%b-%Y"
+ ctx.fillSlots("connected", connected)
+ ctx.fillSlots("since", time.strftime(TIME_FORMAT, time.localtime(since)))
+ ctx.fillSlots("announced", time.strftime(TIME_FORMAT,
+ time.localtime(rsc.announcement_time)))
+ ctx.fillSlots("version", rsc.version)
+ ctx.fillSlots("service_name", rsc.service_name)
+
+ return ctx.tag
+
+ def render_download_form(self, ctx, data):
+ # this is a form where users can download files by URI
+ form = T.form(action="uri", method="get",
+ enctype="multipart/form-data")[
+ T.fieldset[
+ T.legend(class_="freeform-form-label")["Download a file"],
+ "URI to download: ",
+ T.input(type="text", name="uri"), " ",
+ "Filename to download as: ",
+ T.input(type="text", name="filename"), " ",
+ T.input(type="submit", value="Download!"),
+ ]]
+ return T.div[form]
+
+ def render_view_form(self, ctx, data):
+ # this is a form where users can download files by URI, or jump to a
+ # named directory
+ form = T.form(action="uri", method="get",
+ enctype="multipart/form-data")[
+ T.fieldset[
+ T.legend(class_="freeform-form-label")["View a file or directory"],
+ "URI to view: ",
+ T.input(type="text", name="uri"), " ",
+ T.input(type="submit", value="View!"),
+ ]]
+ return T.div[form]
+
+ def render_upload_form(self, ctx, data):
+ # this is a form where users can upload unlinked files
+ form = T.form(action="uri", method="post",
+ enctype="multipart/form-data")[
+ T.fieldset[
+ T.legend(class_="freeform-form-label")["Upload a file"],
+ "Choose a file: ",
+ T.input(type="file", name="file", class_="freeform-input-file"),
+ T.input(type="hidden", name="t", value="upload"),
+ " Mutable?:", T.input(type="checkbox", name="mutable"),
+ T.input(type="submit", value="Upload!"),
+ ]]
+ return T.div[form]
+
+ def render_mkdir_form(self, ctx, data):
+ # this is a form where users can create new directories
+ form = T.form(action="uri", method="post",
+ enctype="multipart/form-data")[
+ T.fieldset[
+ T.legend(class_="freeform-form-label")["Create a directory"],
+ T.input(type="hidden", name="t", value="mkdir"),
+ T.input(type="hidden", name="redirect_to_result", value="true"),
+ T.input(type="submit", value="Create Directory!"),
+ ]]
+ return T.div[form]
+
import urllib
from twisted.web import http
-from nevow import rend, inevow, url, tags as T
+from twisted.internet import defer
+from nevow import rend, url, tags as T
+from nevow.inevow import IRequest
from allmydata.upload import FileHandle
from allmydata.web.common import IClient, getxmlfile, get_arg, boolean_of_arg
from allmydata.web import status
-from allmydata.util import observer
-class UnlinkedPUTCHKUploader(rend.Page):
- def renderHTTP(self, ctx):
- req = inevow.IRequest(ctx)
- assert req.method == "PUT"
- # "PUT /uri", to create an unlinked file. This is like PUT but
- # without the associated set_uri.
-
- client = IClient(ctx)
-
- uploadable = FileHandle(req.content, client.convergence)
- d = client.upload(uploadable)
- d.addCallback(lambda results: results.uri)
- # that fires with the URI of the new file
- return d
-
-class UnlinkedPUTSSKUploader(rend.Page):
- def renderHTTP(self, ctx):
- req = inevow.IRequest(ctx)
- assert req.method == "PUT"
- # SDMF: files are small, and we can only upload data
- req.content.seek(0)
- data = req.content.read()
- d = IClient(ctx).create_mutable_file(data)
- d.addCallback(lambda n: n.get_uri())
- return d
-
-class UnlinkedPUTCreateDirectory(rend.Page):
- def renderHTTP(self, ctx):
- req = inevow.IRequest(ctx)
- assert req.method == "PUT"
- # "PUT /uri?t=mkdir", to create an unlinked directory.
- d = IClient(ctx).create_empty_dirnode()
- d.addCallback(lambda dirnode: dirnode.get_uri())
- # XXX add redirect_to_result
- return d
-
-class UnlinkedPOSTCHKUploader(status.UploadResultsRendererMixin, rend.Page):
+def PUTUnlinkedCHK(ctx):
+ req = IRequest(ctx)
+ # "PUT /uri", to create an unlinked file.
+ client = IClient(ctx)
+ uploadable = FileHandle(req.content, client.convergence)
+ d = client.upload(uploadable)
+ d.addCallback(lambda results: results.uri)
+ # that fires with the URI of the new file
+ return d
+
+def PUTUnlinkedSSK(ctx):
+ req = IRequest(ctx)
+ # SDMF: files are small, and we can only upload data
+ req.content.seek(0)
+ data = req.content.read()
+ d = IClient(ctx).create_mutable_file(data)
+ d.addCallback(lambda n: n.get_uri())
+ return d
+
+def PUTUnlinkedCreateDirectory(ctx):
+ req = IRequest(ctx)
+ # "PUT /uri?t=mkdir", to create an unlinked directory.
+ d = IClient(ctx).create_empty_dirnode()
+ d.addCallback(lambda dirnode: dirnode.get_uri())
+ # XXX add redirect_to_result
+ return d
+
+
+def POSTUnlinkedCHK(ctx):
+ req = IRequest(ctx)
+ client = IClient(ctx)
+ fileobj = req.fields["file"].file
+ uploadable = FileHandle(fileobj, client.convergence)
+ d = client.upload(uploadable)
+ when_done = get_arg(req, "when_done", None)
+ if when_done:
+ # if when_done= is provided, return a redirect instead of our
+ # usual upload-results page
+ def _done(upload_results, redir_to):
+ if "%(uri)s" in redir_to:
+ redir_to = redir_to % {"uri": urllib.quote(upload_results.uri)
+ }
+ return url.URL.fromString(redir_to)
+ d.addCallback(_done, when_done)
+ else:
+ # return the Upload Results page, which includes the URI
+ d.addCallback(UploadResultsPage, ctx)
+ return d
+
+
+class UploadResultsPage(status.UploadResultsRendererMixin, rend.Page):
"""'POST /uri', to create an unlinked file."""
docFactory = getxmlfile("upload-results.xhtml")
- def __init__(self, client, req):
+ def __init__(self, upload_results, ctx):
rend.Page.__init__(self)
- # we start the upload now, and distribute notification of its
- # completion to render_ methods with an ObserverList
- assert req.method == "POST"
- self._done = observer.OneShotObserverList()
- fileobj = req.fields["file"].file
- uploadable = FileHandle(fileobj, client.convergence)
- d = client.upload(uploadable)
- d.addBoth(self._done.fire)
-
- def renderHTTP(self, ctx):
- req = inevow.IRequest(ctx)
- when_done = get_arg(req, "when_done", None)
- if when_done:
- # if when_done= is provided, return a redirect instead of our
- # usual upload-results page
- d = self._done.when_fired()
- d.addCallback(lambda res: url.URL.fromString(when_done))
- return d
- return rend.Page.renderHTTP(self, ctx)
+ self.results = upload_results
def upload_results(self):
- return self._done.when_fired()
+ return defer.succeed(self.results)
def data_done(self, ctx, data):
d = self.upload_results()
["/uri/" + res.uri])
return d
-class UnlinkedPOSTSSKUploader(rend.Page):
- def renderHTTP(self, ctx):
- req = inevow.IRequest(ctx)
- assert req.method == "POST"
-
- # "POST /uri", to create an unlinked file.
- # SDMF: files are small, and we can only upload data
- contents = req.fields["file"]
- contents.file.seek(0)
- data = contents.file.read()
- d = IClient(ctx).create_mutable_file(data)
- d.addCallback(lambda n: n.get_uri())
- return d
-
-class UnlinkedPOSTCreateDirectory(rend.Page):
- def renderHTTP(self, ctx):
- req = inevow.IRequest(ctx)
- assert req.method == "POST"
-
- # "POST /uri?t=mkdir", to create an unlinked directory.
- d = IClient(ctx).create_empty_dirnode()
- redirect = get_arg(req, "redirect_to_result", "false")
- if boolean_of_arg(redirect):
- def _then_redir(res):
- new_url = "uri/" + urllib.quote(res.get_uri())
- req.setResponseCode(http.SEE_OTHER) # 303
- req.setHeader('location', new_url)
- req.finish()
- return ''
- d.addCallback(_then_redir)
- else:
- d.addCallback(lambda dirnode: dirnode.get_uri())
- return d
+def POSTUnlinkedSSK(ctx):
+ req = IRequest(ctx)
+ # "POST /uri", to create an unlinked file.
+ # SDMF: files are small, and we can only upload data
+ contents = req.fields["file"]
+ contents.file.seek(0)
+ data = contents.file.read()
+ d = IClient(ctx).create_mutable_file(data)
+ d.addCallback(lambda n: n.get_uri())
+ return d
+
+def POSTUnlinkedCreateDirectory(ctx):
+ req = IRequest(ctx)
+ # "POST /uri?t=mkdir", to create an unlinked directory.
+ d = IClient(ctx).create_empty_dirnode()
+ redirect = get_arg(req, "redirect_to_result", "false")
+ if boolean_of_arg(redirect):
+ def _then_redir(res):
+ new_url = "uri/" + urllib.quote(res.get_uri())
+ req.setResponseCode(http.SEE_OTHER) # 303
+ req.setHeader('location', new_url)
+ req.finish()
+ return ''
+ d.addCallback(_then_redir)
+ else:
+ d.addCallback(lambda dirnode: dirnode.get_uri())
+ return d
-import time, os.path
from twisted.application import service, strports, internet
-from twisted.web import static, resource, server, html, http
-from twisted.internet import defer, address
-from twisted.internet.interfaces import IConsumer
-from nevow import inevow, rend, appserver, url, tags as T
-from nevow.static import File as nevow_File # TODO: merge with static.File?
-from allmydata.util import fileutil, idlib, log
-import simplejson
-from allmydata.interfaces import IDownloadTarget, IDirectoryNode, IFileNode, \
- IMutableFileNode
-import allmydata # to display import path
-from allmydata import download
-from allmydata.uri import from_string_verifier, CHKFileVerifierURI
-from allmydata.upload import FileHandle, FileName
-from allmydata import provisioning
-from allmydata import get_package_versions_string
-from zope.interface import implements, Interface
-import urllib
-from formless import webform
-from foolscap.eventual import fireEventually
+from twisted.web import http
+from twisted.internet import defer
+from nevow import appserver, inevow
+from allmydata.util import log
-from nevow.util import resource_filename
-
-from allmydata.web import status, unlinked, introweb
-from allmydata.web.common import IClient, getxmlfile, get_arg, \
- boolean_of_arg, abbreviate_size
-
-class ILocalAccess(Interface):
- def local_access_is_allowed():
- """Return True if t=upload&localdir= is allowed, giving anyone who
- can talk to the webserver control over the local (disk) filesystem."""
+from allmydata.web import introweb, root
+from allmydata.web.common import IClient, MyExceptionHandler
# we must override twisted.web.http.Request.requestReceived with a version
# that doesn't use cgi.parse_multipart() . Since we actually use Nevow, we
## self.channel.transport.loseConnection()
## return
## raise
-
self.process()
def _logger(self):
level=log.OPERATIONAL,
)
-class Directory(rend.Page):
- addSlash = True
- docFactory = getxmlfile("directory.xhtml")
-
- def __init__(self, rootname, dirnode, dirpath):
- self._rootname = rootname
- self._dirnode = dirnode
- self._dirpath = dirpath
-
- def dirpath_as_string(self):
- return "/" + "/".join(self._dirpath)
-
- def render_title(self, ctx, data):
- return ctx.tag["Directory '%s':" % self.dirpath_as_string()]
-
- def render_header(self, ctx, data):
- parent_directories = ("<%s>" % self._rootname,) + self._dirpath
- num_dirs = len(parent_directories)
-
- header = ["Directory '"]
- for i,d in enumerate(parent_directories):
- upness = num_dirs - i - 1
- if upness:
- link = "/".join( ("..",) * upness )
- else:
- link = "."
- header.append(T.a(href=link)[d])
- if upness != 0:
- header.append("/")
- header.append("'")
-
- if self._dirnode.is_readonly():
- header.append(" (readonly)")
- header.append(":")
- return ctx.tag[header]
-
- def render_welcome(self, ctx, data):
- depth = len(self._dirpath) + 2
- link = "/".join([".."] * depth)
- return T.div[T.a(href=link)["Return to Welcome page"]]
-
- def data_children(self, ctx, data):
- d = self._dirnode.list()
- d.addCallback(lambda dict: sorted(dict.items()))
- def _stall_some(items):
- # Deferreds don't optimize out tail recursion, and the way
- # Nevow's flattener handles Deferreds doesn't take this into
- # account. As a result, large lists of Deferreds that fire in the
- # same turn (i.e. the output of defer.succeed) will cause a stack
- # overflow. To work around this, we insert a turn break after
- # every 100 items, using foolscap's fireEventually(). This gives
- # the stack a chance to be popped. It would also work to put
- # every item in its own turn, but that'd be a lot more
- # inefficient. This addresses ticket #237, for which I was never
- # able to create a failing unit test.
- output = []
- for i,item in enumerate(items):
- if i % 100 == 0:
- output.append(fireEventually(item))
- else:
- output.append(item)
- return output
- d.addCallback(_stall_some)
- return d
-
- 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 = "-"
- rename = "-"
- else:
- # this creates a button which will cause our child__delete method
- # to be invoked, which deletes the file and then redirects the
- # browser back to this directory
- delete = T.form(action=url.here, method="post")[
- T.input(type='hidden', name='t', value='delete'),
- T.input(type='hidden', name='name', value=name),
- T.input(type='hidden', name='when_done', value=url.here),
- T.input(type='submit', value='del', name="del"),
- ]
-
- rename = T.form(action=url.here, method="get")[
- 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=url.here),
- T.input(type='submit', value='rename', name="rename"),
- ]
-
- ctx.fillSlots("delete", delete)
- ctx.fillSlots("rename", rename)
- check = T.form(action=url.here, method="post")[
- T.input(type='hidden', name='t', value='check'),
- T.input(type='hidden', name='name', value=name),
- T.input(type='hidden', name='when_done', value=url.here),
- T.input(type='submit', value='check', name="check"),
- ]
- ctx.fillSlots("overwrite", self.build_overwrite(ctx, (name, target)))
- ctx.fillSlots("check", check)
-
- times = []
- TIME_FORMAT = "%H:%M:%S %d-%b-%Y"
- if "ctime" in metadata:
- ctime = time.strftime(TIME_FORMAT,
- time.localtime(metadata["ctime"]))
- times.append("c: " + ctime)
- if "mtime" in metadata:
- mtime = time.strftime(TIME_FORMAT,
- time.localtime(metadata["mtime"]))
- if times:
- times.append(T.br())
- times.append("m: " + mtime)
- ctx.fillSlots("times", times)
-
- assert (IFileNode.providedBy(target)
- or IDirectoryNode.providedBy(target)
- or IMutableFileNode.providedBy(target)), target
-
- quoted_uri = urllib.quote(target.get_uri())
-
- if IMutableFileNode.providedBy(target):
- # to prevent javascript in displayed .html files from stealing a
- # secret directory URI from the URL, send the browser to a URI-based
- # page that doesn't know about the directory at all
- dlurl = "/file/%s/@@named=/%s" % (quoted_uri, urllib.quote(name))
-
- ctx.fillSlots("filename",
- T.a(href=dlurl)[html.escape(name)])
- ctx.fillSlots("type", "SSK")
-
- ctx.fillSlots("size", "?")
-
- text_plain_url = "/file/%s/@@named=/foo.txt" % quoted_uri
- text_plain_tag = T.a(href=text_plain_url)["text/plain"]
-
- elif IFileNode.providedBy(target):
- dlurl = "/file/%s/@@named=/%s" % (quoted_uri, urllib.quote(name))
-
- ctx.fillSlots("filename",
- T.a(href=dlurl)[html.escape(name)])
- ctx.fillSlots("type", "FILE")
-
- ctx.fillSlots("size", target.get_size())
-
- text_plain_url = "/file/%s/@@named=/foo.txt" % quoted_uri
- text_plain_tag = T.a(href=text_plain_url)["text/plain"]
-
-
- elif IDirectoryNode.providedBy(target):
- # directory
- uri_link = "/uri/" + urllib.quote(target.get_uri())
- ctx.fillSlots("filename",
- T.a(href=uri_link)[html.escape(name)])
- if target.is_readonly():
- dirtype = "DIR-RO"
- else:
- dirtype = "DIR"
- ctx.fillSlots("type", dirtype)
- ctx.fillSlots("size", "-")
- text_plain_tag = None
-
- childdata = [T.a(href="%s?t=json" % name)["JSON"], ", ",
- T.a(href="%s?t=uri" % name)["URI"], ", ",
- T.a(href="%s?t=readonly-uri" % name)["readonly-URI"],
- ]
- if text_plain_tag:
- childdata.extend([", ", text_plain_tag])
-
- ctx.fillSlots("data", childdata)
-
- try:
- checker = IClient(ctx).getServiceNamed("checker")
- except KeyError:
- checker = None
- if checker:
- d = defer.maybeDeferred(checker.checker_results_for,
- target.get_verifier())
- def _got(checker_results):
- recent_results = reversed(checker_results[-5:])
- if IFileNode.providedBy(target):
- results = ("[" +
- ", ".join(["%d/%d" % (found, needed)
- for (when,
- (needed, total, found, sharemap))
- in recent_results]) +
- "]")
- elif IDirectoryNode.providedBy(target):
- results = ("[" +
- "".join([{True:"+",False:"-"}[res]
- for (when, res) in recent_results]) +
- "]")
- else:
- results = "%d results" % len(checker_results)
- return results
- d.addCallback(_got)
- results = d
- else:
- results = "--"
- # TODO: include a link to see more results, including timestamps
- # TODO: use a sparkline
- ctx.fillSlots("checker_results", results)
-
- return ctx.tag
-
- def render_forms(self, ctx, data):
- if self._dirnode.is_readonly():
- return T.div["No upload forms: directory is read-only"]
- mkdir = 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=url.here),
- T.legend(class_="freeform-form-label")["Create a new directory"],
- "New directory name: ",
- T.input(type="text", name="name"), " ",
- T.input(type="submit", value="Create"),
- ]]
-
- upload = 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=url.here),
- 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"),
- ]]
-
- mount = 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=url.here),
- T.legend(class_="freeform-form-label")["Attach a file or directory"
- " (by URI) to this"
- " directory"],
- "New child name: ",
- T.input(type="text", name="name"), " ",
- "URI of new child: ",
- T.input(type="text", name="uri"), " ",
- T.input(type="submit", value="Attach"),
- ]]
- return [T.div(class_="freeform-form")[mkdir],
- T.div(class_="freeform-form")[upload],
- T.div(class_="freeform-form")[mount],
- ]
-
- def build_overwrite(self, ctx, data):
- name, target = data
- if IMutableFileNode.providedBy(target) and not target.is_readonly():
- action="/uri/" + urllib.quote(target.get_uri())
- overwrite = T.form(action=action, method="post",
- enctype="multipart/form-data")[
- T.fieldset[
- T.input(type="hidden", name="t", value="overwrite"),
- T.input(type='hidden', name='name', value=name),
- T.input(type='hidden', name='when_done', value=url.here),
- T.legend(class_="freeform-form-label")["Overwrite"],
- "Choose new file: ",
- T.input(type="file", name="file", class_="freeform-input-file"),
- " ",
- T.input(type="submit", value="Overwrite")
- ]]
- return [T.div(class_="freeform-form")[overwrite],]
- else:
- return []
-
- def render_results(self, ctx, data):
- req = inevow.IRequest(ctx)
- return get_arg(req, "results", "")
-
-class WebDownloadTarget:
- implements(IDownloadTarget, IConsumer)
- def __init__(self, req, content_type, content_encoding, save_to_file):
- self._req = req
- self._content_type = content_type
- self._content_encoding = content_encoding
- self._opened = False
- self._producer = None
- self._save_to_file = save_to_file
-
- def registerProducer(self, producer, streaming):
- self._req.registerProducer(producer, streaming)
- def unregisterProducer(self):
- self._req.unregisterProducer()
-
- def open(self, size):
- self._opened = True
- self._req.setHeader("content-type", self._content_type)
- if self._content_encoding:
- self._req.setHeader("content-encoding", self._content_encoding)
- self._req.setHeader("content-length", str(size))
- 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"'
- % filename)
-
- def write(self, data):
- self._req.write(data)
- def close(self):
- self._req.finish()
-
- def fail(self, why):
- if self._opened:
- # The content-type is already set, and the response code
- # has already been sent, so we can't provide a clean error
- # indication. We can emit text (which a browser might interpret
- # as something else), and if we sent a Size header, they might
- # notice that we've truncated the data. Keep the error message
- # small to improve the chances of having our error response be
- # shorter than the intended results.
- #
- # We don't have a lot of options, unfortunately.
- self._req.write("problem during download\n")
- else:
- # We haven't written anything yet, so we can provide a sensible
- # error message.
- msg = str(why.type)
- msg.replace("\n", "|")
- self._req.setResponseCode(http.GONE, msg)
- self._req.setHeader("content-type", "text/plain")
- # TODO: HTML-formatted exception?
- self._req.write(str(why))
- self._req.finish()
-
- def register_canceller(self, cb):
- pass
- def finish(self):
- pass
-
-class FileDownloader(resource.Resource):
- isLeaf = True
- def __init__(self, filenode, name):
- assert (IFileNode.providedBy(filenode)
- or IMutableFileNode.providedBy(filenode))
- self._filenode = filenode
- self._name = name
-
- def render(self, req):
- gte = static.getTypeAndEncoding
- 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, ctype, encoding, save_to_file)
- d = self._filenode.download(wdt)
- # exceptions during download are handled by the WebDownloadTarget
- d.addErrback(lambda why: None)
- return server.NOT_DONE_YET
-
-class BlockingFileError(Exception):
- """We cannot auto-create a parent directory, because there is a file in
- the way"""
-class NoReplacementError(Exception):
- """There was already a child by that name, and you asked me to not replace it"""
-class NoLocalDirectoryError(Exception):
- """The localdir= directory didn't exist"""
-
-LOCALHOST = "127.0.0.1"
-
-class NeedLocalhostError:
- implements(inevow.IResource)
-
- def renderHTTP(self, ctx):
- req = inevow.IRequest(ctx)
- req.setResponseCode(http.FORBIDDEN)
- req.setHeader("content-type", "text/plain")
- return "localfile= or localdir= requires a local connection"
-
-class NeedAbsolutePathError:
- implements(inevow.IResource)
-
- def renderHTTP(self, ctx):
- req = inevow.IRequest(ctx)
- req.setResponseCode(http.FORBIDDEN)
- req.setHeader("content-type", "text/plain")
- return "localfile= or localdir= requires an absolute path"
-
-class LocalAccessDisabledError:
- implements(inevow.IResource)
-
- def renderHTTP(self, ctx):
- req = inevow.IRequest(ctx)
- req.setResponseCode(http.FORBIDDEN)
- req.setHeader("content-type", "text/plain")
- return "local file access is disabled"
-
-class WebError:
- implements(inevow.IResource)
- def __init__(self, response_code, errmsg):
- self._response_code = response_code
- self._errmsg = errmsg
-
- def renderHTTP(self, ctx):
- req = inevow.IRequest(ctx)
- req.setResponseCode(self._response_code)
- req.setHeader("content-type", "text/plain")
- return self._errmsg
-
-
-class LocalFileDownloader(resource.Resource):
- def __init__(self, filenode, local_filename):
- self._local_filename = local_filename
- IFileNode(filenode)
- self._filenode = filenode
-
- def render(self, req):
- target = download.FileName(self._local_filename)
- d = self._filenode.download(target)
- def _done(res):
- req.write(self._filenode.get_uri())
- req.finish()
- d.addCallback(_done)
- return server.NOT_DONE_YET
-
-
-class FileJSONMetadata(rend.Page):
- def __init__(self, filenode):
- self._filenode = filenode
-
- def renderHTTP(self, ctx):
- req = inevow.IRequest(ctx)
- req.setHeader("content-type", "text/plain")
- return self.renderNode(self._filenode)
-
- def renderNode(self, filenode):
- file_uri = filenode.get_uri()
- data = ("filenode",
- {'ro_uri': file_uri,
- 'size': filenode.get_size(),
- })
- return simplejson.dumps(data, indent=1)
-
-class FileURI(FileJSONMetadata):
- def renderNode(self, filenode):
- file_uri = filenode.get_uri()
- return file_uri
-
-class FileReadOnlyURI(FileJSONMetadata):
- def renderNode(self, filenode):
- if filenode.is_readonly():
- return filenode.get_uri()
- else:
- return filenode.get_readonly().get_uri()
-
-class DirnodeWalkerMixin:
- """Visit all nodes underneath (and including) the rootnode, one at a
- time. For each one, call the visitor. The visitor will see the
- IDirectoryNode before it sees any of the IFileNodes inside. If the
- visitor returns a Deferred, I do not call the visitor again until it has
- fired.
- """
-
-## def _walk_if_we_could_use_generators(self, rootnode, rootpath=()):
-## # this is what we'd be doing if we didn't have the Deferreds and
-## # thus could use generators
-## yield rootpath, rootnode
-## for childname, childnode in rootnode.list().items():
-## childpath = rootpath + (childname,)
-## if IFileNode.providedBy(childnode):
-## yield childpath, childnode
-## elif IDirectoryNode.providedBy(childnode):
-## for res in self._walk_if_we_could_use_generators(childnode,
-## childpath):
-## yield res
-
- def walk(self, rootnode, visitor, rootpath=()):
- d = rootnode.list()
- def _listed(listing):
- return listing.items()
- d.addCallback(_listed)
- d.addCallback(self._handle_items, visitor, rootpath)
- return d
-
- def _handle_items(self, items, visitor, rootpath):
- if not items:
- return
- childname, (childnode, metadata) = items[0]
- childpath = rootpath + (childname,)
- d = defer.maybeDeferred(visitor, childpath, childnode, metadata)
- if IDirectoryNode.providedBy(childnode):
- d.addCallback(lambda res: self.walk(childnode, visitor, childpath))
- d.addCallback(lambda res:
- self._handle_items(items[1:], visitor, rootpath))
- return d
-
-class LocalDirectoryDownloader(resource.Resource, DirnodeWalkerMixin):
- def __init__(self, dirnode, localdir):
- self._dirnode = dirnode
- 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)
- elif IFileNode.providedBy(node):
- target = download.FileName(localfile)
- return node.download(target)
-
- def render(self, req):
- d = self.walk(self._dirnode, self._handle)
- def _done(res):
- req.setHeader("content-type", "text/plain")
- return "operation complete"
- d.addCallback(_done)
- return d
-
-class DirectoryJSONMetadata(rend.Page):
- def __init__(self, dirnode):
- self._dirnode = dirnode
-
- def renderHTTP(self, ctx):
- req = inevow.IRequest(ctx)
- req.setHeader("content-type", "text/plain")
- return self.renderNode(self._dirnode)
-
- def renderNode(self, node):
- d = node.list()
- def _got(children):
- kids = {}
- for name, (childnode, metadata) in children.iteritems():
- if IFileNode.providedBy(childnode):
- kiduri = childnode.get_uri()
- kiddata = ("filenode",
- {'ro_uri': kiduri,
- 'size': childnode.get_size(),
- 'metadata': metadata,
- })
- else:
- assert IDirectoryNode.providedBy(childnode), (childnode, children,)
- kiddata = ("dirnode",
- {'ro_uri': childnode.get_readonly_uri(),
- 'metadata': metadata,
- })
- if not childnode.is_readonly():
- kiddata[1]['rw_uri'] = childnode.get_uri()
- kids[name] = kiddata
- contents = { 'children': kids,
- 'ro_uri': node.get_readonly_uri(),
- }
- if not node.is_readonly():
- contents['rw_uri'] = node.get_uri()
- data = ("dirnode", contents)
- return simplejson.dumps(data, indent=1)
- d.addCallback(_got)
- return d
-
-class DirectoryURI(DirectoryJSONMetadata):
- def renderNode(self, node):
- return node.get_uri()
-
-class DirectoryReadonlyURI(DirectoryJSONMetadata):
- def renderNode(self, node):
- return node.get_readonly_uri()
-
-class RenameForm(rend.Page):
- addSlash = True
- docFactory = getxmlfile("rename-form.xhtml")
-
- def __init__(self, rootname, dirnode, dirpath):
- self._rootname = rootname
- self._dirnode = dirnode
- self._dirpath = dirpath
-
- def dirpath_as_string(self):
- return "/" + "/".join(self._dirpath)
-
- def render_title(self, ctx, data):
- return ctx.tag["Directory '%s':" % self.dirpath_as_string()]
-
- def render_header(self, ctx, data):
- parent_directories = ("<%s>" % self._rootname,) + self._dirpath
- num_dirs = len(parent_directories)
-
- header = [ "Rename in directory '",
- "<%s>/" % self._rootname,
- "/".join(self._dirpath),
- "':", ]
-
- if self._dirnode.is_readonly():
- header.append(" (readonly)")
- return ctx.tag[header]
-
- def render_when_done(self, ctx, data):
- return T.input(type="hidden", name="when_done", value=url.here)
-
- def render_get_name(self, ctx, data):
- req = inevow.IRequest(ctx)
- name = get_arg(req, "name", "")
- ctx.tag.attributes['value'] = name
- return ctx.tag
-
-class POSTHandler(rend.Page):
- def __init__(self, node, replace):
- self._node = node
- self._replace = replace
-
- def _check_replacement(self, name):
- if self._replace:
- return defer.succeed(None)
- d = self._node.has_child(name)
- def _got(present):
- if present:
- raise NoReplacementError("There was already a child by that "
- "name, and you asked me to not "
- "replace it.")
- return None
- d.addCallback(_got)
- return d
- def _POST_mkdir(self, name):
- d = self._check_replacement(name)
- d.addCallback(lambda res: self._node.create_empty_directory(name))
- d.addCallback(lambda res: "directory created")
- return d
-
- def _POST_mkdir_p(self, path):
- path_ = tuple([seg.decode("utf-8") for seg in path.split('/') if seg ])
- d = self._get_or_create_directories(self._node, path_)
- d.addCallback(lambda node: node.get_uri())
- return d
-
- # this code stolen from PUTHandler: should be refactored to a more
- # generally accesible place, perhaps...
- 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(KeyError)
- return node.create_empty_directory(path[0])
- d.addErrback(_maybe_create)
- d.addCallback(self._get_or_create_directories, path[1:])
- return d
-
- def _POST_uri(self, name, newuri):
- d = self._check_replacement(name)
- d.addCallback(lambda res: self._node.set_uri(name, newuri))
- d.addCallback(lambda res: newuri)
- return d
-
- def _POST_delete(self, name):
- if name is None:
- # apparently an <input type="hidden" name="name" value="">
- # won't show up in the resulting encoded form.. the 'name'
- # field is completely missing. So to allow deletion of an
- # empty file, we have to pretend that None means ''. The only
- # downide of this is a slightly confusing error message if
- # someone does a POST without a name= field. For our own HTML
- # thisn't a big deal, because we create the 'delete' POST
- # buttons ourselves.
- name = ''
- d = self._node.delete(name)
- d.addCallback(lambda res: "thing deleted")
- return d
-
- def _POST_rename(self, name, from_name, to_name):
- d = self._check_replacement(to_name)
- d.addCallback(lambda res: self._node.get(from_name))
- def add_dest(child):
- uri = child.get_uri()
- # now actually do the rename
- return self._node.set_uri(to_name, uri)
- d.addCallback(add_dest)
- def rm_src(junk):
- return self._node.delete(from_name)
- d.addCallback(rm_src)
- d.addCallback(lambda res: "thing renamed")
- return d
-
- def _POST_upload(self, contents, name, mutable, client):
- if mutable:
- # SDMF: files are small, and we can only upload data.
- contents.file.seek(0)
- data = contents.file.read()
- #uploadable = FileHandle(contents.file)
- d = self._check_replacement(name)
- d.addCallback(lambda res: self._node.has_child(name))
- def _checked(present):
- if present:
- # modify the existing one instead of creating a new
- # one
- d2 = self._node.get(name)
- def _got_newnode(newnode):
- d3 = newnode.overwrite(data)
- d3.addCallback(lambda res: newnode.get_uri())
- return d3
- d2.addCallback(_got_newnode)
- else:
- d2 = client.create_mutable_file(data)
- def _uploaded(newnode):
- d1 = self._node.set_node(name, newnode)
- d1.addCallback(lambda res: newnode.get_uri())
- return d1
- d2.addCallback(_uploaded)
- return d2
- d.addCallback(_checked)
- else:
- uploadable = FileHandle(contents.file, convergence=client.convergence)
- d = self._check_replacement(name)
- d.addCallback(lambda res: self._node.add_file(name, uploadable))
- def _done(newnode):
- return newnode.get_uri()
- d.addCallback(_done)
- return d
-
- def _POST_overwrite(self, contents):
- # SDMF: files are small, and we can only upload data.
- contents.file.seek(0)
- data = contents.file.read()
- # TODO: 'name' handling needs review
- d = defer.succeed(self._node)
- def _got_child_overwrite(child_node):
- child_node.overwrite(data)
- return child_node.get_uri()
- d.addCallback(_got_child_overwrite)
- return d
-
- def _POST_check(self, name):
- d = self._node.get(name)
- def _got_child_check(child_node):
- d2 = child_node.check()
- def _done(res):
- log.msg("checked %s, results %s" % (child_node, res),
- facility="tahoe.webish", level=log.NOISY)
- return str(res)
- d2.addCallback(_done)
- return d2
- d.addCallback(_got_child_check)
- return d
-
- def _POST_set_children(self, children):
- cs = []
- for name, (file_or_dir, mddict) in children.iteritems():
- cap = str(mddict.get('rw_uri') or mddict.get('ro_uri'))
- cs.append((name, cap, mddict.get('metadata')))
-
- d = self._node.set_children(cs)
- d.addCallback(lambda res: "Okay so I did it.")
- return d
-
- def renderHTTP(self, ctx):
- req = inevow.IRequest(ctx)
-
- 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)
- req.setHeader("content-type", "text/plain")
- 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
-
- when_done = get_arg(req, "when_done", None)
- if not boolean_of_arg(get_arg(req, "replace", "true")):
- self._replace = False
-
- if t == "mkdir":
- if not name:
- raise RuntimeError("mkdir requires a name")
- d = self._POST_mkdir(name)
- elif t == "mkdir-p":
- path = get_arg(req, "path")
- if not path:
- raise RuntimeError("mkdir-p requires a path")
- d = self._POST_mkdir_p(path)
- elif t == "uri":
- if not name:
- raise RuntimeError("set-uri requires a name")
- newuri = get_arg(req, "uri")
- assert newuri is not None
- d = self._POST_uri(name, newuri)
- elif t == "delete":
- d = self._POST_delete(name)
- elif t == "rename":
- 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 from_name or not to_name:
- raise RuntimeError("rename requires from_name and to_name")
- if not IDirectoryNode.providedBy(self._node):
- raise RuntimeError("rename must only be called on directories")
- for k,v in [ ('from_name', from_name), ('to_name', to_name) ]:
- if v and "/" in v:
- req.setResponseCode(http.BAD_REQUEST)
- req.setHeader("content-type", "text/plain")
- return "%s= may not contain a slash" % (k,)
- d = self._POST_rename(name, from_name, to_name)
- 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)
- mutable = boolean_of_arg(get_arg(req, "mutable", "false"))
- d = self._POST_upload(contents, name, mutable, IClient(ctx))
- elif t == "overwrite":
- contents = req.fields["file"]
- d = self._POST_overwrite(contents)
- elif t == "check":
- d = self._POST_check(name)
- elif t == "set_children":
- req.content.seek(0)
- body = req.content.read()
- try:
- children = simplejson.loads(body)
- except ValueError, le:
- le.args = tuple(le.args + (body,))
- # TODO test handling of bad JSON
- raise
- d = self._POST_set_children(children)
- else:
- req.setResponseCode(http.BAD_REQUEST)
- req.setHeader("content-type", "text/plain")
- return "BAD t=%s" % t
- if when_done:
- d.addCallback(lambda res: url.URL.fromString(when_done))
- def _check_replacement(f):
- # TODO: make this more human-friendly: maybe send them to the
- # when_done page but with an extra query-arg that will display
- # the error message in a big box at the top of the page. The
- # directory page that when_done= usually points to accepts a
- # result= argument.. use that.
- f.trap(NoReplacementError)
- req.setResponseCode(http.CONFLICT)
- req.setHeader("content-type", "text/plain")
- return str(f.value)
- d.addErrback(_check_replacement)
- return d
-
-class DELETEHandler(rend.Page):
- def __init__(self, node, name):
- self._node = node
- self._name = name
-
- def renderHTTP(self, ctx):
- req = inevow.IRequest(ctx)
- d = self._node.delete(self._name)
- def _done(res):
- # what should this return??
- 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.encode("utf-8")
- d.addErrback(_trap_missing)
- return d
-
-class PUTHandler(rend.Page):
- def __init__(self, node, path, t, localfile, localdir, replace):
- self._node = node
- self._path = path
- self._t = t
- self._localfile = localfile
- self._localdir = localdir
- self._replace = replace
-
- def renderHTTP(self, ctx):
- client = IClient(ctx)
- req = inevow.IRequest(ctx)
- t = self._t
- localfile = self._localfile
- localdir = self._localdir
-
- if t == "upload" and not (localfile or localdir):
- req.setResponseCode(http.BAD_REQUEST)
- req.setHeader("content-type", "text/plain")
- return "t=upload requires localfile= or localdir="
-
- # we must traverse the path, creating new directories as necessary
- d = self._get_or_create_directories(self._node, self._path[:-1])
- name = self._path[-1]
- d.addCallback(self._check_replacement, name, self._replace)
- if t == "upload":
- if localfile:
- d.addCallback(self._upload_localfile, localfile, name, convergence=client.convergence)
- else:
- # localdir
- # take the last step
- d.addCallback(self._get_or_create_directories, self._path[-1:])
- d.addCallback(self._upload_localdir, localdir, convergence=client.convergence)
- elif t == "uri":
- d.addCallback(self._attach_uri, req.content, name)
- elif t == "mkdir":
- d.addCallback(self._mkdir, name)
- else:
- d.addCallback(self._upload_file, req.content, name, convergence=client.convergence)
-
- def _transform_error(f):
- errors = {BlockingFileError: http.BAD_REQUEST,
- NoReplacementError: http.CONFLICT,
- NoLocalDirectoryError: http.BAD_REQUEST,
- }
- for k,v in errors.items():
- if f.check(k):
- req.setResponseCode(v)
- req.setHeader("content-type", "text/plain")
- return str(f.value)
- return f
- d.addErrback(_transform_error)
- 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(KeyError)
- return node.create_empty_directory(path[0])
- d.addErrback(_maybe_create)
- d.addCallback(self._get_or_create_directories, path[1:])
- return d
-
- def _check_replacement(self, node, name, replace):
- if replace:
- return node
- d = node.has_child(name)
- def _got(present):
- if present:
- raise NoReplacementError("There was already a child by that "
- "name, and you asked me to not "
- "replace it.")
- return node
- d.addCallback(_got)
- return d
-
- def _mkdir(self, node, name):
- d = node.create_empty_directory(name)
- def _done(newnode):
- return newnode.get_uri()
- d.addCallback(_done)
- return d
-
- def _upload_file(self, node, contents, name, convergence):
- uploadable = FileHandle(contents, convergence=convergence)
- d = node.add_file(name, uploadable)
- def _done(filenode):
- log.msg("webish upload complete",
- facility="tahoe.webish", level=log.NOISY)
- return filenode.get_uri()
- d.addCallback(_done)
- return d
-
- def _upload_localfile(self, node, localfile, name, convergence):
- uploadable = FileName(localfile, convergence=convergence)
- d = node.add_file(name, uploadable)
- d.addCallback(lambda filenode: filenode.get_uri())
- return d
-
- def _attach_uri(self, parentnode, contents, name):
- newuri = contents.read().strip()
- d = parentnode.set_uri(name, newuri)
- def _done(res):
- return newuri
- d.addCallback(_done)
- return d
-
- def _upload_localdir(self, node, localdir, convergence):
- # 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
- if not os.path.exists(localdir):
- msg = "%s doesn't exist!" % localdir
- raise NoLocalDirectoryError(msg)
- for root, dirs, files in os.walk(localdir):
- if root == localdir:
- path = ()
- else:
- relative_root = root[len(localdir)+1:]
- path = tuple(relative_root.split(os.sep))
- for d in dirs:
- 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:
- 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:
- d.addCallback(self._makedir, node, dir)
- for f in all_files:
- d.addCallback(self._upload_one_file, node, localdir, f, convergence=convergence)
- return d
-
- def _makedir(self, res, node, dir):
- d = defer.succeed(None)
- # get the parent. As long as os.walk gives us parents before
- # children, this ought to work
- d.addCallback(lambda res: node.get_child_at_path(dir[:-1]))
- # then create the child directory
- d.addCallback(lambda parent: parent.create_empty_directory(dir[-1]))
- return d
-
- def _upload_one_file(self, res, node, localdir, f, convergence):
- # get the parent. We can be sure this exists because we already
- # went through and created all the directories we require.
- localfile = os.path.join(localdir, *f)
- d = node.get_child_at_path(f[:-1])
- d.addCallback(self._upload_localfile, localfile, f[-1], convergence=convergence)
- return d
-
-
-class Manifest(rend.Page):
- docFactory = getxmlfile("manifest.xhtml")
- def __init__(self, dirnode, dirpath):
- self._dirnode = dirnode
- self._dirpath = dirpath
-
- def dirpath_as_string(self):
- return "/" + "/".join(self._dirpath)
-
- def render_title(self, ctx):
- return T.title["Manifest of %s" % self.dirpath_as_string()]
-
- def render_header(self, ctx):
- return T.p["Manifest of %s" % self.dirpath_as_string()]
-
- def data_items(self, ctx, data):
- return self._dirnode.build_manifest()
-
- def render_row(self, ctx, refresh_cap):
- ctx.fillSlots("refresh_capability", refresh_cap)
- return ctx.tag
-
-class DeepSize(rend.Page):
-
- def __init__(self, dirnode, dirpath):
- self._dirnode = dirnode
- self._dirpath = dirpath
-
- def renderHTTP(self, ctx):
- inevow.IRequest(ctx).setHeader("content-type", "text/plain")
- d = self._dirnode.build_manifest()
- def _measure_size(manifest):
- total = 0
- for verifiercap in manifest:
- u = from_string_verifier(verifiercap)
- if isinstance(u, CHKFileVerifierURI):
- total += u.size
- return str(total)
- d.addCallback(_measure_size)
- return d
-
-class DeepStats(rend.Page):
-
- def __init__(self, dirnode, dirpath):
- self._dirnode = dirnode
- self._dirpath = dirpath
-
- def renderHTTP(self, ctx):
- inevow.IRequest(ctx).setHeader("content-type", "text/plain")
- d = self._dirnode.deep_stats()
- d.addCallback(simplejson.dumps, indent=1)
- return d
-
-class ChildError:
- implements(inevow.IResource)
- def renderHTTP(self, ctx):
- req = inevow.IRequest(ctx)
- req.setResponseCode(http.BAD_REQUEST)
- req.setHeader("content-type", "text/plain")
- return self.text
-
-def child_error(text):
- ce = ChildError()
- ce.text = text
- return ce, ()
-
-class VDrive(rend.Page):
-
- def __init__(self, node, name):
- self.node = node
- self.name = name
-
- def get_child_at_path(self, path):
- if path:
- return self.node.get_child_at_path(path)
- return defer.succeed(self.node)
-
- def locateChild(self, ctx, segments):
- req = inevow.IRequest(ctx)
- method = req.method
- path = tuple([seg.decode("utf-8") for seg in segments])
-
- t = get_arg(req, "t", "")
- localfile = get_arg(req, "localfile", None)
- if localfile is not None:
- if localfile != os.path.abspath(localfile):
- return NeedAbsolutePathError(), ()
- localdir = get_arg(req, "localdir", None)
- if localdir is not None:
- if localdir != os.path.abspath(localdir):
- return NeedAbsolutePathError(), ()
- if localfile or localdir:
- if not ILocalAccess(ctx).local_access_is_allowed():
- return LocalAccessDisabledError(), ()
- if req.getHost().host != LOCALHOST:
- return NeedLocalhostError(), ()
- # TODO: think about clobbering/revealing config files and node secrets
-
- replace = boolean_of_arg(get_arg(req, "replace", "true"))
-
- if method == "GET":
- # the node must exist, and our operation will be performed on the
- # node itself.
- d = self.get_child_at_path(path)
- def file_or_dir(node):
- if (IFileNode.providedBy(node)
- or IMutableFileNode.providedBy(node)):
- filename = "unknown"
- if path:
- filename = path[-1]
- filename = get_arg(req, "filename", filename)
- if t == "download":
- if localfile:
- # write contents to a local file
- return LocalFileDownloader(node, localfile), ()
- # send contents as the result
- return FileDownloader(node, filename), ()
- elif t == "":
- # send contents as the result
- return FileDownloader(node, filename), ()
- elif t == "json":
- return FileJSONMetadata(node), ()
- elif t == "uri":
- return FileURI(node), ()
- elif t == "readonly-uri":
- return FileReadOnlyURI(node), ()
- else:
- return child_error("bad t=%s" % t)
- elif IDirectoryNode.providedBy(node):
- if t == "download":
- if localdir:
- # recursive download to a local directory
- return LocalDirectoryDownloader(node, localdir), ()
- return child_error("t=download requires localdir=")
- elif t == "":
- # send an HTML representation of the directory
- return Directory(self.name, node, path), ()
- elif t == "json":
- return DirectoryJSONMetadata(node), ()
- elif t == "uri":
- return DirectoryURI(node), ()
- elif t == "readonly-uri":
- return DirectoryReadonlyURI(node), ()
- elif t == "manifest":
- return Manifest(node, path), ()
- elif t == "deep-size":
- return DeepSize(node, path), ()
- elif t == "deep-stats":
- return DeepStats(node, path), ()
- elif t == 'rename-form':
- return RenameForm(self.name, node, path), ()
- else:
- return child_error("bad t=%s" % t)
- else:
- return child_error("unknown node type")
- d.addCallback(file_or_dir)
- elif method == "POST":
- # the node must exist, and our operation will be performed on the
- # node itself.
- d = self.get_child_at_path(path)
- def _got_POST(node):
- return POSTHandler(node, replace), ()
- d.addCallback(_got_POST)
- elif method == "DELETE":
- # the node must exist, and our operation will be performed on its
- # parent node.
- assert path # you can't delete the root
- d = self.get_child_at_path(path[:-1])
- def _got_DELETE(node):
- return DELETEHandler(node, path[-1]), ()
- d.addCallback(_got_DELETE)
- elif method in ("PUT",):
- # the node may or may not exist, and our operation may involve
- # all the ancestors of the node.
- return PUTHandler(self.node, path, t, localfile, localdir, replace), ()
- else:
- return rend.NotFound
- return d
-
-class Root(rend.Page):
-
- addSlash = True
- docFactory = getxmlfile("welcome.xhtml")
-
- def locateChild(self, ctx, segments):
- client = IClient(ctx)
- req = inevow.IRequest(ctx)
-
- if not segments:
- return rend.Page.locateChild(self, ctx, segments)
-
- if segments[0] == "file":
- if len(segments) < 2:
- return rend.Page.locateChild(self, ctx, segments)
- filecap = segments[1]
- node = client.create_node_from_uri(filecap)
- name = segments[-1]
- return FileDownloader(node, name), ()
-
- if segments[0] != "uri":
- return rend.Page.locateChild(self, ctx, segments)
-
- segments = list(segments)
- while segments and not segments[-1]:
- segments.pop()
- if not segments:
- segments.append('')
- segments = tuple(segments)
-
- if len(segments) == 1 or segments[1] == '':
- uri = get_arg(req, "uri", None)
- if uri is not None:
- there = url.URL.fromContext(ctx)
- there = there.clear("uri")
- there = there.child("uri").child(uri)
- return there, ()
-
- if len(segments) == 1:
- # /uri
- if req.method == "PUT":
- # either "PUT /uri" to create an unlinked file, or
- # "PUT /uri?t=mkdir" to create an unlinked directory
- t = get_arg(req, "t", "").strip()
- if t == "":
- mutable = bool(get_arg(req, "mutable", "").strip())
- if mutable:
- return unlinked.UnlinkedPUTSSKUploader(), ()
- else:
- return unlinked.UnlinkedPUTCHKUploader(), ()
- if t == "mkdir":
- return unlinked.UnlinkedPUTCreateDirectory(), ()
- errmsg = "/uri only accepts PUT and PUT?t=mkdir"
- return WebError(http.BAD_REQUEST, errmsg), ()
-
- elif req.method == "POST":
- # "POST /uri?t=upload&file=newfile" to upload an
- # unlinked file or "POST /uri?t=mkdir" to create a
- # new directory
- t = get_arg(req, "t", "").strip()
- if t in ("", "upload"):
- mutable = bool(get_arg(req, "mutable", "").strip())
- if mutable:
- return unlinked.UnlinkedPOSTSSKUploader(), ()
- else:
- return unlinked.UnlinkedPOSTCHKUploader(client, req), ()
- if t == "mkdir":
- return unlinked.UnlinkedPOSTCreateDirectory(), ()
- errmsg = "/uri accepts only PUT, PUT?t=mkdir, POST?t=upload, and POST?t=mkdir"
- return WebError(http.BAD_REQUEST, errmsg), ()
-
- if len(segments) < 2:
- return rend.NotFound
-
- uri = segments[1]
- d = defer.maybeDeferred(client.create_node_from_uri, uri)
- d.addCallback(lambda node: VDrive(node, 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
-
- child_webform_css = webform.defaultCSS
- child_tahoe_css = nevow_File(resource_filename('allmydata.web', 'tahoe.css'))
-
- child_provisioning = provisioning.ProvisioningTool()
- child_status = status.Status()
- child_helper_status = status.HelperStatus()
- child_statistics = status.Statistics()
-
- def data_version(self, ctx, data):
- return get_package_versions_string()
- def data_import_path(self, ctx, data):
- return str(allmydata)
- def data_my_nodeid(self, ctx, data):
- return idlib.nodeid_b2a(IClient(ctx).nodeid)
-
- def render_services(self, ctx, data):
- ul = T.ul()
- client = IClient(ctx)
- try:
- ss = client.getServiceNamed("storage")
- allocated_s = abbreviate_size(ss.allocated_size())
- allocated = "about %s allocated" % allocated_s
- sizelimit = "no size limit"
- if ss.sizelimit is not None:
- sizelimit = "size limit is %s" % abbreviate_size(ss.sizelimit)
- ul[T.li["Storage Server: %s, %s" % (allocated, sizelimit)]]
- except KeyError:
- ul[T.li["Not running storage server"]]
-
- try:
- h = client.getServiceNamed("helper")
- stats = h.get_stats()
- active_uploads = stats["chk_upload_helper.active_uploads"]
- ul[T.li["Helper: %d active uploads" % (active_uploads,)]]
- except KeyError:
- ul[T.li["Not running helper"]]
-
- return ctx.tag[ul]
-
- def data_introducer_furl(self, ctx, data):
- return IClient(ctx).introducer_furl
- def data_connected_to_introducer(self, ctx, data):
- if IClient(ctx).connected_to_introducer():
- return "yes"
- return "no"
-
- def data_helper_furl(self, ctx, data):
- try:
- uploader = IClient(ctx).getServiceNamed("uploader")
- except KeyError:
- return None
- furl, connected = uploader.get_helper_info()
- return furl
- def data_connected_to_helper(self, ctx, data):
- try:
- uploader = IClient(ctx).getServiceNamed("uploader")
- except KeyError:
- return "no" # we don't even have an Uploader
- furl, connected = uploader.get_helper_info()
- if connected:
- return "yes"
- return "no"
-
- def data_known_storage_servers(self, ctx, data):
- ic = IClient(ctx).introducer_client
- servers = [c
- for c in ic.get_all_connectors().values()
- if c.service_name == "storage"]
- return len(servers)
-
- def data_connected_storage_servers(self, ctx, data):
- ic = IClient(ctx).introducer_client
- return len(ic.get_all_connections_for("storage"))
-
- def data_services(self, ctx, data):
- ic = IClient(ctx).introducer_client
- c = [ (service_name, nodeid, rsc)
- for (nodeid, service_name), rsc
- in ic.get_all_connectors().items() ]
- c.sort()
- return c
-
- def render_service_row(self, ctx, data):
- (service_name, nodeid, rsc) = data
- ctx.fillSlots("peerid", "%s %s" % (idlib.nodeid_b2a(nodeid),
- rsc.nickname))
- if rsc.rref:
- rhost = rsc.remote_host
- if nodeid == IClient(ctx).nodeid:
- rhost_s = "(loopback)"
- elif isinstance(rhost, address.IPv4Address):
- rhost_s = "%s:%d" % (rhost.host, rhost.port)
- else:
- rhost_s = str(rhost)
- connected = "Yes: to " + rhost_s
- since = rsc.last_connect_time
- else:
- connected = "No"
- since = rsc.last_loss_time
-
- TIME_FORMAT = "%H:%M:%S %d-%b-%Y"
- ctx.fillSlots("connected", connected)
- ctx.fillSlots("since", time.strftime(TIME_FORMAT, time.localtime(since)))
- ctx.fillSlots("announced", time.strftime(TIME_FORMAT,
- time.localtime(rsc.announcement_time)))
- ctx.fillSlots("version", rsc.version)
- ctx.fillSlots("service_name", rsc.service_name)
-
- return ctx.tag
-
- def render_download_form(self, ctx, data):
- # this is a form where users can download files by URI
- form = T.form(action="uri", method="get",
- enctype="multipart/form-data")[
- T.fieldset[
- T.legend(class_="freeform-form-label")["Download a file"],
- "URI to download: ",
- T.input(type="text", name="uri"), " ",
- "Filename to download as: ",
- T.input(type="text", name="filename"), " ",
- T.input(type="submit", value="Download!"),
- ]]
- return T.div[form]
-
- def render_view_form(self, ctx, data):
- # this is a form where users can download files by URI, or jump to a
- # named directory
- form = T.form(action="uri", method="get",
- enctype="multipart/form-data")[
- T.fieldset[
- T.legend(class_="freeform-form-label")["View a file or directory"],
- "URI to view: ",
- T.input(type="text", name="uri"), " ",
- T.input(type="submit", value="View!"),
- ]]
- return T.div[form]
-
- def render_upload_form(self, ctx, data):
- # this is a form where users can upload unlinked files
- form = T.form(action="uri", method="post",
- enctype="multipart/form-data")[
- T.fieldset[
- T.legend(class_="freeform-form-label")["Upload a file"],
- "Choose a file: ",
- T.input(type="file", name="file", class_="freeform-input-file"),
- T.input(type="hidden", name="t", value="upload"),
- " Mutable?:", T.input(type="checkbox", name="mutable"),
- T.input(type="submit", value="Upload!"),
- ]]
- return T.div[form]
-
- def render_mkdir_form(self, ctx, data):
- # this is a form where users can create new directories
- form = T.form(action="uri", method="post",
- enctype="multipart/form-data")[
- T.fieldset[
- T.legend(class_="freeform-form-label")["Create a directory"],
- T.input(type="hidden", name="t", value="mkdir"),
- T.input(type="hidden", name="redirect_to_result", value="true"),
- T.input(type="submit", value="Create Directory!"),
- ]]
- return T.div[form]
-
-
-class LocalAccess:
- implements(ILocalAccess)
- def __init__(self):
- self.local_access = False
- def local_access_is_allowed(self):
- return self.local_access
class WebishServer(service.MultiService):
name = "webish"
- root_class = Root
+ root_class = root.Root
def __init__(self, webport, nodeurl_path=None):
service.MultiService.__init__(self)
self.root = self.root_class()
self.site = site = appserver.NevowSite(self.root)
self.site.requestFactory = MyRequest
- self.allow_local = LocalAccess()
- self.site.remember(self.allow_local, ILocalAccess)
s = strports.service(webport, site)
s.setServiceParent(self)
self.listener = s # stash it so the tests can query for the portnum
if nodeurl_path:
self._started.addCallback(self._write_nodeurl_file, nodeurl_path)
- def allow_local_access(self, enable=True):
- self.allow_local.local_access = enable
-
def startService(self):
service.MultiService.startService(self)
# to make various services available to render_* methods, we stash a
# I thought you could do the same with an existing interface, but
# apparently 'ISite' does not exist
#self.site._client = self.parent
+ self.site.remember(MyExceptionHandler(), inevow.ICanHandleException)
self._started.callback(None)
def _write_nodeurl_file(self, junk, nodeurl_path):