Download Install Tutorial Docs FAQ Tools WikiLicense Team IRC Planet Involvement Shop Book

Ticket #546: uploadfilter.py

Line 
1 '''
2 UploadFilter - File upload functionality.
3     2006, James Kassemi - http://www.kepty.com
4
5 If you allow users to upload files to your site you're definitely going to want
6 to use the uploadfilter.max_concurrent setting, and set it to less than the
7 number of threads in your server.thread_pool setting. Without it you'll be
8 opening your site up to a simple dos if there are a number of concurrent
9 file uploads that utilize all of your threads.
10
11 As you'll be doing anyway, make sure that the enctype of your form is
12 multipart/form-data, as that's what we'll be using to determine whether or not
13 to track a file upload.
14
15 Configuration:
16     - uploadfilter.max_concurrent
17         Set the number of files that can be concurrently uploaded to the site.
18         If the number exceeds the number set here, Upload_MaxConcError will be
19         raised.
20
21     - uploadfilter.max_size
22         Size, in kb, to limit uploaded files to. This will check both the
23         header version, but in case that's spoofed, it will also check during
24         the writing of the file to the temporary area. Raises
25         Upload_MaxSizeError if the size exceeds this number. This will
26         also override cherrypy.max_request_body_size for this area, so you don't
27         have to worry about conflicting with that. If this is NOT set then
28         you'll be dealing with the max_request_body_size, and we'll do NO
29         checks.
30
31     - uploadfilter.timeout
32         Time cap. will raise Upload_TimeoutError if the user has been uploading a file
33         for longer than the value set here.
34
35     - uploadfilter.explicit
36         Tells the system to check whether or not pages allows uploads. Set
37         this at a root directory, and then add
38
39             uploadfilter.declared=True
40
41         where a page accepts file uploads. This prevents someone from posting file
42         data to other fields, tying up your bandwidth by exploiting the fact cp
43         will upload the file before you can check it.
44
45     - uploadfilter.min_upspeed
46         To keep someone from maintaining a connection and tying up a thread by
47         uploading at a VERY slow rate, you can set this value (make sure it's
48         somewhat low). It will raise Upload_UpSpeedError if the user's average
49         upload speed drops below this value. the uploadfilter.timeout filter
50         can be used as an alternative, but this might be preferable, depending
51         on your situation.
52
53 Real-time statistics:
54     The 'file_transfers' attribute is added to the cherrypy object, and can be
55     used to keep track of files being uploaded from a remote host. The format
56     is as follows:
57
58     cherrypy.file_transfers[remote_addr][filename] = ProgressFile object
59
60     And the ProgressFile object will maintain these attributes:
61         - transfered          byte size of transfered data thus far.
62         - speed               bytes/sec
63         - remaining           bytes remaining
64         - eta                 estimated seconds until arrival
65
66     It's possible to create an AJAX-style interface to show the user the status
67     of their file uploads now, so long as you have an available thread to take
68     the requests for it...
69 '''
70
71 import cgi
72 import cherrypy
73 import tempfile
74 import time
75
76 from cherrypy.filters.basefilter import BaseFilter
77
78 class Upload_MaxConcError(Exception):
79     pass
80 class Upload_TimeoutError(Exception):
81     pass
82 class Upload_MaxSizeError(Exception):
83     pass
84 class Upload_UnauthorizedError(Exception):
85     pass
86 class Upload_UpSpeedError(Exception):
87     pass
88
89 current_uploads = 0
90 cherrypy.file_transfers = dict()
91
92 class ProgressFile(object):
93     def __init__(self, buf, *args, **kwargs):
94         self.file_object = tempfile.TemporaryFile(
95                 *args, **kwargs)
96         self.transfered = 0
97         self.buf = buf
98         self.pre_sized = float(cherrypy.request.headers['Content-length'])
99         self.speed = 1
100         self.remaining = 0
101         self.eta = 0
102         self._start = time.time()
103
104     def write(self, data):
105         now = time.time()
106         self.transfered += len(data)
107         upload_timeout = getattr(cherrypy.thread_data, 'upload_timeout', False)
108         if upload_timeout:
109             if (now - self._start) > upload_timeout:
110                 raise Upload_TimeoutError
111
112         upload_maxsize = getattr(cherrypy.thread_data, 'upload_maxsize', False)
113         if upload_maxsize:
114             if self.transfered > upload_maxsize:
115                 raise Upload_MaxSizeError
116
117         self.speed = self.transfered / (now - self._start)
118
119         upload_minspeed = getattr(cherrypy.thread_data, 'upload_minspeed', False)
120         if upload_minspeed:
121             if self.transfered > (5 * self.buf): # gives us a reasonable wait period.
122                 if self.speed < upload_minspeed:
123                     raise Upload_UpSpeedError
124
125         self.remaining = self.pre_sized - self.transfered
126
127         if self.speed == 0: self.eta = 9999999
128         else: self.eta = self.remaining / self.speed
129
130         return self.file_object.write(data)
131
132     def seek(self, pos):
133         self.post_sized = self.transfered
134         self.transfered = True
135         return self.file_object.seek(pos)
136
137     def read(self, size):
138         return self.file_object.read(size)
139
140 class FieldStorage(cherrypy._cpcgifs.FieldStorage):
141     ''' We want control over our timing and download status,
142         so we've got to override the original. This will work
143         transparently without interfering with the user, but
144         might warrant addition to _cpcgifs '''
145
146     def __del__(self, *args, **kwargs):
147         try:
148             dcopy = cherrypy.file_transfers[cherrypy.request.remote_addr].copy()
149             for key, val in dcopy.iteritems():
150                 if val.transfered == True:
151                     del cherrypy.file_transfers[cherrypy.request.remote_addr][key]
152             del dcopy
153             if len(cherrypy.file_transfers[cherrypy.request.remote_addr]) == 0:
154                 del cherrypy.file_transfers[cherrypy.request.remote_addr]
155
156         except KeyError:
157             pass
158
159     def make_file(self, binary=None):
160         fo = ProgressFile(self.bufsize)
161         if cherrypy.file_transfers.has_key(cherrypy.request.remote_addr):
162             cherrypy.file_transfers[cherrypy.request.remote_addr]\
163                     [self.filename] = fo
164         else:
165             cherrypy.file_transfers[cherrypy.request.remote_addr]\
166                     = {self.filename:fo}
167
168         return fo
169
170 cherrypy._cpcgifs.FieldStorage = FieldStorage
171
172 class UploadFilter(BaseFilter):
173
174     #def on_start_resource(self):
175     #    cherrypy.request.rfile = cherrypy.request.rfile.rfile
176
177     def before_request_body(self):
178         global current_uploads
179
180         if not cherrypy.config.get('upload_filter.on', False):
181             return
182
183         if cherrypy.request.headers.get('Content-Type', '').split(';')[0] ==\
184             'multipart/form-data':
185
186             upload_explicit = cherrypy.config.get('upload_filter.explicit', False)
187             upload_declared = cherrypy.config.get('upload_filter.declared', False)
188             upload_limit = cherrypy.config.get('upload_filter.max_concurrent', False)
189             upload_timeout = cherrypy.config.get('upload_filter.timeout', False)
190             upload_maxsize = cherrypy.config.get('upload_filter.max_size', False)
191             upload_minspeed = cherrypy.config.get('upload_filter.min_upspeed', False)
192
193
194             cherrypy.thread_data.upload_minspeed = upload_minspeed
195
196             if upload_explicit and not upload_declared:
197                 raise Upload_UnauthorizedError
198
199             if upload_limit:
200                 if current_uploads > upload_limit:
201                     raise Upload_MaxConcError
202                 current_uploads += 1
203
204             if upload_timeout:
205                 cherrypy.thread_data.upload_timeout = upload_timeout
206
207             if upload_maxsize:
208                 upload_maxsize *= 1024
209                 cherrypy.thread_data.upload_maxsize = upload_maxsize
210                 size = float(cherrypy.request.headers['Content-length'])
211                 if size > upload_maxsize:
212                     raise Upload_MaxSizeError
213                 cherrypy.request.rfile = cherrypy.request.rfile.rfile
214
215     def on_end_resource(self):
216         global current_uploads
217
218         if not cherrypy.config.get('upload_limit_filter.on', False):
219             return
220
221         if cherrypy.request.headers.get('Content-Type', '').split(';')[0] ==\
222             'multipart/form-data':
223
224             upload_explicit = cherrypy.config.get('upload_filter.explicit',
225                     False)
226             upload_declared = cherrypy.config.get('upload_filter.declared',
227                     False)
228             upload_limit = cherrypy.config.get('upload_filter.max_concurrent',
229                     False)
230             upload_timeout = cherrypy.config.get('upload_filter.timeout',
231                     False)
232             upload_maxsize = cherrypy.config.get('upload_filter.max_size',
233                     False)
234
235             if upload_explicit and not upload_declared:
236                 return
237
238             if upload_limit:
239                 current_uploads -= 1
240
241             if upload_timeout:
242                 del cherrypy.thread_data.upload_timeout
243
244             if upload_maxsize:
245                 del cherrypy.thread_data.upload_maxsize

Hosted by WebFaction

Log in as guest/cpguest to create tickets