def render_GET(self, req): """ Historically, accessing this via "GET /uri?uri=<capabilitiy>" was/is a feature -- which simply redirects to the more-common "GET /uri/<capability>" with any other query args preserved. New code should use "/uri/<cap>" """ uri_arg = req.args.get(b"uri", [None])[0] if uri_arg is None: raise WebError("GET /uri requires uri=") # shennanigans like putting "%2F" or just "/" itself, or ../ # etc in the <cap> might be a vector for weirdness so we # validate that this is a valid capability before proceeding. cap = uri.from_string(uri_arg) if isinstance(cap, uri.UnknownURI): raise WebError("Invalid capability") # so, using URL.from_text(req.uri) isn't going to work because # it seems Nevow was creating absolute URLs including # host/port whereas req.uri is absolute (but lacks host/port) redir_uri = URL.from_text(req.prePathURL().decode('utf8')) redir_uri = redir_uri.child(urllib.quote(uri_arg).decode('utf8')) # add back all the query args that AREN'T "?uri=" for k, values in req.args.items(): if k != b"uri": for v in values: redir_uri = redir_uri.add(k.decode('utf8'), v.decode('utf8')) return redirectTo(redir_uri.to_text().encode('utf8'), req)
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 _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") if from_name == to_name: return defer.succeed("redundant rename") # allow from_name to contain slashes, so they can fix names that were # accidentally created with them. But disallow them in to_name, to # discourage the practice. if "/" in to_name: raise WebError("to_name= may not contain a slash", 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 authorize(request, get_auth_token): if "token" in request.args: raise WebError( "Do not pass 'token' as URL argument", http.BAD_REQUEST, ) t = request.content.tell() request.content.seek(0) fields = cgi.FieldStorage( request.content, {k: vs[0] for (k, vs) in request.requestHeaders.getAllRawHeaders() }, environ={'REQUEST_METHOD': 'POST'}, ) request.content.seek(t) # not using get_arg() here because we *don't* want the token # argument to work if you passed it as a GET-style argument token = None if fields and 'token' in fields: token = fields['token'].value.strip() if not token: raise WebError("Missing token", http.UNAUTHORIZED) if not timing_safe_compare(token, get_auth_token()): raise WebError("Invalid token", http.UNAUTHORIZED)
def _POST_rename(self, req): # rename is identical to relink, but to_dir is not allowed # and to_name is required. if get_arg(req, "to_dir") is not None: raise WebError("to_dir= is not valid for rename") if get_arg(req, "to_name") is None: raise WebError("to_name= is required for rename") return self._POST_relink(req)
def childFactory(self, ctx, name): req = IRequest(ctx) if isinstance(self.node, ProhibitedNode): raise FileProhibited(self.node.reason) if should_create_intermediate_directories(req): raise WebError("Cannot create directory %s, because its " "parent is a file, not a directory" % quote_output(name, encoding='utf-8')) raise WebError("Files have no children, certainly not named %s" % quote_output(name, encoding='utf-8'))
def getChild(self, name, req): if req.method not in (b"GET", b"HEAD"): raise WebError("/file can only be used with GET or HEAD") # 'name' must be a file URI try: node = self.client.create_node_from_uri(name) except (TypeError, AssertionError): # I think this can no longer be reached raise WebError("%r is not a valid file- or directory- cap" % name) if not IFileNode.providedBy(node): raise WebError("%r is not a file-cap" % name) return filenode.FileNodeDownloadHandler(self.client, node)
def _POST_relink(self, req): charset = get_arg(req, "_charset", "utf-8") replace = parse_replace_arg(get_arg(req, "replace", "true")) 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) else: raise WebError("from_name= is required") 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) else: to_name = from_name # Disallow slashes in both from_name and to_name, that would only # cause confusion. if "/" in from_name: raise WebError("from_name= may not contain a slash", http.BAD_REQUEST) if "/" in to_name: raise WebError("to_name= may not contain a slash", http.BAD_REQUEST) to_dir = get_arg(req, "to_dir") if to_dir is not None and to_dir != self.node.get_write_uri(): to_dir = to_dir.strip() to_dir = to_dir.decode(charset) assert isinstance(to_dir, unicode) to_path = to_dir.split(u"/") to_root = self.client.nodemaker.create_from_cap(to_str(to_path[0])) if not IDirectoryNode.providedBy(to_root): raise WebError("to_dir is not a directory", http.BAD_REQUEST) d = to_root.get_child_at_path(to_path[1:]) else: d = defer.succeed(self.node) def _got_new_parent(new_parent): if not IDirectoryNode.providedBy(new_parent): raise WebError("to_dir is not a directory", http.BAD_REQUEST) return self.node.move_child_to(from_name, new_parent, to_name, replace) d.addCallback(_got_new_parent) d.addCallback(lambda res: "thing moved") return d
def render_PUT(self, req): t = get_arg(req, "t", "").strip() replace = parse_replace_arg(get_arg(req, "replace", "true")) assert self.parentnode and self.name if req.getHeader("content-range"): raise WebError("Content-Range in PUT not yet supported", http.NOT_IMPLEMENTED) if not t: return self.replace_me_with_a_child(req, self.client, replace) if t == "uri": return self.replace_me_with_a_childcap(req, self.client, replace) raise WebError("PUT to a file: bad t=%s" % t)
def POSTUnlinkedCreateDirectory(req, client): # "POST /uri?t=mkdir", to create an unlinked directory. ct = req.getHeader("content-type") or "" if not ct.startswith("multipart/form-data"): # guard against accidental attempts to call t=mkdir as if it were # t=mkdir-with-children, but make sure we tolerate the usual HTML # create-directory form (in which the t=mkdir and redirect_to_result= # and other arguments can be passed encoded as multipath/form-data, # in the request body). req.content.seek(0) kids_json = req.content.read() if kids_json: raise WebError( "t=mkdir does not accept children=, " "try t=mkdir-with-children instead", http.BAD_REQUEST) d = client.create_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 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(req) else: if not replace: raise ExistingChildError() assert self.parentnode and self.name d = self.replace_me_with_a_formpost(req, self.client, 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 render_POST(self, req): """ "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"): file_format = get_format(req) mutable_type = get_mutable_type(file_format) if mutable_type is not None: return unlinked.POSTUnlinkedSSK(req, self.client, mutable_type) else: return unlinked.POSTUnlinkedCHK(req, self.client) if t == "mkdir": return unlinked.POSTUnlinkedCreateDirectory(req, self.client) elif t == "mkdir-with-children": return unlinked.POSTUnlinkedCreateDirectoryWithChildren(req, self.client) elif t == "mkdir-immutable": return unlinked.POSTUnlinkedCreateImmutableDirectory(req, self.client) errmsg = ("/uri accepts only PUT, PUT?t=mkdir, POST?t=upload, " "and POST?t=mkdir") raise WebError(errmsg, http.BAD_REQUEST)
def render_GET(self, ctx): req = IRequest(ctx) # This is where all of the directory-related ?t=* code goes. t = get_arg(req, "t", "").strip() # t=info contains variable ophandles, t=rename-form contains the name # of the child being renamed. Neither is allowed an ETag. FIXED_OUTPUT_TYPES = ["", "json", "uri", "readonly-uri"] if not self.node.is_mutable() and t in FIXED_OUTPUT_TYPES: si = self.node.get_storage_index() if si and req.setETag('DIR:%s-%s' % (base32.b2a(si), t or "")): return "" if not t: # render the directory as HTML, using the docFactory and Nevow's # whole templating thing. return DirectoryAsHTML(self.node, self.client.mutable_file_default) if t == "json": return DirectoryJSONMetadata(ctx, self.node) if t == "info": return MoreInfo(self.node) if t == "uri": return DirectoryURI(ctx, self.node) if t == "readonly-uri": return DirectoryReadonlyURI(ctx, self.node) if t == 'rename-form': return RenameForm(self.node) raise WebError("GET directory: bad t=%s" % t)
def childFactory(self, ctx, name): ophandle = name if ophandle not in self.handles: raise WebError("unknown/expired handle '%s'" % escape(ophandle), NOT_FOUND) (monitor, renderer, when_added) = self.handles[ophandle] request = IRequest(ctx) t = get_arg(ctx, "t", "status") if t == "cancel" and request.method == "POST": monitor.cancel() # return the status anyways, but release the handle self._release_ophandle(ophandle) else: retain_for = get_arg(ctx, "retain-for", None) if retain_for is not None: self._set_timer(ophandle, int(retain_for)) if monitor.is_finished(): if boolean_of_arg( get_arg(ctx, "release-after-complete", "false")): self._release_ophandle(ophandle) if retain_for is None: # this GET is collecting the ophandle, so change its timer self._set_timer(ophandle, self.COLLECTED_HANDLE_LIFETIME) status = monitor.get_status() if isinstance(status, Failure): return defer.fail(status) return renderer
def render_HEAD(self, req): t = get_arg(req, "t", "").strip() if t: raise WebError("HEAD file: bad t=%s" % t) filename = get_arg(req, "filename", self.name) or "unknown" d = self.node.get_best_readable_version() d.addCallback(lambda dn: FileDownloader(dn, filename)) return d
def childFactory(self, ctx, name): # 'name' is expected to be a URI try: node = self.client.create_node_from_uri(name) return directory.make_handler_for(node, self.client) except (TypeError, AssertionError): raise WebError("'%s' is not a valid file- or directory- cap" % name)
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 _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 = parse_replace_arg(get_arg(req, "replace", "true")) # We mustn't pass childcap for the readcap argument because we don't # know whether it is a read cap. Passing a read cap as the writecap # argument will work (it ends up calling NodeMaker.create_from_cap, # which derives a readcap if necessary and possible). d = self.node.set_uri(name, childcap, None, overwrite=replace) d.addCallback(lambda res: childcap) return d
def render(self, req): if req.method != "POST": raise WebError("/report_incident can only be used with POST") log.msg(format="User reports incident through web page: %(details)s", details=get_arg(req, "details", ""), level=log.WEIRD, umid="LkD9Pw") req.setHeader("content-type", "text/plain; charset=UTF-8") return b"An incident report has been saved to logs/incidents/ in the node directory."
def render_GET(self, ctx): req = IRequest(ctx) t = get_arg(req, "t", "").strip() # t=info contains variable ophandles, so is not allowed an ETag. FIXED_OUTPUT_TYPES = ["", "json", "uri", "readonly-uri"] if not self.node.is_mutable() and t in FIXED_OUTPUT_TYPES: # if the client already has the ETag then we can # short-circuit the whole process. si = self.node.get_storage_index() if si and req.setETag('%s-%s' % (base32.b2a(si), t or "")): return "" if not t: # just get the contents # the filename arrives as part of the URL or in a form input # element, and will be sent back in a Content-Disposition header. # Different browsers use various character sets for this name, # sometimes depending upon how language environment is # configured. Firefox sends the equivalent of # urllib.quote(name.encode("utf-8")), while IE7 sometimes does # latin-1. Browsers cannot agree on how to interpret the name # they see in the Content-Disposition header either, despite some # 11-year old standards (RFC2231) that explain how to do it # properly. So we assume that at least the browser will agree # with itself, and echo back the same bytes that we were given. filename = get_arg(req, "filename", self.name) or "unknown" d = self.node.get_best_readable_version() d.addCallback(lambda dn: FileDownloader(dn, filename)) return d if t == "json": # We do this to make sure that fields like size and # mutable-type (which depend on the file on the grid and not # just on the cap) are filled in. The latter gets used in # tests, in particular. # # TODO: Make it so that the servermap knows how to update in # a mode specifically designed to fill in these fields, and # then update it in that mode. if self.node.is_mutable(): d = self.node.get_servermap(MODE_READ) else: d = defer.succeed(None) if self.parentnode and self.name: d.addCallback(lambda ignored: self.parentnode.get_metadata_for( self.name)) else: d.addCallback(lambda ignored: None) d.addCallback(lambda md: FileJSONMetadata(ctx, self.node, md)) return d if t == "info": return MoreInfo(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_PUT(self, ctx): req = IRequest(ctx) t = get_arg(req, "t", "").strip() replace = parse_replace_arg(get_arg(req, "replace", "true")) offset = parse_offset_arg(get_arg(req, "offset", None)) if not t: 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() if self.node.is_mutable(): # Are we a readonly filenode? We shouldn't allow callers # to try to replace us if we are. if self.node.is_readonly(): raise WebError("PUT to a mutable file: replace or update" " requested with read-only cap") if offset is None: return self.replace_my_contents(req) if offset >= 0: return self.update_my_contents(req, offset) raise WebError("PUT to a mutable file: Invalid offset") else: if offset is not None: raise WebError("PUT to a file: append operation invoked " "on an immutable cap") assert self.parentnode and self.name return self.replace_me_with_a_child(req, self.client, replace) if t == "uri": if not replace: raise ExistingChildError() assert self.parentnode and self.name return self.replace_me_with_a_childcap(req, self.client, replace) raise WebError("PUT to a file: bad t=%s" % t)
def _POST_upload(self, ctx): req = IRequest(ctx) charset = get_arg(req, "_charset", "utf-8") contents = req.fields["file"] assert contents.filename is None or isinstance(contents.filename, str) 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") assert isinstance(name, str) 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(NoSuchChildError) # create a placeholder which will see POST t=upload return PlaceHolderNodeHandler(self.client, self.node, name) else: node = node_or_failure return make_handler_for(node, self.client, 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 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 getChild(self, name, req): """ Most requests look like /uri/<cap> so this fetches the capability and creates and appropriate handler (depending on the kind of capability it was passed). """ try: node = self.client.create_node_from_uri(name) return directory.make_handler_for(node, self.client) except (TypeError, AssertionError): raise WebError( "'{}' is not a valid file- or directory- cap".format(name))
def childFactory(self, ctx, name): if not name: return self # /operation/$OPHANDLE/$STORAGEINDEX provides detailed information # about a specific file or directory that was checked si = base32.a2b(name) s = self.monitor.get_status() try: results = s.get_results_for_storage_index(si) return CheckAndRepairResultsRenderer(self.client, results) except KeyError: raise WebError("No detailed results for SI %s" % html.escape(name), http.NOT_FOUND)
def PUTUnlinkedCreateDirectory(req, client): # "PUT /uri?t=mkdir", to create an unlinked directory. file_format = get_format(req, None) if file_format == "CHK": raise WebError("format=CHK not accepted for PUT /uri?t=mkdir", http.BAD_REQUEST) mt = None if file_format: mt = get_mutable_type(file_format) d = client.create_dirnode(version=mt) d.addCallback(lambda dirnode: dirnode.get_uri()) # XXX add redirect_to_result return d
def render_HEAD(self, ctx): req = IRequest(ctx) t = get_arg(req, "t", "").strip() if t: raise WebError("GET file: bad t=%s" % t) filename = get_arg(req, "filename", self.name) or "unknown" if self.node.is_mutable(): # some day: d = self.node.get_best_version() d = makeMutableDownloadable(self.node) else: d = defer.succeed(self.node) d.addCallback(lambda dn: FileDownloader(dn, filename)) return d
def getChild(self, name, req): if not name: return self # /operation/$OPHANDLE/$STORAGEINDEX provides detailed information # about a specific file or directory that was checked si = base32.a2b(name) r = self.monitor.get_status() try: return CheckResultsRenderer(self._client, r.get_results_for_storage_index(si)) except KeyError: raise WebError( "No detailed results for SI %s" % html.escape(str(name, "utf-8")), http.NOT_FOUND)
def render_GET(self, ctx): req = IRequest(ctx) t = get_arg(req, "t", "").strip() if t == "info": return MoreInfo(self.node) if t == "json": is_parent_known_immutable = self.parentnode and not self.parentnode.is_mutable() if self.parentnode and self.name: d = self.parentnode.get_metadata_for(self.name) else: d = defer.succeed(None) d.addCallback(lambda md: UnknownJSONMetadata(ctx, self.node, md, is_parent_known_immutable)) return d raise WebError("GET unknown URI type: can only do t=info and t=json, not t=%s.\n" "Using a webapi server that supports a later version of Tahoe " "may help." % t)
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 = boolean_of_arg(get_arg(req, "mutable", "false").strip()) if mutable: return unlinked.PUTUnlinkedSSK(req, self.client) else: return unlinked.PUTUnlinkedCHK(req, self.client) if t == "mkdir": return unlinked.PUTUnlinkedCreateDirectory(req, self.client) errmsg = ("/uri accepts only PUT, PUT?t=mkdir, POST?t=upload, " "and POST?t=mkdir") raise WebError(errmsg, http.BAD_REQUEST)