1 import struct
2 import time
3
4 import cherrypy
5 from cherrypy._cpcompat import basestring, BytesIO, ntob, set, unicodestr
6 from cherrypy.lib import file_generator
7 from cherrypy.lib import is_closable_iterator
8 from cherrypy.lib import set_vary_header
9
10
11 -def decode(encoding=None, default_encoding='utf-8'):
36
39 self._iterator = iterator
40
43
46
48 res = next(self._iterator)
49 if isinstance(res, unicodestr):
50 res = res.encode('utf-8')
51 return res
52
56
58 if attr.startswith('__'):
59 raise AttributeError(self, attr)
60 return getattr(self._iterator, attr)
61
62
64
65 default_encoding = 'utf-8'
66 failmsg = "Response body could not be encoded with %r."
67 encoding = None
68 errors = 'strict'
69 text_only = True
70 add_charset = True
71 debug = False
72
85
87 """Encode a streaming response body.
88
89 Use a generator wrapper, and just pray it works as the stream is
90 being written out.
91 """
92 if encoding in self.attempted_charsets:
93 return False
94 self.attempted_charsets.add(encoding)
95
96 def encoder(body):
97 for chunk in body:
98 if isinstance(chunk, unicodestr):
99 chunk = chunk.encode(encoding, self.errors)
100 yield chunk
101 self.body = encoder(self.body)
102 return True
103
105 """Encode a buffered response body."""
106 if encoding in self.attempted_charsets:
107 return False
108 self.attempted_charsets.add(encoding)
109 body = []
110 for chunk in self.body:
111 if isinstance(chunk, unicodestr):
112 try:
113 chunk = chunk.encode(encoding, self.errors)
114 except (LookupError, UnicodeError):
115 return False
116 body.append(chunk)
117 self.body = body
118 return True
119
121 request = cherrypy.serving.request
122 response = cherrypy.serving.response
123
124 if self.debug:
125 cherrypy.log('response.stream %r' %
126 response.stream, 'TOOLS.ENCODE')
127 if response.stream:
128 encoder = self.encode_stream
129 else:
130 encoder = self.encode_string
131 if "Content-Length" in response.headers:
132
133
134
135
136
137
138
139
140
141
142 del response.headers["Content-Length"]
143
144
145
146 encs = request.headers.elements('Accept-Charset')
147 charsets = [enc.value.lower() for enc in encs]
148 if self.debug:
149 cherrypy.log('charsets %s' % repr(charsets), 'TOOLS.ENCODE')
150
151 if self.encoding is not None:
152
153 encoding = self.encoding.lower()
154 if self.debug:
155 cherrypy.log('Specified encoding %r' %
156 encoding, 'TOOLS.ENCODE')
157 if (not charsets) or "*" in charsets or encoding in charsets:
158 if self.debug:
159 cherrypy.log('Attempting encoding %r' %
160 encoding, 'TOOLS.ENCODE')
161 if encoder(encoding):
162 return encoding
163 else:
164 if not encs:
165 if self.debug:
166 cherrypy.log('Attempting default encoding %r' %
167 self.default_encoding, 'TOOLS.ENCODE')
168
169 if encoder(self.default_encoding):
170 return self.default_encoding
171 else:
172 raise cherrypy.HTTPError(500, self.failmsg %
173 self.default_encoding)
174 else:
175 for element in encs:
176 if element.qvalue > 0:
177 if element.value == "*":
178
179 if self.debug:
180 cherrypy.log('Attempting default encoding due '
181 'to %r' % element, 'TOOLS.ENCODE')
182 if encoder(self.default_encoding):
183 return self.default_encoding
184 else:
185 encoding = element.value
186 if self.debug:
187 cherrypy.log('Attempting encoding %s (qvalue >'
188 '0)' % element, 'TOOLS.ENCODE')
189 if encoder(encoding):
190 return encoding
191
192 if "*" not in charsets:
193
194
195
196
197 iso = 'iso-8859-1'
198 if iso not in charsets:
199 if self.debug:
200 cherrypy.log('Attempting ISO-8859-1 encoding',
201 'TOOLS.ENCODE')
202 if encoder(iso):
203 return iso
204
205
206 ac = request.headers.get('Accept-Charset')
207 if ac is None:
208 msg = "Your client did not send an Accept-Charset header."
209 else:
210 msg = "Your client sent this Accept-Charset header: %s." % ac
211 _charsets = ", ".join(sorted(self.attempted_charsets))
212 msg += " We tried these charsets: %s." % (_charsets,)
213 raise cherrypy.HTTPError(406, msg)
214
267
268
269
270
272 """Compress 'body' at the given compress_level."""
273 import zlib
274
275
276 yield ntob('\x1f\x8b')
277 yield ntob('\x08')
278 yield ntob('\x00')
279
280 yield struct.pack("<L", int(time.time()) & int('FFFFFFFF', 16))
281 yield ntob('\x02')
282 yield ntob('\xff')
283
284 crc = zlib.crc32(ntob(""))
285 size = 0
286 zobj = zlib.compressobj(compress_level,
287 zlib.DEFLATED, -zlib.MAX_WBITS,
288 zlib.DEF_MEM_LEVEL, 0)
289 for line in body:
290 size += len(line)
291 crc = zlib.crc32(line, crc)
292 yield zobj.compress(line)
293 yield zobj.flush()
294
295
296 yield struct.pack("<L", crc & int('FFFFFFFF', 16))
297
298 yield struct.pack("<L", size & int('FFFFFFFF', 16))
299
300
302 import gzip
303
304 zbuf = BytesIO()
305 zbuf.write(body)
306 zbuf.seek(0)
307 zfile = gzip.GzipFile(mode='rb', fileobj=zbuf)
308 data = zfile.read()
309 zfile.close()
310 return data
311
312
313 -def gzip(compress_level=5, mime_types=['text/html', 'text/plain'],
314 debug=False):
315 """Try to gzip the response body if Content-Type in mime_types.
316
317 cherrypy.response.headers['Content-Type'] must be set to one of the
318 values in the mime_types arg before calling this function.
319
320 The provided list of mime-types must be of one of the following form:
321 * type/subtype
322 * type/*
323 * type/*+subtype
324
325 No compression is performed if any of the following hold:
326 * The client sends no Accept-Encoding request header
327 * No 'gzip' or 'x-gzip' is present in the Accept-Encoding header
328 * No 'gzip' or 'x-gzip' with a qvalue > 0 is present
329 * The 'identity' value is given with a qvalue > 0.
330
331 """
332 request = cherrypy.serving.request
333 response = cherrypy.serving.response
334
335 set_vary_header(response, "Accept-Encoding")
336
337 if not response.body:
338
339 if debug:
340 cherrypy.log('No response body', context='TOOLS.GZIP')
341 return
342
343
344
345 if getattr(request, "cached", False):
346 if debug:
347 cherrypy.log('Not gzipping cached response', context='TOOLS.GZIP')
348 return
349
350 acceptable = request.headers.elements('Accept-Encoding')
351 if not acceptable:
352
353
354
355
356
357
358
359 if debug:
360 cherrypy.log('No Accept-Encoding', context='TOOLS.GZIP')
361 return
362
363 ct = response.headers.get('Content-Type', '').split(';')[0]
364 for coding in acceptable:
365 if coding.value == 'identity' and coding.qvalue != 0:
366 if debug:
367 cherrypy.log('Non-zero identity qvalue: %s' % coding,
368 context='TOOLS.GZIP')
369 return
370 if coding.value in ('gzip', 'x-gzip'):
371 if coding.qvalue == 0:
372 if debug:
373 cherrypy.log('Zero gzip qvalue: %s' % coding,
374 context='TOOLS.GZIP')
375 return
376
377 if ct not in mime_types:
378
379
380
381
382
383
384
385 found = False
386 if '/' in ct:
387 ct_media_type, ct_sub_type = ct.split('/')
388 for mime_type in mime_types:
389 if '/' in mime_type:
390 media_type, sub_type = mime_type.split('/')
391 if ct_media_type == media_type:
392 if sub_type == '*':
393 found = True
394 break
395 elif '+' in sub_type and '+' in ct_sub_type:
396 ct_left, ct_right = ct_sub_type.split('+')
397 left, right = sub_type.split('+')
398 if left == '*' and ct_right == right:
399 found = True
400 break
401
402 if not found:
403 if debug:
404 cherrypy.log('Content-Type %s not in mime_types %r' %
405 (ct, mime_types), context='TOOLS.GZIP')
406 return
407
408 if debug:
409 cherrypy.log('Gzipping', context='TOOLS.GZIP')
410
411 response.headers['Content-Encoding'] = 'gzip'
412 response.body = compress(response.body, compress_level)
413 if "Content-Length" in response.headers:
414
415 del response.headers["Content-Length"]
416
417 return
418
419 if debug:
420 cherrypy.log('No acceptable encoding found.', context='GZIP')
421 cherrypy.HTTPError(406, "identity, gzip").set_response()
422