Ejemplo n.º 1
0
def render(title):
    """Serve a thumbnail or otherwise rendered content."""

    p = page.get_page(request, title)
    try:
        cache_filename, cache_mime = p.render_mime()
        render_func = p.render_cache
        if not current_app.cache:
            raise NotImplementedError()
    except (AttributeError, NotImplementedError):
        return download(title)

    cache_key = '/render/%s_%s' % (werkzeug.url_quote(title, safe=''), cache_filename)

    rev, dt, author, comment = current_app.storage.page_meta(title)
    cache_file = current_app.cache.open(cache_key, 'r')
    if (cache_file and dt > cache_file.modified) or not cache_file:
        cache_file = current_app.cache.open(cache_key, 'w')
        try:
            result_file = render_func(cache_file)
        except error.UnsupportedMediaTypeErr:
            return download(title)
        else:
            cache_file = current_app.cache.open(cache_key, 'r')
    resp = response.response(request, title, werkzeug.wrap_file(request.environ, cache_file), '/render', cache_mime,
                             size=cache_file.length)
    resp.direct_passthrough = True
    return resp
Ejemplo n.º 2
0
def view_file(req):
    if not 'uploaded_file' in req.files:
        return BaseResponse('no file uploaded')
    f = req.files['uploaded_file']
    return BaseResponse(wrap_file(req.environ, f),
                        mimetype=f.content_type,
                        direct_passthrough=True)
Ejemplo n.º 3
0
def get_resource(request, theme, file):
    """Returns a file from the theme."""
    theme = get_theme(theme)
    if theme is None:
        raise NotFound()
    f = theme.open_resource(file)
    if f is None:
        raise NotFound()
    resp = Response(wrap_file(request.environ, f),
                    mimetype=mimetypes.guess_type(file)[0] or 'text/plain',
                    direct_passthrough=True)
    resp.add_etag()
    return resp.make_conditional(request)
Ejemplo n.º 4
0
 def __init__(self,
              fileobj=None,
              status=None,
              headers=None,
              mimetype=None,
              content_type=None,
              buffer_size=8192):
     fw = wz.wrap_file(rg.environ, fileobj, buffer_size)
     wz.Response.__init__(self,
                          fw,
                          status=status,
                          headers=headers,
                          mimetype=mimetype,
                          content_type=content_type,
                          direct_passthrough=True)
Ejemplo n.º 5
0
def main(request, filename):
    download = session.query(Download).filter_by(filename=filename).first() 
    if not download:
        # TODO: 'download you were looking for was not found, check out these downloads...'
        raise NotFound()

    try:
        file = open("%s/%s" % (config['download_path'], filename))
    except IOError:
        # TODO: 'the file was not found, check this out'
        raise NotFound()

    download.another_download(request)
    return Response(wrap_file(request.environ, file), mimetype=download.mimetype,
            direct_passthrough=True)
Ejemplo n.º 6
0
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)
Ejemplo n.º 7
0
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)
Ejemplo n.º 8
0
def filedown(environ, filename, cache=True, cache_timeout=None, download=False, inline=False, real_filename=None):
    guessed_type = mimetypes.guess_type(filename)
    mime_type = guessed_type[0] or 'text/plain'
    real_filename = real_filename or filename
    f, mtime, file_size = _opener(real_filename)

    headers = [('Date', http_date())]
    
    d_filename = os.path.basename(filename)
    if download:
        headers.append(('Content-Disposition', 'attachment; filename=%s' % d_filename))
    if inline:
        headers.append(('Content-Disposition', 'inline; filename=%s' % d_filename))
    
    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-Type', mime_type),
        ('Content-Length', str(file_size)),
        ('Last-Modified', http_date(mtime))
    ))
    
    return Response(wrap_file(environ, f), status=200, headers=headers,
        direct_passthrough=True)
