Esempio n. 1
0
def serve_file(filename, data=None, mimetype='auto', download=False, charset='UTF-8'):
    headers = dict()

    if mimetype == 'auto':
        mimetype, encoding = mimetypes.guess_type(filename)
        if encoding: headers['Content-Encoding'] = encoding

    if mimetype:
        if mimetype[:5] == 'text/' and charset and 'charset' not in mimetype:
            mimetype += '; charset=%s' % charset
        headers['Content-Type'] = mimetype

    if download:
        headers['Content-Disposition'] = 'attachment; filename="%s"' % filename

    data.seek(0, os.SEEK_END)
    clen = data.tell()
    headers['Content-Length'] = clen
    lm = datetime.datetime.now().strftime("%a, %d %b %Y %H:%M:%S GMT")
    headers['Last-Modified'] = lm

    if bottle.request.method == 'HEAD':
        body = ''
    else:
        data.seek(0)
        body = data.read()

    headers["Accept-Ranges"] = "bytes"
    ranges = bottle.request.environ.get('HTTP_RANGE')
    if 'HTTP_RANGE' in bottle.request.environ:
        ranges = list(bottle.parse_range_header(bottle.request.environ['HTTP_RANGE'], clen))
        if not ranges:
            return bottle.HTTPError(416, "Requested Range Not Satisfiable")
        offset, end = ranges[0]
        headers["Content-Range"] = "bytes %d-%d/%d" % (offset, end - 1, clen)
        headers["Content-Length"] = str(end - offset)
        if body:
            body = bottle._file_iter_range(body, offset, end - offset)
        return bottle.HTTPResponse(body, status=206, **headers)
    return bottle.HTTPResponse(body, **headers)
Esempio n. 2
0
    def get_item_file(self, id):
        item = self.lib.get_item(id)
        if item is None:
            return HTTPError(404, 'File does not exist.')

        if not os.access(item.path, os.R_OK):
            return HTTPError(403, 'You do not have permission to access this file.')

        stats = os.stat(item.path)
        headers = {
            'Content-Type': 'application/octet-stream',
            'Content-Length': stats.st_size,
            'Last-Modified': time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(stats.st_mtime)),
            'Date': time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime())
        }

        ims = request.environ.get('HTTP_IF_MODIFIED_SINCE')
        if ims:
            ims = parse_date(ims.split(";")[0].strip())
        if ims is not None and ims >= int(stats.st_mtime):
            return HTTPResponse(status=304, **headers)

        body = '' if request.method == 'HEAD' else open(item.path, 'rb')

        headers["Accept-Ranges"] = "bytes"
        range_header = request.environ.get('HTTP_RANGE')
        if range_header:
            ranges = list(parse_range_header(range_header, stats.st_size))
            if not ranges:
                return HTTPError(416, "Requested Range Not Satisfiable")

            offset, end = ranges[0]
            headers["Content-Range"] = "bytes %d-%d/%d" % (offset, end - 1, stats.st_size)
            headers["Content-Length"] = str(end - offset)
            if body:
                body = _file_iter_range(body, offset, end - offset)

            return HTTPResponse(body, status=206, **headers)

        return HTTPResponse(body, **headers)
