"""HTTP Messages This modules contains classes for representing HTTP responses and requests. """ import re import encodings import regexes as r import weblogging as logging reasondict = { 100 : 'Continue', 101 : 'Switching Protocols', 200 : 'OK', 201 : 'Created', 202 : 'Accepted', 203 : 'Non-Authoritative Information', 204 : 'No Content', 205 : 'Reset Content', 206 : 'Partial Content', 300 : 'Multiple Choices', 301 : 'Moved Permanently', 302 : 'Found', 303 : 'See Other', 304 : 'Not Modified', 305 : 'Use Proxy', 307 : 'Temporary Redirect', 400 : 'Bad Request', 401 : 'Unauthorized', 402 : 'Payment Required', 403 : 'Forbidden', 404 : 'Not Found', 405 : 'Method Not Allowed', 406 : 'Not Acceptable', 407 : 'Proxy Authentication Required', 408 : 'Request Time-out', 409 : 'Conflict', 410 : 'Gone', 411 : 'Length Required', 412 : 'Precondition Failed', 413 : 'Request Entity Too Large', 414 : 'Request-URI Too Large', 415 : 'Unsupported Media Type', 416 : 'Requested range not satisfiable', 417 : 'Expectation Failed', 500 : 'Internal Server Error', 501 : 'Not Implemented', 502 : 'Bad Gateway', 503 : 'Service Unavailable', 504 : 'Gateway Time-out', 505 : 'HTTP Version not supported', } class Message(object): """Class that stores an HTTP Message""" def __init__(self): """Initialize the Message""" self.version = "HTTP/1.1" self.body = "" self.headers = dict() def set_header(self, name, value): """Add a header and its value Args: name (str): name of header value (str): value of header """ self.headers[name] = str(value) def get_header(self, name): """Get the value of a header Args: name (str): name of header Returns: str: value of header, empty if header does not exist """ if name in self.headers: return self.headers[name] else: return None def parse_headers(self, msg): for name, value in re.findall(r.MessageHeader, msg): self.set_header(name, value.strip()) logging.debug("%s: %s" % (name, value.strip())) def startline(self): return '' def __str__(self): """Convert the Message to a string Returns: str: representation the can be sent over socket """ msg = '' msg += '%s\r\n' % self.startline() msg += '\r\n'.join([k + ": " + v for k, v in self.headers.iteritems()]) msg += '\r\n\r\n' + self.body return msg def __eq__(self, other): return self.__dict__ == other.__dict__ class Request(Message): """Class that stores an HTTP request""" def __init__(self): """Initialize the Request""" super(Request, self).__init__() self.method = "" self.uri = "" def parse(self, msg): [reqline, rest] = msg.split('\r\n', 1) reqline = re.match(r.RequestLine, reqline + '\r\n') self.method, self.uri, self.version = reqline.groups() [headers, body] = rest.split('\r\n\r\n', 1) self.parse_headers(headers) self.body = body def encodings(self): requested = self.get_header('Accept-Encoding') if requested == None: return encodings.all encs = [] requested = re.split(r.EncodingSplit, requested) for value in requested: try: match = re.match(r.AcceptEncodingValue, value) if match == None: return [encodings.IDENTITY] enc, q = match.groups() # Unclear what should happen when some qvalues are omitted q = q or '0.001' if enc == '*': for enc in encodings.all: encs.append((encodings.get(enc), float(q))) else: encs.append((encodings.get(enc), float(q))) except encodings.UnknownEncodingError: pass rejected = [e[0] for e in encs if e[1] == 0] accepted = [e for e in encs if e[1] != 0] if not encodings.IDENTITY in rejected + [a[0] for a in accepted]: accepted.append((encodings.IDENTITY, 0.001)) accepted.sort(key=lambda x:x[1], reverse=True) return [a[0] for a in accepted] def startline(self): return "%s %s %s" % (self.method, self.uri, self.version) def __str__(self): """Convert the Request to a string Returns: str: representation the can be sent over socket """ return super(Request, self).__str__() class Response(Message): """Class that stores an HTTP Response""" def __init__(self): """Initialize the Response""" super(Response, self).__init__() self.set_header('Server', 'WebPy') def parse(self, msg): [respline, rest] = msg.split('\r\n', 1) respline = re.match(r.StatusLine, respline + '\r\n') self.version = respline.group(1) self.code = int(respline.group(2)) [headers, body] = rest.split('\r\n\r\n', 1) self.parse_headers(headers) self.body = body def startline(self): return "%s %d %s" % (self.version, self.code, reasondict[self.code]) def set_content_length(self): self.set_header('Content-Length', len(self.body)) def decompress(self): self.body = encodings.decode( self.get_header('Content-Encoding'), self.body) def __str__(self): """Convert the Response to a string Returns: str: representation the can be sent over socket """ return super(Response, self).__str__()