Ejemplo n.º 9
0
Archivo: helpers.py Proyecto: redb/MNPP
def send_file(filename_or_fp, mimetype=None, as_attachment=False,
              attachment_filename=None, add_etags=True,
              cache_timeout=60 * 60 * 12, conditional=False):
    """Sends the contents of a file to the client.  This will use the
    most efficient method available and configured.  By default it will
    try to use the WSGI server's file_wrapper support.  Alternatively
    you can set the application's :attr:`~Flask.use_x_sendfile` attribute
    to ``True`` to directly emit an `X-Sendfile` header.  This however
    requires support of the underlying webserver for `X-Sendfile`.

    By default it will try to guess the mimetype for you, but you can
    also explicitly provide one.  For extra security you probably want
    to send certain files as attachment (HTML for instance).  The mimetype
    guessing requires a `filename` or an `attachment_filename` to be
    provided.

    Please never pass filenames to this function from user sources without
    checking them first.  Something like this is usually sufficient to
    avoid security problems::

        if '..' in filename or filename.startswith('/'):
            abort(404)

    .. versionadded:: 0.2

    .. versionadded:: 0.5
       The `add_etags`, `cache_timeout` and `conditional` parameters were
       added.  The default behaviour is now to attach etags.

    .. versionchanged:: 0.7
       mimetype guessing and etag support for file objects was
       deprecated because it was unreliable.  Pass a filename if you are
       able to, otherwise attach an etag yourself.  This functionality
       will be removed in Flask 1.0

    :param filename_or_fp: the filename of the file to send.  This is
                           relative to the :attr:`~Flask.root_path` if a
                           relative path is specified.
                           Alternatively a file object might be provided
                           in which case `X-Sendfile` might not work and
                           fall back to the traditional method.  Make sure
                           that the file pointer is positioned at the start
                           of data to send before calling :func:`send_file`.
    :param mimetype: the mimetype of the file if provided, otherwise
                     auto detection happens.
    :param as_attachment: set to `True` if you want to send this file with
                          a ``Content-Disposition: attachment`` header.
    :param attachment_filename: the filename for the attachment if it
                                differs from the file's filename.
    :param add_etags: set to `False` to disable attaching of etags.
    :param conditional: set to `True` to enable conditional responses.
    :param cache_timeout: the timeout in seconds for the headers.
    """
    mtime = None
    if isinstance(filename_or_fp, basestring):
        filename = filename_or_fp
        file = None
    else:
        from warnings import warn
        file = filename_or_fp
        filename = getattr(file, 'name', None)

        # XXX: this behaviour is now deprecated because it was unreliable.
        # removed in Flask 1.0
        if not attachment_filename and not mimetype \
           and isinstance(filename, basestring):
            warn(DeprecationWarning('The filename support for file objects '
                'passed to send_file is now deprecated.  Pass an '
                'attach_filename if you want mimetypes to be guessed.'),
                stacklevel=2)
        if add_etags:
            warn(DeprecationWarning('In future flask releases etags will no '
                'longer be generated for file objects passed to the send_file '
                'function because this behaviour was unreliable.  Pass '
                'filenames instead if possible, otherwise attach an etag '
                'yourself based on another value'), stacklevel=2)

    if filename is not None:
        if not os.path.isabs(filename):
            filename = os.path.join(current_app.root_path, filename)
    if mimetype is None and (filename or attachment_filename):
        mimetype = mimetypes.guess_type(filename or attachment_filename)[0]
    if mimetype is None:
        mimetype = 'application/octet-stream'

    headers = Headers()
    if as_attachment:
        if attachment_filename is None:
            if filename is None:
                raise TypeError('filename unavailable, required for '
                                'sending as attachment')
            attachment_filename = os.path.basename(filename)
        headers.add('Content-Disposition', 'attachment',
                    filename=attachment_filename)

    if current_app.use_x_sendfile and filename:
        if file is not None:
            file.close()
        headers['X-Sendfile'] = filename
        data = None
    else:
        if file is None:
            file = open(filename, 'rb')
            mtime = os.path.getmtime(filename)
        data = wrap_file(request.environ, file)

    rv = current_app.response_class(data, mimetype=mimetype, headers=headers,
                                    direct_passthrough=True)

    # if we know the file modification date, we can store it as the
    # the time of the last modification.
    if mtime is not None:
        rv.last_modified = int(mtime)

    rv.cache_control.public = True
    if cache_timeout:
        rv.cache_control.max_age = cache_timeout
        rv.expires = int(time() + cache_timeout)

    if add_etags and filename is not None:
        rv.set_etag('flask-%s-%s-%s' % (
            os.path.getmtime(filename),
            os.path.getsize(filename),
            adler32(
                filename.encode('utf8') if isinstance(filename, unicode)
                else filename
            ) & 0xffffffff
        ))
        if conditional:
            rv = rv.make_conditional(request)
            # make sure we don't send x-sendfile for servers that
            # ignore the 304 status code for x-sendfile.
            if rv.status_code == 304:
                rv.headers.pop('x-sendfile', None)
    return rv
