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