comparison MoinMoin/util/send_file.py @ 0:5568cf133caf moin20-repo-reboot

create moin/2.0 repo, drop all history (see notes below) Up to now, we used the moin/2.0-dev repository (which was cloned from another, older moin repo quite some time ago). Over the years, these repositories got rather fat (>200MB) and were a pain to clone over slow, high-latency or unreliable connections. After having finished most of the dirty work in moin2, having killed all the 3rd party code we had bundled with (is now installed by quickinstall / pip / setuptools), it is now a good time to get rid of the history (the history made up most of the repository's size). If you need to look at the history, look there: http://hg.moinmo.in/moin/2.0-dev The new moin/2.0 repository has the files as of this changesets: http://hg.moinmo.in/moin/2.0-dev/rev/075132a755dc The changeset hashes that link the repositories will be tagged (in both repositories) as "moin20-repo-reboot".
author Thomas Waldmann <tw AT waldmann-edv DOT de>
date Sun, 20 Feb 2011 20:53:45 +0100
parents
children 3e888522973b
comparison
equal deleted inserted replaced
-1:000000000000 0:5568cf133caf
1 """
2 A better send_file
3 ------------------
4
5 Initially, this was a modified implementation of flask 0.6.0's send_file(),
6 trying to be as compatible as possible.
7
8 For details see: https://github.com/mitsuhiko/flask/issues/issue/104 and the
9 history of this file in our repository. This code fixes all the issues
10 described in the bug report.
11
12 As we forked send_file, we later modified it (without trying to stay
13 compatible), because we can easily adapt anyway and the code can be much
14 simpler without compatibility code.
15
16 This code is under same license as flask.
17 Modifications were done by Thomas Waldmann.
18 """
19
20 import os
21 import mimetypes
22 from time import time
23 from zlib import adler32
24
25 from werkzeug import Headers, wrap_file
26 from flask import current_app, request
27
28
29 def send_file(filename=None, file=None,
30 mimetype=None,
31 as_attachment=False, attachment_filename=None,
32 mtime=None, cache_timeout=60 * 60 * 12,
33 add_etags=True, etag=None, conditional=False):
34 """Sends the contents of a file to the client.
35
36 A file can be either a filesystem file or a file-like object (this code
37 is careful about not assuming that every file is a filesystem file).
38
39 This will use the most efficient method available, configured and possible
40 (for filesystem files some more optimizations may be possible that for
41 file-like objects not having a filesystem filename).
42 By default it will try to use the WSGI server's file_wrapper support.
43 Alternatively you can set the application's :attr:`~Flask.use_x_sendfile`
44 attribute to ``True`` to directly emit an `X-Sendfile` header. This
45 however requires support of the underlying webserver for `X-Sendfile`.
46
47 send_file will try to guess some stuff for you if you do not provide them:
48
49 * mimetype (based on filename / attachment_filename)
50 * mtime (based on filesystem file's metadata)
51 * etag (based on filename, mtime, filesystem file size)
52
53 If you do not provide enough information, send_file might raise a
54 TypeError.
55
56 For extra security you probably want to sent certain files as attachment
57 (HTML for instance).
58
59 Please never pass filenames to this function from user sources without
60 checking them first. Something like this is usually sufficient to
61 avoid security problems::
62
63 if '..' in filename or filename.startswith('/'):
64 abort(404)
65
66 :param filename: the filesystem filename of the file to send (relative to
67 the :attr:`~Flask.root_path` if a relative path is
68 specified).
69 If you just have an open filesystem file object f, give
70 `f.name` here.
71 If you don't have a filesystem file nor a filesystem file
72 name, but just a file-like obj, don't use this argument.
73 :param file: a file (or file-like) object, you may give it if you either do
74 not have a filesystem filename or if you already have an open
75 file anyway.
76 :param mimetype: the mimetype of the file if provided, otherwise
77 auto detection happens based on the filename or
78 attachment_filename.
79 :param as_attachment: set to `True` if you want to send this file with
80 a ``Content-Disposition: attachment`` header.
81 :param attachment_filename: the filename for the attachment if it
82 differs from the filename argument.
83 :param mtime: the modification time of the file if provided, otherwise
84 it will be determined automatically for filesystem files
85 :param cache_timeout: the timeout in seconds for the headers.
86 :param conditional: set to `True` to enable conditional responses.
87 :param add_etags: set to `False` to disable attaching of etags.
88 :param etag: you can give an etag here, None means to try to compute the
89 etag from the file's filesystem metadata (the latter of course
90 only works for filesystem files). If you do not give a
91 filename, but you use add_etags, you must explicitely provide
92 the etag as it can't compute it for that case.
93 """
94 if filename and not os.path.isabs(filename):
95 filename = os.path.join(current_app.root_path, filename)
96
97 if mimetype is None and (filename or attachment_filename):
98 mimetype = mimetypes.guess_type(filename or attachment_filename)[0]
99 if mimetype is None:
100 mimetype = 'application/octet-stream'
101
102 headers = Headers()
103 if as_attachment:
104 if attachment_filename is None:
105 if not filename:
106 raise TypeError('filename unavailable, required for sending as attachment')
107 attachment_filename = os.path.basename(filename)
108 headers.add('Content-Disposition', 'attachment', filename=attachment_filename)
109
110 if current_app.use_x_sendfile and filename:
111 if file:
112 file.close()
113 headers['X-Sendfile'] = filename
114 data = None
115 else:
116 if filename:
117 if not file:
118 file = open(filename, 'rb')
119 if mtime is None:
120 mtime = os.path.getmtime(filename)
121 data = wrap_file(request.environ, file)
122
123 rv = current_app.response_class(data, mimetype=mimetype, headers=headers, direct_passthrough=True)
124
125 # if we know the file modification date, we can store it as the
126 # current time to better support conditional requests. Werkzeug
127 # as of 0.6.1 will override this value however in the conditional
128 # response with the current time. This will be fixed in Werkzeug
129 # with a new release, however many WSGI servers will still emit
130 # a separate date header.
131 if mtime is not None:
132 rv.date = int(mtime)
133
134 rv.cache_control.public = True
135 if cache_timeout:
136 rv.cache_control.max_age = cache_timeout
137 rv.expires = int(time() + cache_timeout)
138
139 if add_etags:
140 if etag is None and filename:
141 etag = 'flask-%s-%s-%s' % (
142 mtime or os.path.getmtime(filename),
143 os.path.getsize(filename),
144 adler32(filename) & 0xffffffff
145 )
146 if etag is None:
147 raise TypeError("can't determine etag - please give etag or filename")
148 rv.set_etag(etag)
149 if conditional:
150 rv = rv.make_conditional(request)
151 # make sure we don't send x-sendfile for servers that
152 # ignore the 304 status code for x-sendfile.
153 if rv.status_code == 304:
154 rv.headers.pop('x-sendfile', None)
155 return rv
156