Ejemplo n.º 10
0
 def __init__(self, fileobj=None, status=None, headers=None, mimetype=None, content_type=None,
              buffer_size=8192):
     fw = wz.wrap_file(rg.environ, fileobj, buffer_size)
     wz.Response.__init__(self, fw, status=status, headers=headers, mimetype=mimetype,
                          content_type=content_type, direct_passthrough=True)
Ejemplo n.º 11
0
def send_file(filename_or_fp,
              mimetype=None,
              as_attachment=False,
              attachment_filename=None):
    """Sends the contents of a file to the client.  This will use the
    most efficient method available and configured.  By default it will
    try to use the WSGI server's file_wrapper support.  Alternatively
    you can set the application's :attr:`~Flask.use_x_sendfile` attribute
    to ``True`` to directly emit an `X-Sendfile` header.  This however
    requires support of the underlying webserver for `X-Sendfile`.

    By default it will try to guess the mimetype for you, but you can
    also explicitly provide one.  For extra security you probably want
    to sent certain files as attachment (HTML for instance).

    Please never pass filenames to this function from user sources without
    checking them first.  Something like this is usually sufficient to
    avoid security problems::

        if '..' in filename or filename.startswith('/'):
            abort(404)

    .. versionadded:: 0.2

    :param filename_or_fp: the filename of the file to send.  This is
                           relative to the :attr:`~Flask.root_path` if a
                           relative path is specified.
                           Alternatively a file object might be provided
                           in which case `X-Sendfile` might not work and
                           fall back to the traditional method.
    :param mimetype: the mimetype of the file if provided, otherwise
                     auto detection happens.
    :param as_attachment: set to `True` if you want to send this file with
                          a ``Content-Disposition: attachment`` header.
    :param attachment_filename: the filename for the attachment if it
                                differs from the file's filename.
    """
    if isinstance(filename_or_fp, basestring):
        filename = filename_or_fp
        file = None
    else:
        file = filename_or_fp
        filename = getattr(file, 'name', None)
    if filename is not None:
        filename = os.path.join(current_app.root_path, filename)
    if mimetype is None and (filename or attachment_filename):
        mimetype = mimetypes.guess_type(filename or attachment_filename)[0]
    if mimetype is None:
        mimetype = 'application/octet-stream'

    headers = Headers()
    if as_attachment:
        if attachment_filename is None:
            if filename is None:
                raise TypeError('filename unavailable, required for '
                                'sending as attachment')
            attachment_filename = os.path.basename(filename)
        headers.add('Content-Disposition',
                    'attachment',
                    filename=attachment_filename)

    if current_app.use_x_sendfile and filename:
        if file is not None:
            file.close()
        headers['X-Sendfile'] = filename
        data = None
    else:
        if file is None:
            file = open(filename, 'rb')
        data = wrap_file(request.environ, file)

    return Response(data,
                    mimetype=mimetype,
                    headers=headers,
                    direct_passthrough=True)
