def set_receiver(self): """Once the headers have been read, decide how to read the body. We use the standard library's email package to parse the headers. The receiver is stored on self so it persists across calls to our own self.received, since it actually holds data between such calls. Later on we will add the request body to self.message. """ content_length = int(self.headers.get('Content-Length', 0)) transfer_encoding = self.headers.get('Transfer-Encoding', '').lower() if not content_length and not transfer_encoding: self._receiver = None elif content_length and not transfer_encoding: buffer_ = OverflowableBuffer(self.adj.inbuf_overflow) self._receiver = FixedStreamReceiver(content_length, buffer_) elif transfer_encoding == 'chunked': buffer_ = OverflowableBuffer(self.adj.inbuf_overflow) self._receiver = ChunkedReceiver(buffer_) else: self._receiver = None
def parse_header(self, header_plus): """ Parses the header_plus block of text (the headers plus the first line of the request). """ index = header_plus.find('\n') if index >= 0: first_line = header_plus[:index].rstrip() header = header_plus[index + 1:] else: first_line = header_plus.rstrip() header = '' self.first_line = first_line self.header = header lines = self.get_header_lines() headers = self.headers for line in lines: index = line.find(':') if index > 0: key = line[:index] value = line[index + 1:].strip() key1 = key.upper().replace('-', '_') # If a header already exists, we append subsequent values # seperated by a comma. Applications already need to handle # the comma seperated values, as HTTP front ends might do # the concatenation for you (behavior specified in RFC2616). try: headers[key1] += ', %s' % value except KeyError: headers[key1] = value # else there's garbage in the headers? command, uri, version = self.crack_first_line() self.command = str(command) self.uri = str(uri) self.version = version self.split_uri() if version == '1.1': te = headers.get('TRANSFER_ENCODING', '') if te == 'chunked': from httpy._zope.server.http.chunking import ChunkedReceiver self.chunked = 1 buf = OverflowableBuffer(self.adj.inbuf_overflow) self.body_rcv = ChunkedReceiver(buf) if not self.chunked: try: cl = int(headers.get('CONTENT_LENGTH', 0)) except ValueError: cl = 0 self.content_length = cl if cl > 0: buf = OverflowableBuffer(self.adj.inbuf_overflow) self.body_rcv = FixedStreamReceiver(cl, buf)
class ZopeRequest: """An implementation of IRequest that works with httpy._zope.server. """ implements(IStreamConsumer, IRequest) raw = '' raw_line = '' raw_headers = '' raw_body = '' method = '' uri = {} path = '' headers = Message() def __init__(self, adj=None): """Takes an Adjustments object. """ if adj is None: adj = default_adj self.adj = adj self._receiver = None self._tmp = '' self._raw = [] # initialize mutable defaults self.uri = {} self.headers = Message() # Fulfill our IStreamConsumer contracts. # ====================================== def received(self, block): """This takes a block of an incoming HTTP stream. We store anything leftover from the last block in raw string form in self._tmp. """ self._raw.append(block) try: if not self.raw_line: self.get_line(block) elif not self.raw_headers: self.get_headers(block) elif not self.raw_body: self.get_body(block) except Complete: self.raw = ''.join(self._raw) self.completed = True return len(block) completed = False empty = False # Request-Line # ============ def get_line(self, block): """ """ block = self._tmp + block if '\r' in block or '\n' in block: lines = block.splitlines(True) i = 0 for line in lines: if not line.strip(): #could be a blank line (like from IE) pass elif (line.endswith('\r') or line.endswith('\n')): #could be the whole thing self.raw_line = line.strip() self._tmp = ''.join(lines[i + 1:]) break else: #or else it is a partial line self._tmp = ''.join(lines[i:]) break i += 1 else: self._tmp = block if self.raw_line: self.parse_line() # Decide if we have any headers. line_ending = lines[0][len(self.raw_line):] if self._tmp == line_ending: #self._raw.append(self._tmp) -- why is this in here? raise Complete if self._tmp: self.get_headers('') def parse_line(self): """Parse and validate the Request-Line. """ # Tokenize the first line as a Request-Line. # ========================================== tokens = self.raw_line.strip().split() if len(tokens) == 3: method, uri, version = tokens elif len(tokens) == 2: method, uri = tokens version = 'HTTP/0.9' else: raise Response( 400, "The Request-Line `%s' " % self.raw_line + "appears to be malformed because it has " + "neither two nor three tokens.") # Validate the URI. # ================= # We build a mapping using the urlparse naming convention for the keys. # Then we do a little validation and cleanup. uri = urlparse.urlparse(uri) keys = ('scheme', 'netloc', 'path', 'parameters', 'query', 'fragment') _uri = {} for i in range(len(uri)): k = keys[i] v = uri[i] _uri[k] = v uri = _uri if not uri['path']: # this catches, e.g., '//foo' raise Response(400) if '%' in uri['path']: uri['path'] = urllib.unquote(uri['path']) # Validate the version. # ===================== # Consider raising Response(505) here ... for 0.9? 2.0? m = HTTP_VERSION.match(version) if not m: raise Response( 400, "The HTTP-Version `%s' appears to " % version + "be malformed because it does not match the " + "pattern `^HTTP/\d+\.\d+$'.") # Save a few absolutely basic things for application use. # ======================================================= self.method = method self.uri = uri self.path = uri['path'] # Headers # ======= def get_headers(self, block): """The tricky part here is dealing with different line delimiters. Technically MIME messages are \r\n delimited, but we can't assume that. We want to split on the first double line break we get, regardless of the control characters used. """ block = self._tmp + block found_splitter = False for double_line_break in ('\n\r\n', '\n\n'): if double_line_break in block: found_splitter = True self.raw_headers, self._tmp = block.split(double_line_break, 1) self.raw_headers = self.raw_headers.strip() break if not found_splitter: self._tmp = block self.headers = message_from_string(self.raw_headers) if self.raw_headers: self.get_body('') # Body # ==== def set_receiver(self): """Once the headers have been read, decide how to read the body. We use the standard library's email package to parse the headers. The receiver is stored on self so it persists across calls to our own self.received, since it actually holds data between such calls. Later on we will add the request body to self.message. """ content_length = int(self.headers.get('Content-Length', 0)) transfer_encoding = self.headers.get('Transfer-Encoding', '').lower() if not content_length and not transfer_encoding: self._receiver = None elif content_length and not transfer_encoding: buffer_ = OverflowableBuffer(self.adj.inbuf_overflow) self._receiver = FixedStreamReceiver(content_length, buffer_) elif transfer_encoding == 'chunked': buffer_ = OverflowableBuffer(self.adj.inbuf_overflow) self._receiver = ChunkedReceiver(buffer_) else: self._receiver = None def get_body(self, block): """Given a block from an HTTP stream, build a message body. We guarantee that the entire block is part of an HTTP request message. """ # Decide which receiver to use. # ============================= # If we don't have a receiver, it means there is no body. if self._receiver is None: self.set_receiver() if self._receiver is None: self.raw_body = '' raise Complete # Get all of the message body in one block. # ========================================= if self._tmp: block = self._tmp + block self._tmp = '' if not self._receiver.completed: self._receiver.received(block) if not self._receiver.completed: return # I'll be back! # Store on self and get out of here. # ================================== # We toss the body away if the requested method doesn't allow for it. I # read RFC 2616, Section 4.3 to call for this behavior rather than, # e.g., returning a 400. I suppose the concern is that the input stream # be properly positioned for multi-request connections. # # If anyone ever wants httpy to support extension-methods that take a # body we will either have to add them here or make allowances in # configuration. if self.method in ('POST', 'POST'): self.raw_body = self._receiver.getfile().read() else: self.raw_body = '' raise Complete