Ticket #1953: safemultipart-filter.diff

File safemultipart-filter.diff, 7.7 kB (added by Chris Arndt, 2 years ago)

New version of patch. Renamed FlashMultipartFilter to SafeMultipartFilter

  • turbogears/startup.py

    old new  
    3030 
    3131from turbogears import config, database, scheduler, view 
    3232from turbogears.database import hub_registry, EndTransactionsFilter 
    33 from turbogears.filters import NestedVariablesFilter, VirtualPathFilter 
     33from turbogears.filters import (NestedVariablesFilter, SafeMultipartFilter, 
     34    VirtualPathFilter) 
    3435 
    3536 
    3637# module globals 
     
    189190    if getattr(cherrypy, 'root', None): 
    190191        if not hasattr(cherrypy.root, '_cp_filters'): 
    191192            cherrypy.root._cp_filters = [] 
    192         cherrypy.root._cp_filters.extend([VirtualPathFilter(webpath), 
    193             EndTransactionsFilter(), NestedVariablesFilter()]) 
     193        cherrypy.root._cp_filters.extend([ 
     194            VirtualPathFilter(webpath), 
     195            EndTransactionsFilter(), 
     196            NestedVariablesFilter(), 
     197            SafeMultipartFilter() 
     198        ]) 
    194199 
    195200    webpath = webpath.lstrip('/') 
    196201    if webpath and not webpath.endswith('/'): 
  • turbogears/qstemplates/quickstart/+package+/config/app.cfg_tmpl

    old new  
    220220# gzip_filter.mime_types = ["application/json", "application/x-javascript", 
    221221#     "text/javascript", "text/html", "text/css", "text/plain"] 
    222222 
     223# Some buggy Flash file upload clients (e.g. on Mac OS X) do not end multipart 
     224# requests correctly with CR/LF, which breaks the request parsing of CherryPy. 
     225# Set 'safempfilter.on' to True to enable a workaround. 
     226# You should restrict this filter to the URL paths which will actually have to 
     227# handle file uploads from Flash. For example: 
     228#[/upload] 
     229#safempfilter.on = True 
     230 
    223231[/static] 
    224232static_filter.on = True 
    225233static_filter.dir = "%(top_level_dir)s/static" 
  • turbogears/filters/__init__.py

    old new  
    11# -*- coding: UTF-8 -*- 
    2 """Package collection various request filters.""" 
     2"""Package collecting various request filters.""" 
    33 
    44from base import NestedVariablesFilter, VirtualPathFilter 
     5from safemultipart import SafeMultipartFilter 
  • turbogears/filters/safemultipart.py

    old new  
     1# -*- coding: UTF-8 -*- 
     2"""Request filter to handle multipart uploads from buggy Flash clients.""" 
     3 
     4__all__ = [ 
     5    'SafeMultipartFilter', 
     6    'MultipartWrapper', 
     7] 
     8 
     9import logging 
     10import re 
     11 
     12import cherrypy 
     13from turbogears import config 
     14 
     15 
     16log = logging.getLogger('turbogears.filters') 
     17 
     18 
     19# Taken from http://www.cherrypy.org/ticket/648 
     20class 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 
     125class 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)