Ejemplo n.º 12
0
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)
Ejemplo n.º 13
0
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)
Ejemplo n.º 14
0
def send_file(filename_or_fp, mimetype=None, as_attachment=False, attachment_filename=None):
    """Sends the contents of a file to the client.  This will use the
    most efficient method available and configured.  By default it will
    try to use the WSGI server's file_wrapper support.  Alternatively
    you can set the application's :attr:`~Flask.use_x_sendfile` attribute
    to ``True`` to directly emit an `X-Sendfile` header.  This however
    requires support of the underlying webserver for `X-Sendfile`.

    By default it will try to guess the mimetype for you, but you can
    also explicitly provide one.  For extra security you probably want
    to sent certain files as attachment (HTML for instance).

    Please never pass filenames to this function from user sources without
    checking them first.  Something like this is usually sufficient to
    avoid security problems::

        if '..' in filename or filename.startswith('/'):
            abort(404)

    .. versionadded:: 0.2

    :param filename_or_fp: the filename of the file to send.  This is
                           relative to the :attr:`~Flask.root_path` if a
                           relative path is specified.
                           Alternatively a file object might be provided
                           in which case `X-Sendfile` might not work and
                           fall back to the traditional method.
    :param mimetype: the mimetype of the file if provided, otherwise
                     auto detection happens.
    :param as_attachment: set to `True` if you want to send this file with
                          a ``Content-Disposition: attachment`` header.
    :param attachment_filename: the filename for the attachment if it
                                differs from the file's filename.
    """
    if isinstance(filename_or_fp, basestring):
        filename = filename_or_fp
        file = None
    else:
        file = filename_or_fp
        filename = getattr(file, "name", None)
    if filename is not None:
        filename = os.path.join(current_app.root_path, filename)
    if mimetype is None and (filename or attachment_filename):
        mimetype = mimetypes.guess_type(filename or attachment_filename)[0]
    if mimetype is None:
        mimetype = "application/octet-stream"

    headers = Headers()
    if as_attachment:
        if attachment_filename is None:
            if filename is None:
                raise TypeError("filename unavailable, required for " "sending as attachment")
            attachment_filename = os.path.basename(filename)
        headers.add("Content-Disposition", "attachment", filename=attachment_filename)

    if current_app.use_x_sendfile and filename:
        if file is not None:
            file.close()
        headers["X-Sendfile"] = filename
        data = None
    else:
        if file is None:
            file = open(filename, "rb")
        data = wrap_file(request.environ, file)

    return Response(data, mimetype=mimetype, headers=headers, direct_passthrough=True)
Ejemplo n.º 15
0
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)
Ejemplo n.º 16
0
def view_file(req):
    if not 'uploaded_file' in req.files:
        return BaseResponse('no file uploaded')
    f = req.files['uploaded_file']
    return BaseResponse(wrap_file(req.environ, f), mimetype=f.content_type,
                        direct_passthrough=True)
Ejemplo n.º 17
0
 def generate(self, rsp):
     rsp.response = wrap_file(self.req.environ, open(self.filename))
Ejemplo n.º 18
0
def send_file(
    filename_or_fp,
    mimetype=None,
    as_attachment=False,
    attachment_filename=None,
    add_etags=True,
    cache_timeout=60 * 60 * 12,
    conditional=False,
):
    """Sends the contents of a file to the client.  This will use the
    most efficient method available and configured.  By default it will
    try to use the WSGI server's file_wrapper support.  Alternatively
    you can set the application's :attr:`~Flask.use_x_sendfile` attribute
    to ``True`` to directly emit an `X-Sendfile` header.  This however
    requires support of the underlying webserver for `X-Sendfile`.

    By default it will try to guess the mimetype for you, but you can
    also explicitly provide one.  For extra security you probably want
    to sent certain files as attachment (HTML for instance).

    Please never pass filenames to this function from user sources without
    checking them first.  Something like this is usually sufficient to
    avoid security problems::

        if '..' in filename or filename.startswith('/'):
            abort(404)

    .. versionadded:: 0.2

    .. versionadded:: 0.5
       The `add_etags`, `cache_timeout` and `conditional` parameters were
       added.  The default behaviour is now to attach etags.

    :param filename_or_fp: the filename of the file to send.  This is
                           relative to the :attr:`~Flask.root_path` if a
                           relative path is specified.
                           Alternatively a file object might be provided
                           in which case `X-Sendfile` might not work and
                           fall back to the traditional method.
    :param mimetype: the mimetype of the file if provided, otherwise
                     auto detection happens.
    :param as_attachment: set to `True` if you want to send this file with
                          a ``Content-Disposition: attachment`` header.
    :param attachment_filename: the filename for the attachment if it
                                differs from the file's filename.
    :param add_etags: set to `False` to disable attaching of etags.
    :param conditional: set to `True` to enable conditional responses.
    :param cache_timeout: the timeout in seconds for the headers.
    """
    mtime = None
    if isinstance(filename_or_fp, basestring):
        filename = filename_or_fp
        file = None
    else:
        file = filename_or_fp
        filename = getattr(file, "name", None)
    if filename is not None:
        if not os.path.isabs(filename):
            filename = os.path.join(current_app.root_path, filename)
    if mimetype is None and (filename or attachment_filename):
        mimetype = mimetypes.guess_type(filename or attachment_filename)[0]
    if mimetype is None:
        mimetype = "application/octet-stream"

    headers = Headers()
    if as_attachment:
        if attachment_filename is None:
            if filename is None:
                raise TypeError("filename unavailable, required for " "sending as attachment")
            attachment_filename = os.path.basename(filename)
        headers.add("Content-Disposition", "attachment", filename=attachment_filename)

    if current_app.use_x_sendfile and filename:
        if file is not None:
            file.close()
        headers["X-Sendfile"] = filename
        data = None
    else:
        if file is None:
            file = open(filename, "rb")
            mtime = os.path.getmtime(filename)
        data = wrap_file(request.environ, file)

    rv = current_app.response_class(data, mimetype=mimetype, headers=headers, direct_passthrough=True)

    # if we know the file modification date, we can store it as the
    # current time to better support conditional requests.  Werkzeug
    # as of 0.6.1 will override this value however in the conditional
    # response with the current time.  This will be fixed in Werkzeug
    # with a new release, however many WSGI servers will still emit
    # a separate date header.
    if mtime is not None:
        rv.date = int(mtime)

    rv.cache_control.public = True
    if cache_timeout:
        rv.cache_control.max_age = cache_timeout
        rv.expires = int(time() + cache_timeout)

    if add_etags and filename is not None:
        rv.set_etag(
            "flask-%s-%s-%s" % (os.path.getmtime(filename), os.path.getsize(filename), adler32(filename) & 0xFFFFFFFF)
        )
        if conditional:
            rv = rv.make_conditional(request)
            # make sure we don't send x-sendfile for servers that
            # ignore the 304 status code for x-sendfile.
            if rv.status_code == 304:
                rv.headers.pop("x-sendfile", None)
    return rv