Esempio n. 3
0
    def static_file(filename,
                    root,
                    mimetype=True,
                    download=False,
                    charset='UTF-8',
                    etag=None):
        """ Open a file in a safe way and return an instance of :exc:`HTTPResponse`
            that can be sent back to the client.
            :param filename: Name or path of the file to send, relative to ``root``.
            :param root: Root path for file lookups. Should be an absolute directory
                path.
            :param mimetype: Provide the content-type header (default: guess from
                file extension)
            :param download: If True, ask the browser to open a `Save as...` dialog
                instead of opening the file with the associated program. You can
                specify a custom filename as a string. If not specified, the
                original filename is used (default: False).
            :param charset: The charset for files with a ``text/*`` mime-type.
                (default: UTF-8)
            :param etag: Provide a pre-computed ETag header. If set to ``False``,
                ETag handling is disabled. (default: auto-generate ETag header)
            While checking user input is always a good idea, this function provides
            additional protection against malicious ``filename`` parameters from
            breaking out of the ``root`` directory and leaking sensitive information
            to an attacker.
            Read-protected files or files outside of the ``root`` directory are
            answered with ``403 Access Denied``. Missing files result in a
            ``404 Not Found`` response. Conditional requests (``If-Modified-Since``,
            ``If-None-Match``) are answered with ``304 Not Modified`` whenever
            possible. ``HEAD`` and ``Range`` requests (used by download managers to
            check or continue partial downloads) are also handled automatically.
        """

        root = os.path.join(os.path.abspath(root), '')
        filename = os.path.abspath(os.path.join(root, filename.strip('/\\')))
        headers = dict()

        if not filename.startswith(root):
            return HTTPError(403, "Access denied.")
        if not os.path.exists(filename) or not os.path.isfile(filename):
            return HTTPError(404, "File does not exist.")
        if not os.access(filename, os.R_OK):
            return HTTPError(
                403, "You do not have permission to access this file.")

        if mimetype is True:
            if download and download is not True:
                mimetype, encoding = mimetypes.guess_type(download)
            else:
                mimetype, encoding = mimetypes.guess_type(filename)
            if encoding: headers['Content-Encoding'] = encoding

        if mimetype:
            if (mimetype[:5] == 'text/' or mimetype == 'application/javascript')\
            and charset and 'charset' not in mimetype:
                mimetype += '; charset=%s' % charset
            headers['Content-Type'] = mimetype

        if download:
            download = os.path.basename(
                filename if download is True else download)
            headers[
                'Content-Disposition'] = 'attachment; filename="%s"' % download

        stats = os.stat(filename)
        headers['Content-Length'] = clen = stats.st_size
        headers['Last-Modified'] = email.utils.formatdate(stats.st_mtime,
                                                          usegmt=True)
        headers['Date'] = email.utils.formatdate(time.time(), usegmt=True)

        getenv = request.environ.get

        if etag is None:
            etag = '%d:%d:%d:%d:%s' % (stats.st_dev, stats.st_ino,
                                       stats.st_mtime, clen, filename)
            etag = hashlib.sha1(tob(etag)).hexdigest()

        if etag:
            headers['ETag'] = etag
            check = getenv('HTTP_IF_NONE_MATCH')
            if check and check == etag:
                return HTTPResponse(status=304, **headers)

        if not (etag and check):
            ims = getenv('HTTP_IF_MODIFIED_SINCE')
            if ims:
                ims = parse_date(ims.split(";")[0].strip())
            if ims is not None and ims >= int(stats.st_mtime):
                return HTTPResponse(status=304, **headers)

        body = '' if request.method == 'HEAD' else open(filename, 'rb')

        headers["Accept-Ranges"] = "bytes"
        range_header = getenv('HTTP_RANGE')
        if range_header:
            ranges = list(parse_range_header(range_header, clen))
            if not ranges:
                return HTTPError(416, "Requested Range Not Satisfiable")
            offset, end = ranges[0]
            headers["Content-Range"] = "bytes %d-%d/%d" % (offset, end - 1,
                                                           clen)
            headers["Content-Length"] = str(end - offset)
            if body: body = _file_iter_range(body, offset, end - offset)
            return HTTPResponse(body, status=206, **headers)
        return HTTPResponse(body, **headers)
Esempio n. 4
0
def handle_subarchive_path(archivefile,
                           subarchivepath,
                           mimetype,
                           encoding,
                           download=False,
                           charset='UTF-8',
                           etag=None,
                           format=None):
    """Show content of a path in a zip file.
    """
    from bottle import parse_range_header, parse_date, _file_iter_range, tob

    if not os.access(archivefile, os.R_OK):
        return http_error(403,
                          "You do not have permission to access this file.",
                          format=format)

    try:
        zip = zipfile.ZipFile(archivefile)
    except:
        return http_error(500, "Unable to open the ZIP file.", format=format)

    try:
        # KeyError is raised if subarchivepath does not exist
        info = zip.getinfo(subarchivepath)
    except KeyError:
        # subarchivepath does not exist
        # possibility a missing directory entry?
        return handle_zip_directory_listing(zip, archivefile, subarchivepath)

    fh = zip.open(subarchivepath, 'r')

    headers = dict()

    if encoding: headers['Content-Encoding'] = encoding

    if mimetype is True:
        if download and download is not True:
            mimetype, encoding = mimetypes.guess_type(download)
        else:
            mimetype, encoding = mimetypes.guess_type(subarchivepath)
        if encoding: headers['Content-Encoding'] = encoding

    if mimetype:
        if (mimetype[:5] == 'text/' or mimetype == 'application/javascript')\
        and charset and 'charset' not in mimetype:
            mimetype += '; charset=%s' % charset
        headers['Content-Type'] = mimetype

    if download:
        download = os.path.basename(
            subarchivepath if download is True else download)
        headers['Content-Disposition'] = 'attachment; filename="%s"' % download

    headers['Content-Length'] = clen = info.file_size

    lm = info.date_time
    epoch = int(
        time.mktime((lm[0], lm[1], lm[2], lm[3], lm[4], lm[5], 0, 0, -1)))
    headers['Last-Modified'] = email.utils.formatdate(epoch, usegmt=True)

    headers['Date'] = email.utils.formatdate(time.time(), usegmt=True)

    getenv = request.environ.get

    if etag is None:
        etag = '%d:%d:%s' % (epoch, clen, subarchivepath)
        etag = hashlib.sha1(tob(etag)).hexdigest()

    if etag:
        headers['ETag'] = etag
        check = getenv('HTTP_IF_NONE_MATCH')
        if check and check == etag:
            return HTTPResponse(status=304, **headers)

    if not (etag and check):
        ims = getenv('HTTP_IF_MODIFIED_SINCE')
        if ims:
            ims = parse_date(ims.split(";")[0].strip())
        if ims is not None and ims >= int(epoch):
            return HTTPResponse(status=304, **headers)

    body = '' if request.method == 'HEAD' else fh

    headers["Accept-Ranges"] = "bytes"
    range_header = getenv('HTTP_RANGE')
    if range_header:
        ranges = list(parse_range_header(range_header, clen))
        if not ranges:
            return http_error(416, "Requested Range Not Satisfiable")
        offset, end = ranges[0]
        headers["Content-Range"] = "bytes %d-%d/%d" % (offset, end - 1, clen)
        headers["Content-Length"] = str(end - offset)
        if body: body = _file_iter_range(body, offset, end - offset)
        return HTTPResponse(body, status=206, **headers)
    return HTTPResponse(body, **headers)
