| | 1 | # -*- coding: UTF-8 -*- |
|---|
| | 2 | """Request filter to handle multipart uploads from buggy Flash clients.""" |
|---|
| | 3 | |
|---|
| | 4 | __all__ = [ |
|---|
| | 5 | 'SafeMultipartFilter', |
|---|
| | 6 | 'MultipartWrapper', |
|---|
| | 7 | ] |
|---|
| | 8 | |
|---|
| | 9 | import logging |
|---|
| | 10 | import re |
|---|
| | 11 | |
|---|
| | 12 | import cherrypy |
|---|
| | 13 | from turbogears import config |
|---|
| | 14 | |
|---|
| | 15 | |
|---|
| | 16 | log = logging.getLogger('turbogears.filters') |
|---|
| | 17 | |
|---|
| | 18 | |
|---|
| | 19 | # Taken from http://www.cherrypy.org/ticket/648 |
|---|
| | 20 | class MultipartWrapper(object): |
|---|
| | 21 | r"""Wraps a file-like object, returning '' when Content-Length is reached. |
|---|
| | 22 | |
|---|
| | 23 | The cgi module's logic for reading multipart MIME messages doesn't |
|---|
| | 24 | allow the parts to know when the Content-Length for the entire message |
|---|
| | 25 | has been reached, and doesn't allow for multipart-MIME messages that |
|---|
| | 26 | omit the trailing CRLF (Flash 8's FileReference.upload(url), for example, |
|---|
| | 27 | does this). The read_lines_to_outerboundary function gets stuck in a loop |
|---|
| | 28 | until the socket times out. |
|---|
| | 29 | |
|---|
| | 30 | This rfile wrapper simply monitors the incoming stream. When a read is |
|---|
| | 31 | attempted past the Content-Length, it returns an empty string rather |
|---|
| | 32 | than timing out (of course, if the last read *overlaps* the C-L, you'll |
|---|
| | 33 | get the last bit of data up to C-L, and then the next read will return |
|---|
| | 34 | an empty string). |
|---|
| | 35 | |
|---|
| | 36 | """ |
|---|
| | 37 | def __init__(self, rfile, clen): |
|---|
| | 38 | self.rfile = rfile |
|---|
| | 39 | self.clen = clen |
|---|
| | 40 | self.bytes_read = 0 |
|---|
| | 41 | |
|---|
| | 42 | def read(self, size=None): |
|---|
| | 43 | if self.clen: |
|---|
| | 44 | # Return '' if we've read all the data. |
|---|
| | 45 | if self.bytes_read >= self.clen: |
|---|
| | 46 | return '' |
|---|
| | 47 | |
|---|
| | 48 | # Reduce 'size' if it's over our limit. |
|---|
| | 49 | new_bytes_read = self.bytes_read + size |
|---|
| | 50 | if new_bytes_read > self.clen: |
|---|
| | 51 | size = self.clen - self.bytes_read |
|---|
| | 52 | |
|---|
| | 53 | data = self.rfile.read(size) |
|---|
| | 54 | self.bytes_read += len(data) |
|---|
| | 55 | return data |
|---|
| | 56 | |
|---|
| | 57 | def readline(self, size=None): |
|---|
| | 58 | if size is not None: |
|---|
| | 59 | if self.clen: |
|---|
| | 60 | # Return '' if we've read all the data. |
|---|
| | 61 | if self.bytes_read >= self.clen: |
|---|
| | 62 | return '' |
|---|
| | 63 | |
|---|
| | 64 | # Reduce 'size' if it's over our limit. |
|---|
| | 65 | new_bytes_read = self.bytes_read + size |
|---|
| | 66 | if new_bytes_read > self.clen: |
|---|
| | 67 | size = self.clen - self.bytes_read |
|---|
| | 68 | |
|---|
| | 69 | data = self.rfile.readline(size) |
|---|
| | 70 | self.bytes_read += len(data) |
|---|
| | 71 | return data |
|---|
| | 72 | |
|---|
| | 73 | # User didn't specify a size ... |
|---|
| | 74 | # We read the line in chunks to make sure it's not a 100MB line ! |
|---|
| | 75 | res = [] |
|---|
| | 76 | size = 256 |
|---|
| | 77 | while True: |
|---|
| | 78 | if self.clen: |
|---|
| | 79 | # Return if we've read all the data. |
|---|
| | 80 | if self.bytes_read >= self.clen: |
|---|
| | 81 | return ''.join(res) |
|---|
| | 82 | |
|---|
| | 83 | # Reduce 'size' if it's over our limit. |
|---|
| | 84 | new_bytes_read = self.bytes_read + size |
|---|
| | 85 | if new_bytes_read > self.clen: |
|---|
| | 86 | size = self.clen - self.bytes_read |
|---|
| | 87 | |
|---|
| | 88 | data = self.rfile.readline(size) |
|---|
| | 89 | self.bytes_read += len(data) |
|---|
| | 90 | res.append(data) |
|---|
| | 91 | # See http://www.cherrypy.org/ticket/421 |
|---|
| | 92 | if len(data) < size or data[-1:] == "\n": |
|---|
| | 93 | return ''.join(res) |
|---|
| | 94 | |
|---|
| | 95 | def readlines(self, sizehint=0): |
|---|
| | 96 | # Shamelessly stolen from StringIO |
|---|
| | 97 | total = 0 |
|---|
| | 98 | lines = [] |
|---|
| | 99 | line = self.readline() |
|---|
| | 100 | while line: |
|---|
| | 101 | lines.append(line) |
|---|
| | 102 | total += len(line) |
|---|
| | 103 | if 0 < sizehint <= total: |
|---|
| | 104 | break |
|---|
| | 105 | line = self.readline() |
|---|
| | 106 | return lines |
|---|
| | 107 | |
|---|
| | 108 | def close(self): |
|---|
| | 109 | self.rfile.close() |
|---|
| | 110 | |
|---|
| | 111 | def __iter__(self): |
|---|
| | 112 | return self.rfile |
|---|
| | 113 | |
|---|
| | 114 | def next(self): |
|---|
| | 115 | if self.clen: |
|---|
| | 116 | # Return '' if we've read all the data. |
|---|
| | 117 | if self.bytes_read >= self.clen: |
|---|
| | 118 | return '' |
|---|
| | 119 | |
|---|
| | 120 | data = self.rfile.next() |
|---|
| | 121 | self.bytes_read += len(data) |
|---|
| | 122 | return data |
|---|
| | 123 | |
|---|
| | 124 | |
|---|
| | 125 | class SafeMultipartFilter: |
|---|
| | 126 | """CherryPy filter to handle buggy multipart requests from Flash clients.""" |
|---|
| | 127 | |
|---|
| | 128 | flash_ua_rx = re.compile(r'(Shockwave|Adobe)\s+Flash.*') |
|---|
| | 129 | |
|---|
| | 130 | def before_request_body(self): |
|---|
| | 131 | if not config.get("safempfilter.on", False): |
|---|
| | 132 | return |
|---|
| | 133 | log.debug("Using FlashMultipartFilter for request %s.", |
|---|
| | 134 | cherrypy.request.path) |
|---|
| | 135 | ct = cherrypy.request.headers.get('Content-Type', '') |
|---|
| | 136 | ua = cherrypy.request.headers.get('User-Agent', '') |
|---|
| | 137 | |
|---|
| | 138 | if ct.startswith('multipart/') and self.flash_ua_rx.match(ua): |
|---|
| | 139 | log.debug("Detected Flash client multipart request. " |
|---|
| | 140 | "Using MulipartWrapper to read request") |
|---|
| | 141 | clen = cherrypy.request.headers.get('Content-Length', '0') |
|---|
| | 142 | try: |
|---|
| | 143 | clen = int(clen) |
|---|
| | 144 | except ValueError: |
|---|
| | 145 | return |
|---|
| | 146 | cherrypy.request.rfile = MultipartWrapper( |
|---|
| | 147 | cherrypy.request.rfile, clen) |