Ejemplo n.º 19
0
def send_file(filename_or_fp,
              mimetype=None,
              as_attachment=False,
              attachment_filename=None,
              add_etags=True,
              cache_timeout=60 * 60 * 12,
              conditional=False):
    """Sends the contents of a file to the client.  This will use the
    most efficient method available and configured.  By default it will
    try to use the WSGI server's file_wrapper support.  Alternatively
    you can set the application's :attr:`~Flask.use_x_sendfile` attribute
    to ``True`` to directly emit an `X-Sendfile` header.  This however
    requires support of the underlying webserver for `X-Sendfile`.

    By default it will try to guess the mimetype for you, but you can
    also explicitly provide one.  For extra security you probably want
    to sent certain files as attachment (HTML for instance).

    Please never pass filenames to this function from user sources without
    checking them first.  Something like this is usually sufficient to
    avoid security problems::

        if '..' in filename or filename.startswith('/'):
            abort(404)

    .. versionadded:: 0.2

    .. versionadded:: 0.5
       The `add_etags`, `cache_timeout` and `conditional` parameters were
       added.  The default behaviour is now to attach etags.

    :param filename_or_fp: the filename of the file to send.  This is
                           relative to the :attr:`~Flask.root_path` if a
                           relative path is specified.
                           Alternatively a file object might be provided
                           in which case `X-Sendfile` might not work and
                           fall back to the traditional method.
    :param mimetype: the mimetype of the file if provided, otherwise
                     auto detection happens.
    :param as_attachment: set to `True` if you want to send this file with
                          a ``Content-Disposition: attachment`` header.
    :param attachment_filename: the filename for the attachment if it
                                differs from the file's filename.
    :param add_etags: set to `False` to disable attaching of etags.
    :param conditional: set to `True` to enable conditional responses.
    :param cache_timeout: the timeout in seconds for the headers.
    """
    mtime = None
    if isinstance(filename_or_fp, basestring):
        filename = filename_or_fp
        file = None
    else:
        file = filename_or_fp
        filename = getattr(file, 'name', None)
    if filename is not None:
        if not os.path.isabs(filename):
            filename = os.path.join(current_app.root_path, filename)
    if mimetype is None and (filename or attachment_filename):
        mimetype = mimetypes.guess_type(filename or attachment_filename)[0]
    if mimetype is None:
        mimetype = 'application/octet-stream'

    headers = Headers()
    if as_attachment:
        if attachment_filename is None:
            if filename is None:
                raise TypeError('filename unavailable, required for '
                                'sending as attachment')
            attachment_filename = os.path.basename(filename)
        headers.add('Content-Disposition',
                    'attachment',
                    filename=attachment_filename)

    if current_app.use_x_sendfile and filename:
        if file is not None:
            file.close()
        headers['X-Sendfile'] = filename
        data = None
    else:
        if file is None:
            file = open(filename, 'rb')
            mtime = os.path.getmtime(filename)
        data = wrap_file(request.environ, file)

    rv = current_app.response_class(data,
                                    mimetype=mimetype,
                                    headers=headers,
                                    direct_passthrough=True)

    # if we know the file modification date, we can store it as the
    # current time to better support conditional requests.  Werkzeug
    # as of 0.6.1 will override this value however in the conditional
    # response with the current time.  This will be fixed in Werkzeug
    # with a new release, however many WSGI servers will still emit
    # a separate date header.
    if mtime is not None:
        rv.date = int(mtime)

    rv.cache_control.public = True
    if cache_timeout:
        rv.cache_control.max_age = cache_timeout
        rv.expires = int(time() + cache_timeout)

    if add_etags and filename is not None:
        rv.set_etag('flask-%s-%s-%s' %
                    (os.path.getmtime(filename), os.path.getsize(filename),
                     adler32(filename) & 0xffffffff))
        if conditional:
            rv = rv.make_conditional(request)
            # make sure we don't send x-sendfile for servers that
            # ignore the 304 status code for x-sendfile.
            if rv.status_code == 304:
                rv.headers.pop('x-sendfile', None)
    return rv