Esempio n. 5
0
def static_file(filename, root, mimetype="auto", download=False, charset="UTF-8"):
    """ Open a file in a safe way and return :exc:`HTTPResponse` with status
        code 200, 305, 403 or 404. The ``Content-Type``, ``Content-Encoding``,
        ``Content-Length`` and ``Last-Modified`` headers are set if possible.
        Special support for ``If-Modified-Since``, ``Range`` and ``HEAD``
        requests.

        :param filename: Name or path of the file to send.
        :param root: Root path for file lookups. Should be an absolute directory
            path.
        :param mimetype: Defines the content-type header (default: guess from
            file extension)
        :param download: If True, ask the browser to open a `Save as...` dialog
            instead of opening the file with the associated program. You can
            specify a custom filename as a string. If not specified, the
            original filename is used (default: False).
        :param charset: The charset to use for files with a ``text/*``
            mime-type. (default: UTF-8)
    """

    root = os.path.abspath(root) + os.sep
    filename = os.path.abspath(os.path.join(root, filename.strip("/\\")))
    headers = dict()

    if not filename.startswith(root):
        return HTTPError(403, "Access denied.")
    if not os.path.exists(filename) or not os.path.isfile(filename):
        return HTTPError(404, "File does not exist.")
    if not os.access(filename, os.R_OK):
        return HTTPError(403, "You do not have permission to access this file.")

    if mimetype == "auto":
        mimetype, encoding = mimetypes.guess_type(filename)
        if encoding:
            headers["Content-Encoding"] = encoding

    if mimetype:
        if mimetype[:5] == "text/" and charset and "charset" not in mimetype:
            mimetype += "; charset=%s" % charset
        headers["Content-Type"] = mimetype

    if download:
        download = os.path.basename(filename if download == True else download)
        headers["Content-Disposition"] = 'attachment; filename="%s"' % download

    stats = os.stat(filename)
    headers["Content-Length"] = clen = stats.st_size
    lm = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(stats.st_mtime))
    headers["Last-Modified"] = lm

    ### Added here:
    headers["Cache-Control"] = "max-age=3600, public"

    ims = request.environ.get("HTTP_IF_MODIFIED_SINCE")
    if ims:
        ims = parse_date(ims.split(";")[0].strip())
    if ims is not None and ims >= int(stats.st_mtime):
        headers["Date"] = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime())
        return HTTPResponse(status=304, **headers)

    body = "" if request.method == "HEAD" else open(filename, "rb")

    headers["Accept-Ranges"] = "bytes"
    ranges = request.environ.get("HTTP_RANGE")
    if "HTTP_RANGE" in request.environ:
        ranges = list(parse_range_header(request.environ["HTTP_RANGE"], clen))
        if not ranges:
            return HTTPError(416, "Requested Range Not Satisfiable")
        offset, end = ranges[0]
        headers["Content-Range"] = "bytes %d-%d/%d" % (offset, end - 1, clen)
        headers["Content-Length"] = str(end - offset)
        if body:
            body = _file_iter_range(body, offset, end - offset)
        return HTTPResponse(body, status=206, **headers)
    return HTTPResponse(body, **headers)