def _POST_stream_deep_check(self, ctx): verify = boolean_of_arg(get_arg(ctx, "verify", "false")) repair = boolean_of_arg(get_arg(ctx, "repair", "false")) add_lease = boolean_of_arg(get_arg(ctx, "add-lease", "false")) walker = DeepCheckStreamer(ctx, self.node, verify, repair, add_lease) monitor = self.node.deep_traverse(walker) walker.setMonitor(monitor) # register to hear stopProducing. The walker ignores pauseProducing. IRequest(ctx).registerProducer(walker, True) d = monitor.when_done() def _done(res): IRequest(ctx).unregisterProducer() return res d.addBoth(_done) def _cancelled(f): f.trap(OperationCancelledError) return "Operation Cancelled" d.addErrback(_cancelled) def _error(f): # signal the error as a non-JSON "ERROR:" line, plus exception msg = "ERROR: %s(%s)\n" % (f.value.__class__.__name__, ", ".join([str(a) for a in f.value.args])) msg += str(f) return msg d.addErrback(_error) return d
def _POST_check(self, req): verify = boolean_of_arg(get_arg(req, "verify", "false")) repair = boolean_of_arg(get_arg(req, "repair", "false")) add_lease = boolean_of_arg(get_arg(req, "add-lease", "false")) if repair: d = self.node.check_and_repair(Monitor(), verify, add_lease) d.addCallback(self._maybe_literal, CheckAndRepairResultsRenderer) else: d = self.node.check(Monitor(), verify, add_lease) d.addCallback(self._maybe_literal, CheckResultsRenderer) return d
def _POST_start_deep_check(self, ctx): # check this directory and everything reachable from it if not get_arg(ctx, "ophandle"): raise NeedOperationHandleError("slow operation requires ophandle=") verify = boolean_of_arg(get_arg(ctx, "verify", "false")) repair = boolean_of_arg(get_arg(ctx, "repair", "false")) add_lease = boolean_of_arg(get_arg(ctx, "add-lease", "false")) if repair: monitor = self.node.start_deep_check_and_repair(verify, add_lease) renderer = DeepCheckAndRepairResultsRenderer(self.client, monitor) else: monitor = self.node.start_deep_check(verify, add_lease) renderer = DeepCheckResultsRenderer(self.client, monitor) return self._start_operation(monitor, renderer, ctx)
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 replace_me_with_a_formpost(self, req, client, replace): # create a new file, maybe mutable, maybe immutable mutable = boolean_of_arg(get_arg(req, "mutable", "false")) # create an immutable file contents = req.fields["file"] if mutable: arg = get_arg(req, "mutable-type", None) mutable_type = parse_mutable_type_arg(arg) if mutable_type is "invalid": raise WebError("Unknown type: %s" % arg, http.BAD_REQUEST) uploadable = MutableFileHandle(contents.file) d = client.create_mutable_file(uploadable, version=mutable_type) 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 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
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 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 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 replace_me_with_a_child(self, req, client, replace): # a new file is being uploaded in our place. mutable = boolean_of_arg(get_arg(req, "mutable", "false")) if mutable: arg = get_arg(req, "mutable-type", None) mutable_type = parse_mutable_type_arg(arg) if mutable_type is "invalid": raise WebError("Unknown type: %s" % arg, http.BAD_REQUEST) data = MutableFileHandle(req.content) d = client.create_mutable_file(data, version=mutable_type) def _uploaded(newnode): d2 = self.parentnode.set_node(self.name, newnode, overwrite=replace) d2.addCallback(lambda res: newnode) return d2 d.addCallback(_uploaded) else: 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, umid="TCjBGQ") 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_child(self, req, client, replace): # a new file is being uploaded in our place. mutable = boolean_of_arg(get_arg(req, "mutable", "false")) if mutable: req.content.seek(0) data = req.content.read() d = client.create_mutable_file(data) def _uploaded(newnode): d2 = self.parentnode.set_node(self.name, newnode, overwrite=replace) d2.addCallback(lambda res: newnode) return d2 d.addCallback(_uploaded) else: 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, umid="TCjBGQ") 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 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 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 _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
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
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")) kids = {} d = self.node.create_subdirectory(name, kids, overwrite=replace) d.addCallback(lambda child: child.get_uri()) # TODO: urlencode return 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 == "": 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_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 _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")) # 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 POSTUnlinkedCreateImmutableDirectory(req, client): # "POST /uri?t=mkdir", to create an unlinked directory. req.content.seek(0) kids_json = req.content.read() kids = convert_children_json(client.nodemaker, kids_json) d = client.create_immutable_dirnode(kids) 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 POSTUnlinkedCreateImmutableDirectory(req, client): # "POST /uri?t=mkdir", to create an unlinked directory. req.content.seek(0) kids_json = req.content.read() kids = convert_children_json(client.nodemaker, kids_json) d = client.create_immutable_dirnode(kids) redirect = get_arg(req, "redirect_to_result", "false") if boolean_of_arg(redirect): def _then_redir(res): new_url = "uri/" + urlquote(res.get_uri()) req.setResponseCode(http.SEE_OTHER) # 303 req.setHeader('location', new_url) return '' d.addCallback(_then_redir) else: d.addCallback(lambda dirnode: dirnode.get_uri()) return d
def replace_me_with_a_formpost(self, req, client, replace): # create a new file, maybe mutable, maybe immutable 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
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 == "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) 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): t = get_arg(req, b"t", b"").strip() replace = boolean_of_arg(get_arg(req, b"replace", b"true")) if t == b"check": d = self._POST_check(req) elif t == b"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" % str(t, "ascii")) 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 == "": mutable = boolean_of_arg(get_arg(req, "mutable", "false").strip()) if mutable: arg = get_arg(req, "mutable-type", None) version = parse_mutable_type_arg(arg) if version == "invalid": errmsg = "Unknown type: %s" % arg raise WebError(errmsg, http.BAD_REQUEST) return unlinked.PUTUnlinkedSSK(req, self.client, version) 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(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): 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 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") # 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 "" finished = [] def _request_finished(ign): finished.append(True) 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