Ejemplo n.º 20
0
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)
Ejemplo n.º 21
0
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')
    """
    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:
        #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)
Ejemplo n.º 22
0
def send_file(filename=None, file=None,
              mimetype=None,
              as_attachment=False, attachment_filename=None,
              mtime=None, cache_timeout=60 * 60 * 12,
              add_etags=True, etag=None, conditional=False):
    """Sends the contents of a file to the client.

    A file can be either a filesystem file or a file-like object (this code
    is careful about not assuming that every file is a filesystem file).

    This will use the most efficient method available, configured and possible
    (for filesystem files some more optimizations may be possible that for
    file-like objects not having a filesystem filename).
    By default it will try to use the WSGI server's file_wrapper support.
    Alternatively you can set the application's :attr:`~Flask.use_x_sendfile`
    attribute to ``True`` to directly emit an `X-Sendfile` header.  This
    however requires support of the underlying webserver for `X-Sendfile`.

    send_file will try to guess some stuff for you if you do not provide them:

    * mimetype (based on filename / attachment_filename)
    * mtime (based on filesystem file's metadata)
    * etag (based on filename, mtime, filesystem file size)

    If you do not provide enough information, send_file might raise a
    TypeError.

    For extra security you probably want to sent certain files as attachment
    (HTML for instance).

    Please never pass filenames to this function from user sources without
    checking them first.  Something like this is usually sufficient to
    avoid security problems::

        if '..' in filename or filename.startswith('/'):
            abort(404)

    :param filename: the filesystem filename of the file to send (relative to
                     the :attr:`~Flask.root_path` if a relative path is
                     specified).
                     If you just have an open filesystem file object f, give
                     `f.name` here.
                     If you don't have a filesystem file nor a filesystem file
                     name, but just a file-like obj, don't use this argument.
    :param file: a file (or file-like) object, you may give it if you either do
                 not have a filesystem filename or if you already have an open
                 file anyway.
    :param mimetype: the mimetype of the file if provided, otherwise
                     auto detection happens based on the filename or
                     attachment_filename.
    :param as_attachment: set to `True` if you want to send this file with
                          a ``Content-Disposition: attachment`` header.
    :param attachment_filename: the filename for the attachment if it
                                differs from the filename argument.
    :param mtime: the modification time of the file if provided, otherwise
                  it will be determined automatically for filesystem files
    :param cache_timeout: the timeout in seconds for the headers.
    :param conditional: set to `True` to enable conditional responses.
    :param add_etags: set to `False` to disable attaching of etags.
    :param etag: you can give an etag here, None means to try to compute the
                 etag from the file's filesystem metadata (the latter of course
                 only works for filesystem files). If you do not give a
                 filename, but you use add_etags, you must explicitely provide
                 the etag as it can't compute it for that case.
    """
    if filename and not os.path.isabs(filename):
        filename = os.path.join(current_app.root_path, filename)

    if mimetype is None and (filename or attachment_filename):
        mimetype = mimetypes.guess_type(filename or attachment_filename)[0]
    if mimetype is None:
        mimetype = 'application/octet-stream'

    headers = Headers()

    # We must compute size the smart way rather than letting
    # werkzeug turn our iterable into an in-memory sequence
    # See `_ensure_sequence` in werkzeug/wrappers.py
    if filename:
        fsize = os.path.getsize(filename)
    elif file and hasattr(file, 'seek') and hasattr(file, 'tell'):
        fsize = None
        # be extra careful as some file-like objects (like zip members) have a seek
        # and tell methods, but they just raise some exception (e.g. UnsupportedOperation)
        # instead of really doing what they are supposed to do (or just be missing).
        try:
            file.seek(0, 2)  # seek to EOF
            try:
                fsize = file.tell()  # tell position
            except Exception:
                pass
            file.seek(0, 0)  # seek to start of file
        except Exception:
            pass
    else:
        fsize = None
    if fsize is not None:
        headers.add('Content-Length', fsize)

    if as_attachment:
        if attachment_filename is None:
            if not filename:
                raise TypeError('filename unavailable, required for sending as attachment')
            attachment_filename = os.path.basename(filename)
        # Note: we only give filename* param, not filename param, hoping that a user agent that
        # does not support filename* then falls back into using the last URL fragment (and decodes
        # that correctly). See there for details: http://greenbytes.de/tech/tc2231/
        headers.add('Content-Disposition', 'attachment; filename*={0}'.format(encode_rfc2231(attachment_filename)))

    if current_app.use_x_sendfile and filename:
        if file:
            file.close()
        headers['X-Sendfile'] = filename
        data = None
    else:
        if filename:
            if not file:
                file = open(filename, 'rb')
            if mtime is None:
                mtime = os.path.getmtime(filename)
        data = wrap_file(request.environ, file)

    rv = current_app.response_class(data, mimetype=mimetype, headers=headers, direct_passthrough=True)

    # if we know the file modification date, we can store it as the
    # current time to better support conditional requests.  Werkzeug
    # as of 0.6.1 will override this value however in the conditional
    # response with the current time.  This will be fixed in Werkzeug
    # with a new release, however many WSGI servers will still emit
    # a separate date header.
    if mtime is not None:
        rv.date = int(mtime)

    rv.cache_control.public = True
    if cache_timeout:
        rv.cache_control.max_age = cache_timeout
        rv.expires = int(time() + cache_timeout)

    if add_etags:
        if etag is None and filename:
            etag = 'flask-{0}-{1}-{2}'.format(
                mtime or os.path.getmtime(filename),
                os.path.getsize(filename),
                adler32(filename) & 0xffffffff
            )
        if etag is None:
            raise TypeError("can't determine etag - please give etag or filename")
        rv.set_etag(etag)
        if conditional:
            rv = rv.make_conditional(request)
            # make sure we don't send x-sendfile for servers that
            # ignore the 304 status code for x-sendfile.
            if rv.status_code == 304:
                rv.headers.pop('x-sendfile', None)
    return rv
Ejemplo n.º 23
0
def send_file(filename_or_fp, mimetype=None, as_attachment=False,
              attachment_filename=None, add_etags=True,
              cache_timeout=60 * 60 * 12, conditional=False):
    """Sends the contents of a file to the client.  This will use the
    most efficient method available and configured.  By default it will
    try to use the WSGI server's file_wrapper support.  Alternatively
    you can set the application's :attr:`~Flask.use_x_sendfile` attribute
    to ``True`` to directly emit an `X-Sendfile` header.  This however
    requires support of the underlying webserver for `X-Sendfile`.

    By default it will try to guess the mimetype for you, but you can
    also explicitly provide one.  For extra security you probably want
    to send certain files as attachment (HTML for instance).  The mimetype
    guessing requires a `filename` or an `attachment_filename` to be
    provided.

    Please never pass filenames to this function from user sources without
    checking them first.  Something like this is usually sufficient to
    avoid security problems::

        if '..' in filename or filename.startswith('/'):
            abort(404)

    .. versionadded:: 0.2

    .. versionadded:: 0.5
       The `add_etags`, `cache_timeout` and `conditional` parameters were
       added.  The default behaviour is now to attach etags.

    .. versionchanged:: 0.7
       mimetype guessing and etag support for file objects was
       deprecated because it was unreliable.  Pass a filename if you are
       able to, otherwise attach an etag yourself.  This functionality
       will be removed in Flask 1.0

    :param filename_or_fp: the filename of the file to send.  This is
                           relative to the :attr:`~Flask.root_path` if a
                           relative path is specified.
                           Alternatively a file object might be provided
                           in which case `X-Sendfile` might not work and
                           fall back to the traditional method.  Make sure
                           that the file pointer is positioned at the start
                           of data to send before calling :func:`send_file`.
    :param mimetype: the mimetype of the file if provided, otherwise
                     auto detection happens.
    :param as_attachment: set to `True` if you want to send this file with
                          a ``Content-Disposition: attachment`` header.
    :param attachment_filename: the filename for the attachment if it
                                differs from the file's filename.
    :param add_etags: set to `False` to disable attaching of etags.
    :param conditional: set to `True` to enable conditional responses.
    :param cache_timeout: the timeout in seconds for the headers.
    """
    mtime = None
    if isinstance(filename_or_fp, basestring):
        filename = filename_or_fp
        file = None
    else:
        from warnings import warn
        file = filename_or_fp
        filename = getattr(file, 'name', None)

        # XXX: this behaviour is now deprecated because it was unreliable.
        # removed in Flask 1.0
        if not attachment_filename and not mimetype \
           and isinstance(filename, basestring):
            warn(DeprecationWarning('The filename support for file objects '
                'passed to send_file is not deprecated.  Pass an '
                'attach_filename if you want mimetypes to be guessed.'),
                stacklevel=2)
        if add_etags:
            warn(DeprecationWarning('In future flask releases etags will no '
                'longer be generated for file objects passed to the send_file '
                'function because this behaviour was unreliable.  Pass '
                'filenames instead if possible, otherwise attach an etag '
                'yourself based on another value'), stacklevel=2)

    if filename is not None:
        if not os.path.isabs(filename):
            filename = os.path.join(current_app.root_path, filename)
    if mimetype is None and (filename or attachment_filename):
        mimetype = mimetypes.guess_type(filename or attachment_filename)[0]
    if mimetype is None:
        mimetype = 'application/octet-stream'

    headers = Headers()
    if as_attachment:
        if attachment_filename is None:
            if filename is None:
                raise TypeError('filename unavailable, required for '
                                'sending as attachment')
            attachment_filename = os.path.basename(filename)
        headers.add('Content-Disposition', 'attachment',
                    filename=attachment_filename)

    if current_app.use_x_sendfile and filename:
        if file is not None:
            file.close()
        headers['X-Sendfile'] = filename
        data = None
    else:
        if file is None:
            file = open(filename, 'rb')
            mtime = os.path.getmtime(filename)
        data = wrap_file(request.environ, file)

    rv = current_app.response_class(data, mimetype=mimetype, headers=headers,
                                    direct_passthrough=True)

    # if we know the file modification date, we can store it as the
    # the time of the last modification.
    if mtime is not None:
        rv.last_modified = int(mtime)

    rv.cache_control.public = True
    if cache_timeout:
        rv.cache_control.max_age = cache_timeout
        rv.expires = int(time() + cache_timeout)

    if add_etags and filename is not None:
        rv.set_etag('flask-%s-%s-%s' % (
            os.path.getmtime(filename),
            os.path.getsize(filename),
            adler32(filename) & 0xffffffff
        ))
        if conditional:
            rv = rv.make_conditional(request)
            # make sure we don't send x-sendfile for servers that
            # ignore the 304 status code for x-sendfile.
            if rv.status_code == 304:
                rv.headers.pop('x-sendfile', None)
    return rv
Ejemplo n.º 24
0
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')
    """
    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 = os.path.basename(filename)
    if action == 'download':
        headers.append(('Content-Disposition', 'attachment; filename=%s' % d_filename))
    elif action == 'inline':
        headers.append(('Content-Disposition', 'inline; filename=%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:
        #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)