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_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 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 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 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 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) file_format = get_format(req, None) if file_format == "CHK": raise WebError( "format=CHK not currently accepted for POST /uri?t=mkdir", http.BAD_REQUEST) mt = None if file_format: mt = get_mutable_type(file_format) d = client.create_dirnode(version=mt) 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 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_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)
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_GET(self, ctx): req = IRequest(ctx) t = get_arg(req, "t", "").strip() 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_POST(self, req): t = get_arg(req, "t", "").strip() replace = boolean_of_arg(get_arg(req, "replace", "true")) if 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(req, self.client, replace) else: # t=mkdir is handled in DirectoryNodeHandler._POST_mkdir, so # there are no other t= values left to be handled by the # placeholder. raise WebError("POST to a file: bad t=%s" % t) return handle_when_done(req, d)
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 == "": file_format = get_format(req, "CHK") mutable_type = get_mutable_type(file_format) if mutable_type is not None: return unlinked.PUTUnlinkedSSK(req, self.client, mutable_type) 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)
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-with-children": d = self._POST_mkdir_with_children(req) elif t == "mkdir-immutable": d = self._POST_mkdir_immutable(req) elif t == "mkdir-p": # TODO: docs, tests d = self._POST_mkdir_p(req) elif t == "upload": d = self._POST_upload(ctx) # this one needs the context elif t == "uri": 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 == "start-deep-check": d = self._POST_start_deep_check(ctx) elif t == "stream-deep-check": d = self._POST_stream_deep_check(ctx) elif t == "start-manifest": d = self._POST_start_manifest(ctx) elif t == "start-deep-size": d = self._POST_start_deep_size(ctx) elif t == "start-deep-stats": d = self._POST_start_deep_stats(ctx) elif t == "stream-manifest": d = self._POST_stream_manifest(ctx) elif t == "set_children" or t == "set-children": 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 render_PUT(self, ctx): req = IRequest(ctx) t = get_arg(req, "t", "").strip() replace = parse_replace_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.replace_me_with_a_childcap(req, self.client, replace) # TODO: results return d raise WebError("PUT to a directory")
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() 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 == "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 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). """ # this is in case a URI like "/uri/?cap=<valid capability>" is # passed -- we re-direct to the non-trailing-slash version so # that there is just one valid URI for "uri" resource. if not name: u = DecodedURL.from_text(req.uri.decode('utf8')) u = u.replace( path=(s for s in u.path if s), # remove empty segments ) return redirectTo(u.to_uri().to_text().encode('utf8'), req) 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 render_POST(self, request): authorize(request, self.get_auth_token) request.setHeader("content-type", "application/json") nick = request.args.get("name", ["default"])[0] try: magic_folder = self.get_magic_folder(nick) except KeyError: raise WebError( "No such magic-folder '{}'".format(nick), 404, ) data = [] for item in magic_folder.uploader.get_status(): d = dict( path=item.relpath_u, status=item.status_history()[-1][0], kind='upload', ) for (status, ts) in item.status_history(): d[status + '_at'] = ts d['percent_done'] = item.progress.progress d['size'] = item.size data.append(d) for item in magic_folder.downloader.get_status(): d = dict( path=item.relpath_u, status=item.status_history()[-1][0], kind='download', ) for (status, ts) in item.status_history(): d[status + '_at'] = ts d['percent_done'] = item.progress.progress d['size'] = item.size data.append(d) return json.dumps(data)
def render_PUT(self, ctx): req = IRequest(ctx) t = get_arg(req, "t", "").strip() replace = parse_replace_arg(get_arg(req, "replace", "true")) if not t: if self.node.is_mutable(): return self.replace_my_contents(req) 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(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 render_POST(self, req): 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) return handle_when_done(req, d)
def post_json(self, req): req.setHeader("content-type", "application/json") nick = get_arg(req, 'name', 'default') try: magic_folder = self.client._magic_folders[nick] except KeyError: raise WebError( "No such magic-folder '{}'".format(nick), 404, ) data = [] for item in magic_folder.uploader.get_status(): d = dict( path=item.relpath_u, status=item.status_history()[-1][0], kind='upload', ) for (status, ts) in item.status_history(): d[status + '_at'] = ts d['percent_done'] = item.progress.progress d['size'] = item.size data.append(d) for item in magic_folder.downloader.get_status(): d = dict( path=item.relpath_u, status=item.status_history()[-1][0], kind='download', ) for (status, ts) in item.status_history(): d[status + '_at'] = ts d['percent_done'] = item.progress.progress d['size'] = item.size data.append(d) return json.dumps(data)
def render_GET(self, ctx): req = IRequest(ctx) t = get_arg(req, "t", "").strip() 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" 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 if t == "json": if self.parentnode and self.name: d = self.parentnode.get_metadata_for(self.name) else: d = defer.succeed(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_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(req, self.client) 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 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(NoSuchChildError) # 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_subdirectory(name) d.addCallback(make_handler_for, self.client, self.node, name) return d else: if DEBUG: print " terminal" # terminal node if (method,t) in [ ("POST","mkdir"), ("PUT","mkdir"), ("POST", "mkdir-with-children"), ("POST", "mkdir-immutable") ]: if DEBUG: print " making final directory" # final directory kids = {} if t in ("mkdir-with-children", "mkdir-immutable"): req.content.seek(0) kids_json = req.content.read() kids = convert_children_json(self.client.nodemaker, kids_json) file_format = get_format(req, None) mutable = True mt = get_mutable_type(file_format) if t == "mkdir-immutable": mutable = False d = self.node.create_subdirectory(name, kids, mutable=mutable, mutable_version=mt) d.addCallback(make_handler_for, self.client, 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.client, self.node, name) if DEBUG: print " 404" # otherwise, we just return a no-such-child error return f 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.client, self.node, name)
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)
def render_GET(self, req): raise WebError("/file must be followed by a file-cap and a name", http.NOT_FOUND)
def renderHTTP(self, ctx): req = IRequest(ctx) gte = static.getTypeAndEncoding ctype, encoding = gte(self.filename, static.File.contentTypes, static.File.contentEncodings, defaultType="text/plain") req.setHeader("content-type", ctype) if encoding: req.setHeader("content-encoding", encoding) if boolean_of_arg(get_arg(req, "save", "False")): # tell the browser to save the file rather display it we don't # try to encode the filename, instead we echo back the exact same # bytes we were given in the URL. See the comment in # FileNodeHandler.render_GET for the sad details. req.setHeader("content-disposition", 'attachment; filename="%s"' % self.filename) filesize = self.filenode.get_size() assert isinstance(filesize, (int, long)), filesize first, size = 0, None contentsize = filesize req.setHeader("accept-ranges", "bytes") if not self.filenode.is_mutable(): # TODO: look more closely at Request.setETag and how it interacts # with a conditional "if-etag-equals" request, I think this may # need to occur after the setResponseCode below si = self.filenode.get_storage_index() if si: req.setETag(base32.b2a(si)) # TODO: for mutable files, use the roothash. For LIT, hash the data. # or maybe just use the URI for CHK and LIT. rangeheader = req.getHeader('range') if rangeheader: ranges = self.parse_range_header(rangeheader) # ranges = None means the header didn't parse, so ignore # the header as if it didn't exist. If is more than one # range, then just return the first for now, until we can # generate multipart/byteranges. if ranges is not None: first, last = ranges[0] if first >= filesize: raise WebError('First beyond end of file', http.REQUESTED_RANGE_NOT_SATISFIABLE) else: first = max(0, first) last = min(filesize - 1, last) req.setResponseCode(http.PARTIAL_CONTENT) req.setHeader( 'content-range', "bytes %s-%s/%s" % (str(first), str(last), str(filesize))) contentsize = last - first + 1 size = contentsize req.setHeader("content-length", str(contentsize)) if req.method == "HEAD": return "" # Twisted >=9.0 throws an error if we call req.finish() on a closed # HTTP connection. It also has req.notifyFinish() to help avoid it. finished = [] def _request_finished(ign): finished.append(True) if hasattr(req, "notifyFinish"): req.notifyFinish().addBoth(_request_finished) d = self.filenode.read(req, first, size) def _finished(ign): if not finished: req.finish() def _error(f): lp = log.msg("error during GET", facility="tahoe.webish", failure=f, level=log.UNUSUAL, umid="xSiF3w") if finished: log.msg("but it's too late to tell them", parent=lp, level=log.UNUSUAL, umid="j1xIbw") return req._tahoe_request_had_error = f # for HTTP-style logging if req.startedWriting: # 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. req.write("problem during download\n") req.finish() else: # We haven't written anything yet, so we can provide a # sensible error message. eh = MyExceptionHandler() eh.renderHTTP_exception(ctx, f) d.addCallbacks(_finished, _error) return req.deferred
def render(self, req): gte = static.getTypeAndEncoding ctype, encoding = gte(self.filename, static.File.contentTypes, static.File.contentEncodings, defaultType="text/plain") req.setHeader("content-type", ctype) if encoding: req.setHeader("content-encoding", encoding) if boolean_of_arg(get_arg(req, "save", "False")): # tell the browser to save the file rather display it we don't # try to encode the filename, instead we echo back the exact same # bytes we were given in the URL. See the comment in # FileNodeHandler.render_GET for the sad details. req.setHeader("content-disposition", 'attachment; filename="%s"' % self.filename) filesize = self.filenode.get_size() assert isinstance(filesize, (int, long)), filesize first, size = 0, None contentsize = filesize req.setHeader("accept-ranges", "bytes") # TODO: for mutable files, use the roothash. For LIT, hash the data. # or maybe just use the URI for CHK and LIT. rangeheader = req.getHeader('range') if rangeheader: ranges = self.parse_range_header(rangeheader) # ranges = None means the header didn't parse, so ignore # the header as if it didn't exist. If is more than one # range, then just return the first for now, until we can # generate multipart/byteranges. if ranges is not None: first, last = ranges[0] if first >= filesize: raise WebError('First beyond end of file', http.REQUESTED_RANGE_NOT_SATISFIABLE) else: first = max(0, first) last = min(filesize - 1, last) req.setResponseCode(http.PARTIAL_CONTENT) req.setHeader( 'content-range', "bytes %s-%s/%s" % (str(first), str(last), str(filesize))) contentsize = last - first + 1 size = contentsize req.setHeader("content-length", b"%d" % contentsize) if req.method == "HEAD": return "" d = self.filenode.read(req, first, size) def _error(f): if f.check(defer.CancelledError): # The HTTP connection was lost and we no longer have anywhere # to send our result. Let this pass through. return f if req.startedWriting: # 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. return b"problem during download\n" else: # We haven't written anything yet, so we can provide a # sensible error message. return f d.addCallbacks( lambda ignored: None, _error, ) return d
def renderHTTP(self, ctx): raise WebError("/file must be followed by a file-cap and a name", http.NOT_FOUND)