import cherrypy


class MultipartWrapper(object):
    """Wraps a file-like object, returning '' when Content-Length is reached.
    
    The cgi module's logic for reading multipart MIME messages doesn't
    allow the parts to know when the Content-Length for the entire message
    has been reached, and doesn't allow for multipart-MIME messages that
    omit the trailing CRLF (Flash 8's FileReference.upload(url), for example,
    does this). The read_lines_to_outerboundary function gets stuck in a loop
    until the socket times out.
    
    This rfile wrapper simply monitors the incoming stream. When a read is
    attempted past the Content-Length, it returns an empty string rather
    than timing out (of course, if the last read *overlaps* the C-L, you'll
    get the last bit of data up to C-L, and then the next read will return
    an empty string).
    """
    
    def __init__(self, rfile, clen):
        self.rfile = rfile
        self.clen = clen
        self.bytes_read = 0
    
    def read(self, size = None):
        if self.clen:
            # Return '' if we've read all the data.
            if self.bytes_read >= self.clen:
                return ''
            
            # Reduce 'size' if it's over our limit.
            new_bytes_read = self.bytes_read + size
            if new_bytes_read > self.clen:
                size = self.clen - self.bytes_read
        
        data = self.rfile.read(size)
        self.bytes_read += len(data)
        return data
    
    def readline(self, size = None):
        if size is not None:
            if self.clen:
                # Return '' if we've read all the data.
                if self.bytes_read >= self.clen:
                    return ''
                
                # Reduce 'size' if it's over our limit.
                new_bytes_read = self.bytes_read + size
                if new_bytes_read > self.clen:
                    size = self.clen - self.bytes_read
            
            data = self.rfile.readline(size)
            self.bytes_read += len(data)
            return data
        
        # User didn't specify a size ...
        # We read the line in chunks to make sure it's not a 100MB line !
        res = []
        size = 256
        while True:
            if self.clen:
                # Return if we've read all the data.
                if self.bytes_read >= self.clen:
                    return ''.join(res)
                
                # Reduce 'size' if it's over our limit.
                new_bytes_read = self.bytes_read + size
                if new_bytes_read > self.clen:
                    size = self.clen - self.bytes_read
            
            data = self.rfile.readline(size)
            self.bytes_read += len(data)
            res.append(data)
            # See http://www.cherrypy.org/ticket/421
            if len(data) < size or data[-1:] == "\n":
                return ''.join(res)
    
    def readlines(self, sizehint = 0):
        # Shamelessly stolen from StringIO
        total = 0
        lines = []
        line = self.readline()
        while line:
            lines.append(line)
            total += len(line)
            if 0 < sizehint <= total:
                break
            line = self.readline()
        return lines
    
    def close(self):
        self.rfile.close()
    
    def __iter__(self):
        return self.rfile
    
    def next(self):
        if self.clen:
            # Return '' if we've read all the data.
            if self.bytes_read >= self.clen:
                return ''
        
        data = self.rfile.next()
        self.bytes_read += len(data)
        return data


def safe_multipart(flash_only=False):
    """Wrap request.rfile in a reader that won't crash on no trailing CRLF."""
    h = cherrypy.request.headers
    if not h.get('Content-Type','').startswith('multipart/'):
        return
    if flash_only and not 'Shockwave Flash' in h.get('User-Agent', ''):
        return
    
    clen = h.get('Content-Length', '0')
    try:
        clen = int(clen)
    except ValueError:
        return
    cherrypy.request.rfile = MultipartWrapper(cherrypy.request.rfile, clen)

def init():
    """Create a Tool for safe_multipart and add it to cherrypy.tools."""
    cherrypy.tools.safe_multipart = cherrypy.Tool('before_request_body',
                                                   safe_multipart)

