def TestOneInput(data): fdp = atheris.FuzzedDataProvider(data) whttp.parse_content_range_header(fdp.ConsumeUnicode(100)) whttp.parse_range_header(fdp.ConsumeUnicode(100)) whttp.parse_set_header(fdp.ConsumeUnicode(100)) whttp.parse_etags(fdp.ConsumeUnicode(100)) whttp.parse_if_range_header(fdp.ConsumeUnicode(100)) whttp.parse_dict_header(fdp.ConsumeUnicode(100))
def database_item(database_id, item_id, suffix, session_id): """ """ range_header = request.headers.get("Range", None) if range_header: begin, end = http.parse_range_header(range_header).ranges[0] data, mimetype, total_length = provider.get_item( session_id, database_id, item_id, byte_range=(begin, end)) begin, end = (begin or 0), (end or total_length) # Setup response response = Response( data, 206, mimetype=mimetype, direct_passthrough=not isinstance(data, basestring)) response.headers["Content-Range"] = "bytes %d-%d/%d" % ( begin, end - 1, total_length) response.headers["Content-Length"] = end - begin else: data, mimetype, total_length = provider.get_item( session_id, database_id, item_id) # Setup response response = Response( data, 200, mimetype=mimetype, direct_passthrough=not isinstance(data, basestring)) response.headers["Content-Length"] = total_length return response
def read_range(request, read_data: Callable[[int, int], bytes]) -> None: """ Read an optional ``Range`` header, reads data appropriately via the given callable, writes the data to the request. Only parses a subset of ``Range`` headers that we support: must be set, bytes only, only a single range, the end must be explicitly specified. Raises a ``_HTTPError(http.REQUESTED_RANGE_NOT_SATISFIABLE)`` if parsing is not possible or the header isn't set. Takes a function that will do the actual reading given the start offset and a length to read. The resulting data is written to the request. """ if request.getHeader("range") is None: # Return the whole thing. start = 0 while True: # TODO should probably yield to event loop occasionally... # https://tahoe-lafs.org/trac/tahoe-lafs/ticket/3872 data = read_data(start, start + 65536) if not data: request.finish() return request.write(data) start += len(data) range_header = parse_range_header(request.getHeader("range")) if ( range_header is None # failed to parse or range_header.units != "bytes" or len(range_header.ranges) > 1 # more than one range or range_header.ranges[0][1] is None # range without end ): raise _HTTPError(http.REQUESTED_RANGE_NOT_SATISFIABLE) offset, end = range_header.ranges[0] # TODO limit memory usage # https://tahoe-lafs.org/trac/tahoe-lafs/ticket/3872 data = read_data(offset, end - offset) request.setResponseCode(http.PARTIAL_CONTENT) if len(data): # For empty bodies the content-range header makes no sense since # the end of the range is inclusive. request.setHeader( "content-range", ContentRange("bytes", offset, offset + len(data)).to_header(), ) request.write(data) request.finish()
def test_range_parsing(): rv = http.parse_range_header("bytes=52") assert rv is None rv = http.parse_range_header("bytes=52-") assert rv.units == "bytes" assert rv.ranges == [(52, None)] assert rv.to_header() == "bytes=52-" rv = http.parse_range_header("bytes=52-99") assert rv.units == "bytes" assert rv.ranges == [(52, 100)] assert rv.to_header() == "bytes=52-99" rv = http.parse_range_header("bytes=52-99,-1000") assert rv.units == "bytes" assert rv.ranges == [(52, 100), (-1000, None)] assert rv.to_header() == "bytes=52-99,-1000" rv = http.parse_range_header("bytes = 1 - 100") assert rv.units == "bytes" assert rv.ranges == [(1, 101)] assert rv.to_header() == "bytes=1-100" rv = http.parse_range_header("AWesomes=0-999") assert rv.units == "awesomes" assert rv.ranges == [(0, 1000)] assert rv.to_header() == "awesomes=0-999"
def test_range_parsing(): rv = http.parse_range_header('bytes=52') assert rv is None rv = http.parse_range_header('bytes=52-') assert rv.units == 'bytes' assert rv.ranges == [(52, None)] assert rv.to_header() == 'bytes=52-' rv = http.parse_range_header('bytes=52-99') assert rv.units == 'bytes' assert rv.ranges == [(52, 100)] assert rv.to_header() == 'bytes=52-99' rv = http.parse_range_header('bytes=52-99,-1000') assert rv.units == 'bytes' assert rv.ranges == [(52, 100), (-1000, None)] assert rv.to_header() == 'bytes=52-99,-1000' rv = http.parse_range_header('bytes = 1 - 100') assert rv.units == 'bytes' assert rv.ranges == [(1, 101)] assert rv.to_header() == 'bytes=1-100' rv = http.parse_range_header('AWesomes=0-999') assert rv.units == 'awesomes' assert rv.ranges == [(0, 1000)] assert rv.to_header() == 'awesomes=0-999'
def get_download_response(payload, content_length, content_format, filename, request=None): """ :param payload: File like object. :param content_length: Size of payload in bytes :param content_format: ``couchexport.models.Format`` instance :param filename: Name of the download :param request: The request. Used to determine if a range response should be given. :return: HTTP response """ ranges = None if request and "HTTP_RANGE" in request.META: try: ranges = parse_range_header(request.META['HTTP_RANGE'], content_length) except ValueError: pass if ranges and len(ranges.ranges) != 1: ranges = None response = StreamingHttpResponse(content_type=content_format.mimetype) if content_format.download: response['Content-Disposition'] = safe_filename_header(filename) response["Content-Length"] = content_length response["Accept-Ranges"] = "bytes" if ranges: start, stop = ranges.ranges[0] if stop is not None and stop > content_length: # requested range not satisfiable return HttpResponse(status=416) response.streaming_content = RangedFileWrapper(payload, start=start, stop=stop or float("inf")) end = stop or content_length response["Content-Range"] = "bytes %d-%d/%d" % (start, end - 1, content_length) response["Content-Length"] = end - start response.status_code = 206 else: response.streaming_content = FileWrapper(payload) return response
def sharedir_download(dname, rpath): rootpath = os.path.abspath(settings.SHAREDIR.directories.get(dname)) if not rootpath: raise NotFound apath = os.path.abspath(os.path.join(rootpath, rpath)) if (not apath.startswith(rootpath)) or (not os.path.isfile(apath)): raise NotFound def _opener(filename): return (open(filename, 'rb'), datetime.utcfromtimestamp(os.path.getmtime(filename)), int(os.path.getsize(filename))) guessed_type = mimetypes.guess_type(apath) mime_type = guessed_type[0] or 'application/octet-stream' headers = [] headers.append(('Content-Type', mime_type)) if request.range: range = request.range else: range = parse_range_header(request.environ.get('HTTP_RANGE')) #when request range,only recognize "bytes" as range units if range and range.units == "bytes": rbegin, rend = range.ranges[0] try: fsize = os.path.getsize(apath) except OSError as e: return Response("Not found", status=404) if (rbegin + 1) < fsize: if rend == None: rend = fsize - 1 headers.append(('Content-Length', str(rend - rbegin + 1))) headers.append( ('Content-Range', '%s %d-%d/%d' % (range.units, rbegin, rend, fsize))) return Response(FileIterator(apath, rbegin, rend), status=206, headers=headers, direct_passthrough=True) f, mtime, file_size = _opener(apath) return Response(wrap_file(request.environ, f), status=200, headers=headers, direct_passthrough=True)
def _process_range_request( self, environ: "WSGIEnvironment", complete_length: t.Optional[int] = None, accept_ranges: t.Optional[t.Union[bool, str]] = None, ) -> bool: """Handle Range Request related headers (RFC7233). If `Accept-Ranges` header is valid, and Range Request is processable, we set the headers as described by the RFC, and wrap the underlying response in a RangeWrapper. Returns ``True`` if Range Request can be fulfilled, ``False`` otherwise. :raises: :class:`~werkzeug.exceptions.RequestedRangeNotSatisfiable` if `Range` header could not be parsed or satisfied. .. versionchanged:: 2.0 Returns ``False`` if the length is 0. """ from ..exceptions import RequestedRangeNotSatisfiable if ( accept_ranges is None or complete_length is None or complete_length == 0 or not self._is_range_request_processable(environ) ): return False parsed_range = parse_range_header(environ.get("HTTP_RANGE")) if parsed_range is None: raise RequestedRangeNotSatisfiable(complete_length) range_tuple = parsed_range.range_for_length(complete_length) content_range_header = parsed_range.to_content_range_header(complete_length) if range_tuple is None or content_range_header is None: raise RequestedRangeNotSatisfiable(complete_length) content_length = range_tuple[1] - range_tuple[0] self.headers["Content-Length"] = content_length self.headers["Accept-Ranges"] = accept_ranges self.content_range = content_range_header # type: ignore self.status_code = 206 self._wrap_range_response(range_tuple[0], content_length) return True
def sendFile(aStrFileName, aErrorStatusCode=200): """发送文件到客户端,支持单 Range 请求 :aStrFileName: 文件名称 :returns: Response """ if not os.path.isfile(aStrFileName): log.logObject().error("找不到文件=%s" % aStrFileName) return buildErrorResponseData(kCmdUserError_NotResource, statusCode=aErrorStatusCode) try: httpHeaderRange = request.headers.get("Range") r = http.parse_range_header(httpHeaderRange) bHasRange = r is not None and len( r.ranges) == 1 and r.ranges[0][0] is not None except Exception as e: bHasRange = False #发送整个文件 if not bHasRange: return send_file(aStrFileName) #发送部分文件 nFileSize = os.path.getsize(aStrFileName) beginPos, stopPos = r.range_for_length(nFileSize) with open(aStrFileName, "rb") as f: f.seek(beginPos) byteContens = f.read(stopPos - beginPos) strMimeType = mimetypes.guess_type(aStrFileName)[0] if strMimeType is None: strMimeType = "application/octet-stream" resp = Response(byteContens, 206, mimetype=strMimeType, headers={ "Content-Range": r.make_content_range(nFileSize), "Accept-Ranges": r.units, "Etag": "%d" % nFileSize }) return resp
def sharedir_download(dname,rpath): rootpath = os.path.abspath(settings.SHAREDIR.directories.get(dname)) if not rootpath: raise NotFound apath = os.path.abspath(os.path.join(rootpath,rpath)) if (not apath.startswith(rootpath)) or (not os.path.isfile(apath)): raise NotFound def _opener(filename): return ( open(filename, 'rb'), datetime.utcfromtimestamp(os.path.getmtime(filename)), int(os.path.getsize(filename)) ) guessed_type = mimetypes.guess_type(apath) mime_type = guessed_type[0] or 'application/octet-stream' headers = [] headers.append(('Content-Type', mime_type)) if request.range: range = request.range else: range = parse_range_header(request.environ.get('HTTP_RANGE')) #when request range,only recognize "bytes" as range units if range and range.units=="bytes": rbegin,rend = range.ranges[0] try: fsize = os.path.getsize(apath) except OSError as e: return Response("Not found",status=404) if (rbegin+1)<fsize: if rend == None: rend = fsize-1 headers.append(('Content-Length',str(rend-rbegin+1))) headers.append(('Content-Range','%s %d-%d/%d' %(range.units,rbegin, rend, fsize))) return Response(FileIterator(apath,rbegin,rend), status=206, headers=headers, direct_passthrough=True) f, mtime, file_size = _opener(apath) return Response(wrap_file(request.environ, f), status=200, headers=headers, direct_passthrough=True)
def encode(path): headers = request.headers entry = current_app.nekumo.get_entry(path) encode = request.args.get('encode', 'chromecast') if not entry.exists(): raise NotFound ffmpeg_args = FfmpegEncode(entry.gateway_path) ffmpeg_args.set_encode(encode) b = parse_range_header(headers.get('Range')) if b and b.ranges[0][0]: ffmpeg_args.set_skip_initial_bytes(b.ranges[0][0]) p = ffmpeg_args.popen(True) def generate(): for row in iter(lambda: p.stdout.read(1024 * 8), ''): yield row return Response(generate(), mimetype='video/mp4' if encode == 'chromecast' else ffmpeg_args.fmt, headers={'Accept-Ranges': 'bytes'})
def collection_rule(*args, **kwargs): # Note: As an alternative, we could use the following instead of a # missing Range header: `Range('items', [(0, 500)])`. try: r = parse_range_header(request.headers["Range"]) except KeyError: raise ValidationError("Range required") if r is None or r.units != "items" or len(r.ranges) != 1: abort(416) begin, end = r.ranges[0] if not 0 <= begin < end: abort(416) end = min(end, begin + 500) kwargs.update(begin=begin, count=end - begin) total, response = rule(*args, **kwargs) if begin > total - 1: response.headers.add("Content-Range", ContentRange("items", None, None, total)) abort(416) end = min(end, total) response.headers.add("Content-Range", ContentRange("items", begin, end, total)) return response, 206
def database_item(database_id, item_id, suffix, session_id): """ """ range_header = request.headers.get("Range", None) if range_header: begin, end = http.parse_range_header(range_header).ranges[0] data, mimetype, total_length = provider.get_item( session_id, database_id, item_id, byte_range=(begin, end)) begin, end = (begin or 0), (end or total_length) # Setup response response = Response( data, 206, mimetype=mimetype, direct_passthrough=not isinstance(data, basestring)) # A streaming response with unknown content lenght, Range x-* # as per RFC2616 section 14.16 if total_length <= 0: response.headers["Content-Range"] = "bytes %d-%d/*" % ( begin, end - 1) elif total_length > 0: response.headers["Content-Range"] = "bytes %d-%d/%d" % ( begin, end - 1, total_length) response.headers["Content-Length"] = end - begin else: data, mimetype, total_length = provider.get_item( session_id, database_id, item_id) # Setup response response = Response( data, 200, mimetype=mimetype, direct_passthrough=not isinstance(data, basestring)) if total_length > 0: response.headers["Content-Length"] = total_length return response
def test_range_to_header(ranges): header = Range("byes", ranges).to_header() r = http.parse_range_header(header) assert r.ranges == ranges
def filedown(environ, filename, cache=True, cache_timeout=None, action=None, real_filename=None, x_sendfile=False, x_header_name=None, x_filename=None, fileobj=None, default_mimetype='application/octet-stream'): """ @param filename: is used for display in download @param real_filename: if used for the real file location @param x_urlfile: is only used in x-sendfile, and be set to x-sendfile header @param fileobj: if provided, then returned as file content @type fileobj: (fobj, mtime, size) filedown now support web server controlled download, you should set xsendfile=True, and add x_header, for example: nginx ('X-Accel-Redirect', '/path/to/local_url') apache ('X-Sendfile', '/path/to/local_url') """ from .common import safe_str from werkzeug.http import parse_range_header guessed_type = mimetypes.guess_type(filename) mime_type = guessed_type[0] or default_mimetype real_filename = real_filename or filename #make common headers headers = [] headers.append(('Content-Type', mime_type)) d_filename = _get_download_filename(environ, os.path.basename(filename)) if action == 'download': headers.append(('Content-Disposition', 'attachment; %s' % d_filename)) elif action == 'inline': headers.append(('Content-Disposition', 'inline; %s' % d_filename)) if x_sendfile: if not x_header_name or not x_filename: raise Exception("x_header_name or x_filename can't be empty") headers.append((x_header_name, safe_str(x_filename))) return Response('', status=200, headers=headers, direct_passthrough=True) else: request = environ.get('werkzeug.request') if request: range = request.range else: range = parse_range_header(environ.get('HTTP_RANGE')) #when request range,only recognize "bytes" as range units if range and range.units == "bytes": try: fsize = os.path.getsize(real_filename) except OSError as e: return Response("Not found", status=404) mtime = datetime.utcfromtimestamp(os.path.getmtime(real_filename)) mtime_str = http_date(mtime) if cache: etag = _generate_etag(mtime, fsize, real_filename) else: etag = mtime_str if_range = environ.get('HTTP_IF_RANGE') if if_range: check_if_range_ok = (if_range.strip('"') == etag) #print "check_if_range_ok (%s) = (%s ==%s)"%(check_if_range_ok,if_range.strip('"'),etag) else: check_if_range_ok = True rbegin, rend = range.ranges[0] if check_if_range_ok and (rbegin + 1) < fsize: if rend == None: rend = fsize headers.append(('Content-Length', str(rend - rbegin))) #werkzeug do not count rend with the same way of rfc7233,so -1 headers.append( ('Content-Range', '%s %d-%d/%d' % (range.units, rbegin, rend - 1, fsize))) headers.append(('Last-Modified', mtime_str)) if cache: headers.append(('ETag', '"%s"' % etag)) #for small file, read it to memory and return directly #and this can avoid some issue with google chrome if (rend - rbegin) < FileIterator.chunk_size: s = "".join([ chunk for chunk in FileIterator(real_filename, rbegin, rend) ]) return Response(s, status=206, headers=headers, direct_passthrough=True) else: return Response(FileIterator(real_filename, rbegin, rend), status=206, headers=headers, direct_passthrough=True) #process fileobj if fileobj: f, mtime, file_size = fileobj else: f, mtime, file_size = _opener(real_filename) headers.append(('Date', http_date())) if cache: etag = _generate_etag(mtime, file_size, real_filename) headers += [ ('ETag', '"%s"' % etag), ] if cache_timeout: headers += [('Cache-Control', 'max-age=%d, public' % cache_timeout), ('Expires', http_date(time() + cache_timeout))] if not is_resource_modified(environ, etag, last_modified=mtime): f.close() return Response(status=304, headers=headers) else: headers.append(('Cache-Control', 'public')) headers.extend( (('Content-Length', str(file_size)), ('Last-Modified', http_date(mtime)))) return Response(wrap_file(environ, f), status=200, headers=headers, direct_passthrough=True)
def filedown(environ, filename, cache=True, cache_timeout=None, action=None, real_filename=None, x_sendfile=False, x_header_name=None, x_filename=None, fileobj=None, default_mimetype='application/octet-stream'): """ @param filename: is used for display in download @param real_filename: if used for the real file location @param x_urlfile: is only used in x-sendfile, and be set to x-sendfile header @param fileobj: if provided, then returned as file content @type fileobj: (fobj, mtime, size) filedown now support web server controlled download, you should set xsendfile=True, and add x_header, for example: nginx ('X-Accel-Redirect', '/path/to/local_url') apache ('X-Sendfile', '/path/to/local_url') """ from werkzeug.http import parse_range_header guessed_type = mimetypes.guess_type(filename) mime_type = guessed_type[0] or default_mimetype real_filename = real_filename or filename #make common headers headers = [] headers.append(('Content-Type', mime_type)) d_filename = _get_download_filename(environ, os.path.basename(filename)) if action == 'download': headers.append(('Content-Disposition', 'attachment; %s' % d_filename)) elif action == 'inline': headers.append(('Content-Disposition', 'inline; %s' % d_filename)) if x_sendfile: if not x_header_name or not x_filename: raise Exception, "x_header_name or x_filename can't be empty" headers.append((x_header_name, x_filename)) return Response('', status=200, headers=headers, direct_passthrough=True) else: request = environ.get('werkzeug.request') if request: range = request.range else: range = parse_range_header(environ.get('HTTP_RANGE')) #when request range,only recognize "bytes" as range units if range!=None and range.units=="bytes": rbegin,rend = range.ranges[0] try: fsize = os.path.getsize(real_filename) except OSError,e: return Response("Not found",status=404) if (rbegin+1)<fsize: if rend == None: rend = fsize-1 headers.append(('Content-Length',str(rend-rbegin+1))) headers.append(('Content-Range','%s %d-%d/%d' %(range.units,rbegin, rend, fsize))) return Response(FileIterator(real_filename,rbegin,rend), status=206, headers=headers, direct_passthrough=True) #process fileobj if fileobj: f, mtime, file_size = fileobj else: f, mtime, file_size = _opener(real_filename) headers.append(('Date', http_date())) if cache: etag = _generate_etag(mtime, file_size, real_filename) headers += [ ('ETag', '"%s"' % etag), ] if cache_timeout: headers += [ ('Cache-Control', 'max-age=%d, public' % cache_timeout), ('Expires', http_date(time() + cache_timeout)) ] if not is_resource_modified(environ, etag, last_modified=mtime): f.close() return Response(status=304, headers=headers) else: headers.append(('Cache-Control', 'public')) headers.extend(( ('Content-Length', str(file_size)), ('Last-Modified', http_date(mtime)) )) return Response(wrap_file(environ, f), status=200, headers=headers, direct_passthrough=True)
def filedown(environ, filename, cache=True, cache_timeout=None, action=None, real_filename=None, x_sendfile=False, x_header_name=None, x_filename=None, fileobj=None, default_mimetype='application/octet-stream'): """ @param filename: is used for display in download @param real_filename: if used for the real file location @param x_urlfile: is only used in x-sendfile, and be set to x-sendfile header @param fileobj: if provided, then returned as file content @type fileobj: (fobj, mtime, size) filedown now support web server controlled download, you should set xsendfile=True, and add x_header, for example: nginx ('X-Accel-Redirect', '/path/to/local_url') apache ('X-Sendfile', '/path/to/local_url') """ from werkzeug.http import parse_range_header guessed_type = mimetypes.guess_type(filename) mime_type = guessed_type[0] or default_mimetype real_filename = real_filename or filename #make common headers headers = [] headers.append(('Content-Type', mime_type)) d_filename = _get_download_filename(environ, os.path.basename(filename)) if action == 'download': headers.append(('Content-Disposition', 'attachment; %s' % d_filename)) elif action == 'inline': headers.append(('Content-Disposition', 'inline; %s' % d_filename)) if x_sendfile: if not x_header_name or not x_filename: raise Exception, "x_header_name or x_filename can't be empty" headers.append((x_header_name, x_filename)) return Response('', status=200, headers=headers, direct_passthrough=True) else: request = environ.get('werkzeug.request') if request: range = request.range else: range = parse_range_header(environ.get('HTTP_RANGE')) #when request range,only recognize "bytes" as range units if range != None and range.units == "bytes": rbegin, rend = range.ranges[0] try: fsize = os.path.getsize(real_filename) except OSError, e: return Response("Not found", status=404) if (rbegin + 1) < fsize: if rend == None: rend = fsize - 1 headers.append(('Content-Length', str(rend - rbegin + 1))) headers.append( ('Content-Range', '%s %d-%d/%d' % (range.units, rbegin, rend, fsize))) return Response(FileIterator(real_filename, rbegin, rend), status=206, headers=headers, direct_passthrough=True) #process fileobj if fileobj: f, mtime, file_size = fileobj else: f, mtime, file_size = _opener(real_filename) headers.append(('Date', http_date())) if cache: etag = _generate_etag(mtime, file_size, real_filename) headers += [ ('ETag', '"%s"' % etag), ] if cache_timeout: headers += [('Cache-Control', 'max-age=%d, public' % cache_timeout), ('Expires', http_date(time() + cache_timeout))] if not is_resource_modified(environ, etag, last_modified=mtime): f.close() return Response(status=304, headers=headers) else: headers.append(('Cache-Control', 'public')) headers.extend( (('Content-Length', str(file_size)), ('Last-Modified', http_date(mtime)))) return Response(wrap_file(environ, f), status=200, headers=headers, direct_passthrough=True)
def test_range_parsing(self): rv = http.parse_range_header("bytes=52") assert rv is None rv = http.parse_range_header("bytes=52-") assert rv.units == "bytes" assert rv.ranges == [(52, None)] assert rv.to_header() == "bytes=52-" rv = http.parse_range_header("bytes=52-99") assert rv.units == "bytes" assert rv.ranges == [(52, 100)] assert rv.to_header() == "bytes=52-99" rv = http.parse_range_header("bytes=52-99,-1000") assert rv.units == "bytes" assert rv.ranges == [(52, 100), (-1000, None)] assert rv.to_header() == "bytes=52-99,-1000" rv = http.parse_range_header("bytes = 1 - 100") assert rv.units == "bytes" assert rv.ranges == [(1, 101)] assert rv.to_header() == "bytes=1-100" rv = http.parse_range_header("AWesomes=0-999") assert rv.units == "awesomes" assert rv.ranges == [(0, 1000)] assert rv.to_header() == "awesomes=0-999" rv = http.parse_range_header("bytes=-") assert rv is None rv = http.parse_range_header("bytes=bad") assert rv is None rv = http.parse_range_header("bytes=bad-1") assert rv is None rv = http.parse_range_header("bytes=-bad") assert rv is None rv = http.parse_range_header("bytes=52-99, bad") assert rv is None
def range(self) -> Optional[Range]: return parse_range_header(self.headers.get("Range"))
def filedown(environ, filename, cache=True, cache_timeout=None, action=None, real_filename=None, x_sendfile=False, x_header_name=None, x_filename=None, fileobj=None, default_mimetype='application/octet-stream'): """ @param filename: is used for display in download @param real_filename: if used for the real file location @param x_urlfile: is only used in x-sendfile, and be set to x-sendfile header @param fileobj: if provided, then returned as file content @type fileobj: (fobj, mtime, size) filedown now support web server controlled download, you should set xsendfile=True, and add x_header, for example: nginx ('X-Accel-Redirect', '/path/to/local_url') apache ('X-Sendfile', '/path/to/local_url') """ from .common import safe_str from werkzeug.http import parse_range_header guessed_type = mimetypes.guess_type(filename) mime_type = guessed_type[0] or default_mimetype real_filename = real_filename or filename #make common headers headers = [] headers.append(('Content-Type', mime_type)) d_filename = _get_download_filename(environ, os.path.basename(filename)) if action == 'download': headers.append(('Content-Disposition', 'attachment; %s' % d_filename)) elif action == 'inline': headers.append(('Content-Disposition', 'inline; %s' % d_filename)) if x_sendfile: if not x_header_name or not x_filename: raise Exception("x_header_name or x_filename can't be empty") headers.append((x_header_name, safe_str(x_filename))) return Response('', status=200, headers=headers, direct_passthrough=True) else: request = environ.get('werkzeug.request') if request: range = request.range else: range = parse_range_header(environ.get('HTTP_RANGE')) #when request range,only recognize "bytes" as range units if range and range.units=="bytes": try: fsize = os.path.getsize(real_filename) except OSError as e: return Response("Not found",status=404) mtime = datetime.utcfromtimestamp(os.path.getmtime(real_filename)) mtime_str = http_date(mtime) if cache: etag = _generate_etag(mtime, fsize, real_filename) else: etag = mtime_str if_range = environ.get('HTTP_IF_RANGE') if if_range: check_if_range_ok = (if_range.strip('"')==etag) #print "check_if_range_ok (%s) = (%s ==%s)"%(check_if_range_ok,if_range.strip('"'),etag) else: check_if_range_ok = True rbegin,rend = range.ranges[0] if check_if_range_ok and (rbegin+1)<fsize: if rend == None: rend = fsize headers.append(('Content-Length',str(rend-rbegin))) #werkzeug do not count rend with the same way of rfc7233,so -1 headers.append(('Content-Range','%s %d-%d/%d' %(range.units,rbegin, rend-1, fsize))) headers.append(('Last-Modified', mtime_str)) if cache: headers.append(('ETag', '"%s"' % etag)) #for small file, read it to memory and return directly #and this can avoid some issue with google chrome if (rend-rbegin) < FileIterator.chunk_size: s = "".join([chunk for chunk in FileIterator(real_filename,rbegin,rend)]) return Response(s,status=206, headers=headers, direct_passthrough=True) else: return Response(FileIterator(real_filename,rbegin,rend), status=206, headers=headers, direct_passthrough=True) #process fileobj if fileobj: f, mtime, file_size = fileobj else: f, mtime, file_size = _opener(real_filename) headers.append(('Date', http_date())) if cache: etag = _generate_etag(mtime, file_size, real_filename) headers += [ ('ETag', '"%s"' % etag), ] if cache_timeout: headers += [ ('Cache-Control', 'max-age=%d, public' % cache_timeout), ('Expires', http_date(time() + cache_timeout)) ] if not is_resource_modified(environ, etag, last_modified=mtime): f.close() return Response(status=304, headers=headers) else: headers.append(('Cache-Control', 'public')) headers.extend(( ('Content-Length', str(file_size)), ('Last-Modified', http_date(mtime)) )) return Response(wrap_file(environ, f), status=200, headers=headers, direct_passthrough=True)