diff options
Diffstat (limited to 'lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/web/http.py')
-rwxr-xr-x | lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/web/http.py | 1818 |
1 files changed, 0 insertions, 1818 deletions
diff --git a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/web/http.py b/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/web/http.py deleted file mode 100755 index 77d878b7..00000000 --- a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/web/http.py +++ /dev/null @@ -1,1818 +0,0 @@ -# -*- test-case-name: twisted.web.test.test_http -*- -# Copyright (c) Twisted Matrix Laboratories. -# See LICENSE for details. - -""" -HyperText Transfer Protocol implementation. - -This is the basic server-side protocol implementation used by the Twisted -Web server. It can parse HTTP 1.0 requests and supports many HTTP 1.1 -features as well. Additionally, some functionality implemented here is -also useful for HTTP clients (such as the chunked encoding parser). -""" - -# system imports -from cStringIO import StringIO -import tempfile -import base64, binascii -import cgi -import socket -import math -import time -import calendar -import warnings -import os -from urlparse import urlparse as _urlparse - -from zope.interface import implements - -# twisted imports -from twisted.internet import interfaces, reactor, protocol, address -from twisted.internet.defer import Deferred -from twisted.protocols import policies, basic -from twisted.python import log -from urllib import unquote - -from twisted.web.http_headers import _DictHeaders, Headers - -protocol_version = "HTTP/1.1" - -_CONTINUE = 100 -SWITCHING = 101 - -OK = 200 -CREATED = 201 -ACCEPTED = 202 -NON_AUTHORITATIVE_INFORMATION = 203 -NO_CONTENT = 204 -RESET_CONTENT = 205 -PARTIAL_CONTENT = 206 -MULTI_STATUS = 207 - -MULTIPLE_CHOICE = 300 -MOVED_PERMANENTLY = 301 -FOUND = 302 -SEE_OTHER = 303 -NOT_MODIFIED = 304 -USE_PROXY = 305 -TEMPORARY_REDIRECT = 307 - -BAD_REQUEST = 400 -UNAUTHORIZED = 401 -PAYMENT_REQUIRED = 402 -FORBIDDEN = 403 -NOT_FOUND = 404 -NOT_ALLOWED = 405 -NOT_ACCEPTABLE = 406 -PROXY_AUTH_REQUIRED = 407 -REQUEST_TIMEOUT = 408 -CONFLICT = 409 -GONE = 410 -LENGTH_REQUIRED = 411 -PRECONDITION_FAILED = 412 -REQUEST_ENTITY_TOO_LARGE = 413 -REQUEST_URI_TOO_LONG = 414 -UNSUPPORTED_MEDIA_TYPE = 415 -REQUESTED_RANGE_NOT_SATISFIABLE = 416 -EXPECTATION_FAILED = 417 - -INTERNAL_SERVER_ERROR = 500 -NOT_IMPLEMENTED = 501 -BAD_GATEWAY = 502 -SERVICE_UNAVAILABLE = 503 -GATEWAY_TIMEOUT = 504 -HTTP_VERSION_NOT_SUPPORTED = 505 -INSUFFICIENT_STORAGE_SPACE = 507 -NOT_EXTENDED = 510 - -RESPONSES = { - # 100 - _CONTINUE: "Continue", - SWITCHING: "Switching Protocols", - - # 200 - OK: "OK", - CREATED: "Created", - ACCEPTED: "Accepted", - NON_AUTHORITATIVE_INFORMATION: "Non-Authoritative Information", - NO_CONTENT: "No Content", - RESET_CONTENT: "Reset Content.", - PARTIAL_CONTENT: "Partial Content", - MULTI_STATUS: "Multi-Status", - - # 300 - MULTIPLE_CHOICE: "Multiple Choices", - MOVED_PERMANENTLY: "Moved Permanently", - FOUND: "Found", - SEE_OTHER: "See Other", - NOT_MODIFIED: "Not Modified", - USE_PROXY: "Use Proxy", - # 306 not defined?? - TEMPORARY_REDIRECT: "Temporary Redirect", - - # 400 - BAD_REQUEST: "Bad Request", - UNAUTHORIZED: "Unauthorized", - PAYMENT_REQUIRED: "Payment Required", - FORBIDDEN: "Forbidden", - NOT_FOUND: "Not Found", - NOT_ALLOWED: "Method Not Allowed", - NOT_ACCEPTABLE: "Not Acceptable", - PROXY_AUTH_REQUIRED: "Proxy Authentication Required", - REQUEST_TIMEOUT: "Request Time-out", - CONFLICT: "Conflict", - GONE: "Gone", - LENGTH_REQUIRED: "Length Required", - PRECONDITION_FAILED: "Precondition Failed", - REQUEST_ENTITY_TOO_LARGE: "Request Entity Too Large", - REQUEST_URI_TOO_LONG: "Request-URI Too Long", - UNSUPPORTED_MEDIA_TYPE: "Unsupported Media Type", - REQUESTED_RANGE_NOT_SATISFIABLE: "Requested Range not satisfiable", - EXPECTATION_FAILED: "Expectation Failed", - - # 500 - INTERNAL_SERVER_ERROR: "Internal Server Error", - NOT_IMPLEMENTED: "Not Implemented", - BAD_GATEWAY: "Bad Gateway", - SERVICE_UNAVAILABLE: "Service Unavailable", - GATEWAY_TIMEOUT: "Gateway Time-out", - HTTP_VERSION_NOT_SUPPORTED: "HTTP Version not supported", - INSUFFICIENT_STORAGE_SPACE: "Insufficient Storage Space", - NOT_EXTENDED: "Not Extended" - } - -CACHED = """Magic constant returned by http.Request methods to set cache -validation headers when the request is conditional and the value fails -the condition.""" - -# backwards compatability -responses = RESPONSES - - -# datetime parsing and formatting -weekdayname = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] -monthname = [None, - 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', - 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] -weekdayname_lower = [name.lower() for name in weekdayname] -monthname_lower = [name and name.lower() for name in monthname] - -def urlparse(url): - """ - Parse an URL into six components. - - This is similar to L{urlparse.urlparse}, but rejects C{unicode} input - and always produces C{str} output. - - @type url: C{str} - - @raise TypeError: The given url was a C{unicode} string instead of a - C{str}. - - @rtype: six-tuple of str - @return: The scheme, net location, path, params, query string, and fragment - of the URL. - """ - if isinstance(url, unicode): - raise TypeError("url must be str, not unicode") - scheme, netloc, path, params, query, fragment = _urlparse(url) - if isinstance(scheme, unicode): - scheme = scheme.encode('ascii') - netloc = netloc.encode('ascii') - path = path.encode('ascii') - query = query.encode('ascii') - fragment = fragment.encode('ascii') - return scheme, netloc, path, params, query, fragment - - -def parse_qs(qs, keep_blank_values=0, strict_parsing=0, unquote=unquote): - """ - like cgi.parse_qs, only with custom unquote function - """ - d = {} - items = [s2 for s1 in qs.split("&") for s2 in s1.split(";")] - for item in items: - try: - k, v = item.split("=", 1) - except ValueError: - if strict_parsing: - raise - continue - if v or keep_blank_values: - k = unquote(k.replace("+", " ")) - v = unquote(v.replace("+", " ")) - if k in d: - d[k].append(v) - else: - d[k] = [v] - return d - -def datetimeToString(msSinceEpoch=None): - """ - Convert seconds since epoch to HTTP datetime string. - """ - if msSinceEpoch == None: - msSinceEpoch = time.time() - year, month, day, hh, mm, ss, wd, y, z = time.gmtime(msSinceEpoch) - s = "%s, %02d %3s %4d %02d:%02d:%02d GMT" % ( - weekdayname[wd], - day, monthname[month], year, - hh, mm, ss) - return s - -def datetimeToLogString(msSinceEpoch=None): - """ - Convert seconds since epoch to log datetime string. - """ - if msSinceEpoch == None: - msSinceEpoch = time.time() - year, month, day, hh, mm, ss, wd, y, z = time.gmtime(msSinceEpoch) - s = "[%02d/%3s/%4d:%02d:%02d:%02d +0000]" % ( - day, monthname[month], year, - hh, mm, ss) - return s - -def timegm(year, month, day, hour, minute, second): - """ - Convert time tuple in GMT to seconds since epoch, GMT - """ - EPOCH = 1970 - if year < EPOCH: - raise ValueError("Years prior to %d not supported" % (EPOCH,)) - assert 1 <= month <= 12 - days = 365*(year-EPOCH) + calendar.leapdays(EPOCH, year) - for i in range(1, month): - days = days + calendar.mdays[i] - if month > 2 and calendar.isleap(year): - days = days + 1 - days = days + day - 1 - hours = days*24 + hour - minutes = hours*60 + minute - seconds = minutes*60 + second - return seconds - -def stringToDatetime(dateString): - """ - Convert an HTTP date string (one of three formats) to seconds since epoch. - """ - parts = dateString.split() - - if not parts[0][0:3].lower() in weekdayname_lower: - # Weekday is stupid. Might have been omitted. - try: - return stringToDatetime("Sun, "+dateString) - except ValueError: - # Guess not. - pass - - partlen = len(parts) - if (partlen == 5 or partlen == 6) and parts[1].isdigit(): - # 1st date format: Sun, 06 Nov 1994 08:49:37 GMT - # (Note: "GMT" is literal, not a variable timezone) - # (also handles without "GMT") - # This is the normal format - day = parts[1] - month = parts[2] - year = parts[3] - time = parts[4] - elif (partlen == 3 or partlen == 4) and parts[1].find('-') != -1: - # 2nd date format: Sunday, 06-Nov-94 08:49:37 GMT - # (Note: "GMT" is literal, not a variable timezone) - # (also handles without without "GMT") - # Two digit year, yucko. - day, month, year = parts[1].split('-') - time = parts[2] - year=int(year) - if year < 69: - year = year + 2000 - elif year < 100: - year = year + 1900 - elif len(parts) == 5: - # 3rd date format: Sun Nov 6 08:49:37 1994 - # ANSI C asctime() format. - day = parts[2] - month = parts[1] - year = parts[4] - time = parts[3] - else: - raise ValueError("Unknown datetime format %r" % dateString) - - day = int(day) - month = int(monthname_lower.index(month.lower())) - year = int(year) - hour, min, sec = map(int, time.split(':')) - return int(timegm(year, month, day, hour, min, sec)) - -def toChunk(data): - """ - Convert string to a chunk. - - @returns: a tuple of strings representing the chunked encoding of data - """ - return ("%x\r\n" % len(data), data, "\r\n") - -def fromChunk(data): - """ - Convert chunk to string. - - @returns: tuple (result, remaining), may raise ValueError. - """ - prefix, rest = data.split('\r\n', 1) - length = int(prefix, 16) - if length < 0: - raise ValueError("Chunk length must be >= 0, not %d" % (length,)) - if not rest[length:length + 2] == '\r\n': - raise ValueError, "chunk must end with CRLF" - return rest[:length], rest[length + 2:] - - -def parseContentRange(header): - """ - Parse a content-range header into (start, end, realLength). - - realLength might be None if real length is not known ('*'). - """ - kind, other = header.strip().split() - if kind.lower() != "bytes": - raise ValueError, "a range of type %r is not supported" - startend, realLength = other.split("/") - start, end = map(int, startend.split("-")) - if realLength == "*": - realLength = None - else: - realLength = int(realLength) - return (start, end, realLength) - - - -class StringTransport: - """ - I am a StringIO wrapper that conforms for the transport API. I support - the `writeSequence' method. - """ - def __init__(self): - self.s = StringIO() - def writeSequence(self, seq): - self.s.write(''.join(seq)) - def __getattr__(self, attr): - return getattr(self.__dict__['s'], attr) - - -class HTTPClient(basic.LineReceiver): - """ - A client for HTTP 1.0. - - Notes: - You probably want to send a 'Host' header with the name of the site you're - connecting to, in order to not break name based virtual hosting. - - @ivar length: The length of the request body in bytes. - @type length: C{int} - - @ivar firstLine: Are we waiting for the first header line? - @type firstLine: C{bool} - - @ivar __buffer: The buffer that stores the response to the HTTP request. - @type __buffer: A C{StringIO} object. - - @ivar _header: Part or all of an HTTP request header. - @type _header: C{str} - """ - length = None - firstLine = True - __buffer = None - _header = "" - - def sendCommand(self, command, path): - self.transport.write('%s %s HTTP/1.0\r\n' % (command, path)) - - def sendHeader(self, name, value): - self.transport.write('%s: %s\r\n' % (name, value)) - - def endHeaders(self): - self.transport.write('\r\n') - - - def extractHeader(self, header): - """ - Given a complete HTTP header, extract the field name and value and - process the header. - - @param header: a complete HTTP request header of the form - 'field-name: value'. - @type header: C{str} - """ - key, val = header.split(':', 1) - val = val.lstrip() - self.handleHeader(key, val) - if key.lower() == 'content-length': - self.length = int(val) - - - def lineReceived(self, line): - """ - Parse the status line and headers for an HTTP request. - - @param line: Part of an HTTP request header. Request bodies are parsed - in L{rawDataReceived}. - @type line: C{str} - """ - if self.firstLine: - self.firstLine = False - l = line.split(None, 2) - version = l[0] - status = l[1] - try: - message = l[2] - except IndexError: - # sometimes there is no message - message = "" - self.handleStatus(version, status, message) - return - if not line: - if self._header != "": - # Only extract headers if there are any - self.extractHeader(self._header) - self.__buffer = StringIO() - self.handleEndHeaders() - self.setRawMode() - return - - if line.startswith('\t') or line.startswith(' '): - # This line is part of a multiline header. According to RFC 822, in - # "unfolding" multiline headers you do not strip the leading - # whitespace on the continuing line. - self._header = self._header + line - elif self._header: - # This line starts a new header, so process the previous one. - self.extractHeader(self._header) - self._header = line - else: # First header - self._header = line - - - def connectionLost(self, reason): - self.handleResponseEnd() - - def handleResponseEnd(self): - """ - The response has been completely received. - - This callback may be invoked more than once per request. - """ - if self.__buffer is not None: - b = self.__buffer.getvalue() - self.__buffer = None - self.handleResponse(b) - - def handleResponsePart(self, data): - self.__buffer.write(data) - - def connectionMade(self): - pass - - def handleStatus(self, version, status, message): - """ - Called when the status-line is received. - - @param version: e.g. 'HTTP/1.0' - @param status: e.g. '200' - @type status: C{str} - @param message: e.g. 'OK' - """ - - def handleHeader(self, key, val): - """ - Called every time a header is received. - """ - - def handleEndHeaders(self): - """ - Called when all headers have been received. - """ - - - def rawDataReceived(self, data): - if self.length is not None: - data, rest = data[:self.length], data[self.length:] - self.length -= len(data) - else: - rest = '' - self.handleResponsePart(data) - if self.length == 0: - self.handleResponseEnd() - self.setLineMode(rest) - - - -# response codes that must have empty bodies -NO_BODY_CODES = (204, 304) - -class Request: - """ - A HTTP request. - - Subclasses should override the process() method to determine how - the request will be processed. - - @ivar method: The HTTP method that was used. - @ivar uri: The full URI that was requested (includes arguments). - @ivar path: The path only (arguments not included). - @ivar args: All of the arguments, including URL and POST arguments. - @type args: A mapping of strings (the argument names) to lists of values. - i.e., ?foo=bar&foo=baz&quux=spam results in - {'foo': ['bar', 'baz'], 'quux': ['spam']}. - - @type requestHeaders: L{http_headers.Headers} - @ivar requestHeaders: All received HTTP request headers. - - @ivar received_headers: Backwards-compatibility access to - C{requestHeaders}. Use C{requestHeaders} instead. C{received_headers} - behaves mostly like a C{dict} and does not provide access to all header - values. - - @type responseHeaders: L{http_headers.Headers} - @ivar responseHeaders: All HTTP response headers to be sent. - - @ivar headers: Backwards-compatibility access to C{responseHeaders}. Use - C{responseHeaders} instead. C{headers} behaves mostly like a C{dict} - and does not provide access to all header values nor does it allow - multiple values for one header to be set. - - @ivar notifications: A C{list} of L{Deferred}s which are waiting for - notification that the response to this request has been finished - (successfully or with an error). Don't use this attribute directly, - instead use the L{Request.notifyFinish} method. - - @ivar _disconnected: A flag which is C{False} until the connection over - which this request was received is closed and which is C{True} after - that. - @type _disconnected: C{bool} - """ - implements(interfaces.IConsumer) - - producer = None - finished = 0 - code = OK - code_message = RESPONSES[OK] - method = "(no method yet)" - clientproto = "(no clientproto yet)" - uri = "(no uri yet)" - startedWriting = 0 - chunked = 0 - sentLength = 0 # content-length of response, or total bytes sent via chunking - etag = None - lastModified = None - args = None - path = None - content = None - _forceSSL = 0 - _disconnected = False - - def __init__(self, channel, queued): - """ - @param channel: the channel we're connected to. - @param queued: are we in the request queue, or can we start writing to - the transport? - """ - self.notifications = [] - self.channel = channel - self.queued = queued - self.requestHeaders = Headers() - self.received_cookies = {} - self.responseHeaders = Headers() - self.cookies = [] # outgoing cookies - - if queued: - self.transport = StringTransport() - else: - self.transport = self.channel.transport - - - def __setattr__(self, name, value): - """ - Support assignment of C{dict} instances to C{received_headers} for - backwards-compatibility. - """ - if name == 'received_headers': - # A property would be nice, but Request is classic. - self.requestHeaders = headers = Headers() - for k, v in value.iteritems(): - headers.setRawHeaders(k, [v]) - elif name == 'requestHeaders': - self.__dict__[name] = value - self.__dict__['received_headers'] = _DictHeaders(value) - elif name == 'headers': - self.responseHeaders = headers = Headers() - for k, v in value.iteritems(): - headers.setRawHeaders(k, [v]) - elif name == 'responseHeaders': - self.__dict__[name] = value - self.__dict__['headers'] = _DictHeaders(value) - else: - self.__dict__[name] = value - - - def _cleanup(self): - """ - Called when have finished responding and are no longer queued. - """ - if self.producer: - log.err(RuntimeError("Producer was not unregistered for %s" % self.uri)) - self.unregisterProducer() - self.channel.requestDone(self) - del self.channel - try: - self.content.close() - except OSError: - # win32 suckiness, no idea why it does this - pass - del self.content - for d in self.notifications: - d.callback(None) - self.notifications = [] - - # methods for channel - end users should not use these - - def noLongerQueued(self): - """ - Notify the object that it is no longer queued. - - We start writing whatever data we have to the transport, etc. - - This method is not intended for users. - """ - if not self.queued: - raise RuntimeError, "noLongerQueued() got called unnecessarily." - - self.queued = 0 - - # set transport to real one and send any buffer data - data = self.transport.getvalue() - self.transport = self.channel.transport - if data: - self.transport.write(data) - - # if we have producer, register it with transport - if (self.producer is not None) and not self.finished: - self.transport.registerProducer(self.producer, self.streamingProducer) - - # if we're finished, clean up - if self.finished: - self._cleanup() - - def gotLength(self, length): - """ - Called when HTTP channel got length of content in this request. - - This method is not intended for users. - - @param length: The length of the request body, as indicated by the - request headers. C{None} if the request headers do not indicate a - length. - """ - if length is not None and length < 100000: - self.content = StringIO() - else: - self.content = tempfile.TemporaryFile() - - - def parseCookies(self): - """ - Parse cookie headers. - - This method is not intended for users. - """ - cookieheaders = self.requestHeaders.getRawHeaders("cookie") - - if cookieheaders is None: - return - - for cookietxt in cookieheaders: - if cookietxt: - for cook in cookietxt.split(';'): - cook = cook.lstrip() - try: - k, v = cook.split('=', 1) - self.received_cookies[k] = v - except ValueError: - pass - - - def handleContentChunk(self, data): - """ - Write a chunk of data. - - This method is not intended for users. - """ - self.content.write(data) - - - def requestReceived(self, command, path, version): - """ - Called by channel when all data has been received. - - This method is not intended for users. - - @type command: C{str} - @param command: The HTTP verb of this request. This has the case - supplied by the client (eg, it maybe "get" rather than "GET"). - - @type path: C{str} - @param path: The URI of this request. - - @type version: C{str} - @param version: The HTTP version of this request. - """ - self.content.seek(0,0) - self.args = {} - self.stack = [] - - self.method, self.uri = command, path - self.clientproto = version - x = self.uri.split('?', 1) - - if len(x) == 1: - self.path = self.uri - else: - self.path, argstring = x - self.args = parse_qs(argstring, 1) - - # cache the client and server information, we'll need this later to be - # serialized and sent with the request so CGIs will work remotely - self.client = self.channel.transport.getPeer() - self.host = self.channel.transport.getHost() - - # Argument processing - args = self.args - ctype = self.requestHeaders.getRawHeaders('content-type') - if ctype is not None: - ctype = ctype[0] - - if self.method == "POST" and ctype: - mfd = 'multipart/form-data' - key, pdict = cgi.parse_header(ctype) - if key == 'application/x-www-form-urlencoded': - args.update(parse_qs(self.content.read(), 1)) - elif key == mfd: - try: - args.update(cgi.parse_multipart(self.content, pdict)) - except KeyError, e: - if e.args[0] == 'content-disposition': - # Parse_multipart can't cope with missing - # content-dispostion headers in multipart/form-data - # parts, so we catch the exception and tell the client - # it was a bad request. - self.channel.transport.write( - "HTTP/1.1 400 Bad Request\r\n\r\n") - self.channel.transport.loseConnection() - return - raise - self.content.seek(0, 0) - - self.process() - - - def __repr__(self): - return '<%s %s %s>'% (self.method, self.uri, self.clientproto) - - def process(self): - """ - Override in subclasses. - - This method is not intended for users. - """ - pass - - - # consumer interface - - def registerProducer(self, producer, streaming): - """ - Register a producer. - """ - if self.producer: - raise ValueError, "registering producer %s before previous one (%s) was unregistered" % (producer, self.producer) - - self.streamingProducer = streaming - self.producer = producer - - if self.queued: - if streaming: - producer.pauseProducing() - else: - self.transport.registerProducer(producer, streaming) - - def unregisterProducer(self): - """ - Unregister the producer. - """ - if not self.queued: - self.transport.unregisterProducer() - self.producer = None - - # private http response methods - - def _sendError(self, code, resp=''): - self.transport.write('%s %s %s\r\n\r\n' % (self.clientproto, code, resp)) - - - # The following is the public interface that people should be - # writing to. - def getHeader(self, key): - """ - Get an HTTP request header. - - @type key: C{str} - @param key: The name of the header to get the value of. - - @rtype: C{str} or C{NoneType} - @return: The value of the specified header, or C{None} if that header - was not present in the request. - """ - value = self.requestHeaders.getRawHeaders(key) - if value is not None: - return value[-1] - - - def getCookie(self, key): - """ - Get a cookie that was sent from the network. - """ - return self.received_cookies.get(key) - - - def notifyFinish(self): - """ - Notify when the response to this request has finished. - - @rtype: L{Deferred} - - @return: A L{Deferred} which will be triggered when the request is - finished -- with a C{None} value if the request finishes - successfully or with an error if the request is interrupted by an - error (for example, the client closing the connection prematurely). - """ - self.notifications.append(Deferred()) - return self.notifications[-1] - - - def finish(self): - """ - Indicate that all response data has been written to this L{Request}. - """ - if self._disconnected: - raise RuntimeError( - "Request.finish called on a request after its connection was lost; " - "use Request.notifyFinish to keep track of this.") - if self.finished: - warnings.warn("Warning! request.finish called twice.", stacklevel=2) - return - - if not self.startedWriting: - # write headers - self.write('') - - if self.chunked: - # write last chunk and closing CRLF - self.transport.write("0\r\n\r\n") - - # log request - if hasattr(self.channel, "factory"): - self.channel.factory.log(self) - - self.finished = 1 - if not self.queued: - self._cleanup() - - - def write(self, data): - """ - Write some data as a result of an HTTP request. The first - time this is called, it writes out response data. - - @type data: C{str} - @param data: Some bytes to be sent as part of the response body. - """ - if self.finished: - raise RuntimeError('Request.write called on a request after ' - 'Request.finish was called.') - if not self.startedWriting: - self.startedWriting = 1 - version = self.clientproto - l = [] - l.append('%s %s %s\r\n' % (version, self.code, - self.code_message)) - # if we don't have a content length, we send data in - # chunked mode, so that we can support pipelining in - # persistent connections. - if ((version == "HTTP/1.1") and - (self.responseHeaders.getRawHeaders('content-length') is None) and - self.method != "HEAD" and self.code not in NO_BODY_CODES): - l.append("%s: %s\r\n" % ('Transfer-Encoding', 'chunked')) - self.chunked = 1 - - if self.lastModified is not None: - if self.responseHeaders.hasHeader('last-modified'): - log.msg("Warning: last-modified specified both in" - " header list and lastModified attribute.") - else: - self.responseHeaders.setRawHeaders( - 'last-modified', - [datetimeToString(self.lastModified)]) - - if self.etag is not None: - self.responseHeaders.setRawHeaders('ETag', [self.etag]) - - for name, values in self.responseHeaders.getAllRawHeaders(): - for value in values: - l.append("%s: %s\r\n" % (name, value)) - - for cookie in self.cookies: - l.append('%s: %s\r\n' % ("Set-Cookie", cookie)) - - l.append("\r\n") - - self.transport.writeSequence(l) - - # if this is a "HEAD" request, we shouldn't return any data - if self.method == "HEAD": - self.write = lambda data: None - return - - # for certain result codes, we should never return any data - if self.code in NO_BODY_CODES: - self.write = lambda data: None - return - - self.sentLength = self.sentLength + len(data) - if data: - if self.chunked: - self.transport.writeSequence(toChunk(data)) - else: - self.transport.write(data) - - def addCookie(self, k, v, expires=None, domain=None, path=None, max_age=None, comment=None, secure=None): - """ - Set an outgoing HTTP cookie. - - In general, you should consider using sessions instead of cookies, see - L{twisted.web.server.Request.getSession} and the - L{twisted.web.server.Session} class for details. - """ - cookie = '%s=%s' % (k, v) - if expires is not None: - cookie = cookie +"; Expires=%s" % expires - if domain is not None: - cookie = cookie +"; Domain=%s" % domain - if path is not None: - cookie = cookie +"; Path=%s" % path - if max_age is not None: - cookie = cookie +"; Max-Age=%s" % max_age - if comment is not None: - cookie = cookie +"; Comment=%s" % comment - if secure: - cookie = cookie +"; Secure" - self.cookies.append(cookie) - - def setResponseCode(self, code, message=None): - """ - Set the HTTP response code. - """ - if not isinstance(code, (int, long)): - raise TypeError("HTTP response code must be int or long") - self.code = code - if message: - self.code_message = message - else: - self.code_message = RESPONSES.get(code, "Unknown Status") - - - def setHeader(self, name, value): - """ - Set an HTTP response header. Overrides any previously set values for - this header. - - @type name: C{str} - @param name: The name of the header for which to set the value. - - @type value: C{str} - @param value: The value to set for the named header. - """ - self.responseHeaders.setRawHeaders(name, [value]) - - - def redirect(self, url): - """ - Utility function that does a redirect. - - The request should have finish() called after this. - """ - self.setResponseCode(FOUND) - self.setHeader("location", url) - - - def setLastModified(self, when): - """ - Set the C{Last-Modified} time for the response to this request. - - If I am called more than once, I ignore attempts to set - Last-Modified earlier, only replacing the Last-Modified time - if it is to a later value. - - If I am a conditional request, I may modify my response code - to L{NOT_MODIFIED} if appropriate for the time given. - - @param when: The last time the resource being returned was - modified, in seconds since the epoch. - @type when: number - @return: If I am a C{If-Modified-Since} conditional request and - the time given is not newer than the condition, I return - L{http.CACHED<CACHED>} to indicate that you should write no - body. Otherwise, I return a false value. - """ - # time.time() may be a float, but the HTTP-date strings are - # only good for whole seconds. - when = long(math.ceil(when)) - if (not self.lastModified) or (self.lastModified < when): - self.lastModified = when - - modifiedSince = self.getHeader('if-modified-since') - if modifiedSince: - firstPart = modifiedSince.split(';', 1)[0] - try: - modifiedSince = stringToDatetime(firstPart) - except ValueError: - return None - if modifiedSince >= when: - self.setResponseCode(NOT_MODIFIED) - return CACHED - return None - - def setETag(self, etag): - """ - Set an C{entity tag} for the outgoing response. - - That's \"entity tag\" as in the HTTP/1.1 C{ETag} header, \"used - for comparing two or more entities from the same requested - resource.\" - - If I am a conditional request, I may modify my response code - to L{NOT_MODIFIED} or L{PRECONDITION_FAILED}, if appropriate - for the tag given. - - @param etag: The entity tag for the resource being returned. - @type etag: string - @return: If I am a C{If-None-Match} conditional request and - the tag matches one in the request, I return - L{http.CACHED<CACHED>} to indicate that you should write - no body. Otherwise, I return a false value. - """ - if etag: - self.etag = etag - - tags = self.getHeader("if-none-match") - if tags: - tags = tags.split() - if (etag in tags) or ('*' in tags): - self.setResponseCode(((self.method in ("HEAD", "GET")) - and NOT_MODIFIED) - or PRECONDITION_FAILED) - return CACHED - return None - - - def getAllHeaders(self): - """ - Return dictionary mapping the names of all received headers to the last - value received for each. - - Since this method does not return all header information, - C{self.requestHeaders.getAllRawHeaders()} may be preferred. - """ - headers = {} - for k, v in self.requestHeaders.getAllRawHeaders(): - headers[k.lower()] = v[-1] - return headers - - - def getRequestHostname(self): - """ - Get the hostname that the user passed in to the request. - - This will either use the Host: header (if it is available) or the - host we are listening on if the header is unavailable. - - @returns: the requested hostname - @rtype: C{str} - """ - # XXX This method probably has no unit tests. I changed it a ton and - # nothing failed. - host = self.getHeader('host') - if host: - return host.split(':', 1)[0] - return self.getHost().host - - - def getHost(self): - """ - Get my originally requesting transport's host. - - Don't rely on the 'transport' attribute, since Request objects may be - copied remotely. For information on this method's return value, see - twisted.internet.tcp.Port. - """ - return self.host - - def setHost(self, host, port, ssl=0): - """ - Change the host and port the request thinks it's using. - - This method is useful for working with reverse HTTP proxies (e.g. - both Squid and Apache's mod_proxy can do this), when the address - the HTTP client is using is different than the one we're listening on. - - For example, Apache may be listening on https://www.example.com, and then - forwarding requests to http://localhost:8080, but we don't want HTML produced - by Twisted to say 'http://localhost:8080', they should say 'https://www.example.com', - so we do:: - - request.setHost('www.example.com', 443, ssl=1) - - @type host: C{str} - @param host: The value to which to change the host header. - - @type ssl: C{bool} - @param ssl: A flag which, if C{True}, indicates that the request is - considered secure (if C{True}, L{isSecure} will return C{True}). - """ - self._forceSSL = ssl # set first so isSecure will work - if self.isSecure(): - default = 443 - else: - default = 80 - if port == default: - hostHeader = host - else: - hostHeader = '%s:%d' % (host, port) - self.requestHeaders.setRawHeaders("host", [hostHeader]) - self.host = address.IPv4Address("TCP", host, port) - - - def getClientIP(self): - """ - Return the IP address of the client who submitted this request. - - @returns: the client IP address - @rtype: C{str} - """ - if isinstance(self.client, address.IPv4Address): - return self.client.host - else: - return None - - def isSecure(self): - """ - Return True if this request is using a secure transport. - - Normally this method returns True if this request's HTTPChannel - instance is using a transport that implements ISSLTransport. - - This will also return True if setHost() has been called - with ssl=True. - - @returns: True if this request is secure - @rtype: C{bool} - """ - if self._forceSSL: - return True - transport = getattr(getattr(self, 'channel', None), 'transport', None) - if interfaces.ISSLTransport(transport, None) is not None: - return True - return False - - def _authorize(self): - # Authorization, (mostly) per the RFC - try: - authh = self.getHeader("Authorization") - if not authh: - self.user = self.password = '' - return - bas, upw = authh.split() - if bas.lower() != "basic": - raise ValueError - upw = base64.decodestring(upw) - self.user, self.password = upw.split(':', 1) - except (binascii.Error, ValueError): - self.user = self.password = "" - except: - log.err() - self.user = self.password = "" - - def getUser(self): - """ - Return the HTTP user sent with this request, if any. - - If no user was supplied, return the empty string. - - @returns: the HTTP user, if any - @rtype: C{str} - """ - try: - return self.user - except: - pass - self._authorize() - return self.user - - def getPassword(self): - """ - Return the HTTP password sent with this request, if any. - - If no password was supplied, return the empty string. - - @returns: the HTTP password, if any - @rtype: C{str} - """ - try: - return self.password - except: - pass - self._authorize() - return self.password - - def getClient(self): - if self.client.type != 'TCP': - return None - host = self.client.host - try: - name, names, addresses = socket.gethostbyaddr(host) - except socket.error: - return host - names.insert(0, name) - for name in names: - if '.' in name: - return name - return names[0] - - - def connectionLost(self, reason): - """ - There is no longer a connection for this request to respond over. - Clean up anything which can't be useful anymore. - """ - self._disconnected = True - self.channel = None - if self.content is not None: - self.content.close() - for d in self.notifications: - d.errback(reason) - self.notifications = [] - - - -class _DataLoss(Exception): - """ - L{_DataLoss} indicates that not all of a message body was received. This - is only one of several possible exceptions which may indicate that data - was lost. Because of this, it should not be checked for by - specifically; any unexpected exception should be treated as having - caused data loss. - """ - - - -class PotentialDataLoss(Exception): - """ - L{PotentialDataLoss} may be raised by a transfer encoding decoder's - C{noMoreData} method to indicate that it cannot be determined if the - entire response body has been delivered. This only occurs when making - requests to HTTP servers which do not set I{Content-Length} or a - I{Transfer-Encoding} in the response because in this case the end of the - response is indicated by the connection being closed, an event which may - also be due to a transient network problem or other error. - """ - - - -class _IdentityTransferDecoder(object): - """ - Protocol for accumulating bytes up to a specified length. This handles the - case where no I{Transfer-Encoding} is specified. - - @ivar contentLength: Counter keeping track of how many more bytes there are - to receive. - - @ivar dataCallback: A one-argument callable which will be invoked each - time application data is received. - - @ivar finishCallback: A one-argument callable which will be invoked when - the terminal chunk is received. It will be invoked with all bytes - which were delivered to this protocol which came after the terminal - chunk. - """ - def __init__(self, contentLength, dataCallback, finishCallback): - self.contentLength = contentLength - self.dataCallback = dataCallback - self.finishCallback = finishCallback - - - def dataReceived(self, data): - """ - Interpret the next chunk of bytes received. Either deliver them to the - data callback or invoke the finish callback if enough bytes have been - received. - - @raise RuntimeError: If the finish callback has already been invoked - during a previous call to this methood. - """ - if self.dataCallback is None: - raise RuntimeError( - "_IdentityTransferDecoder cannot decode data after finishing") - - if self.contentLength is None: - self.dataCallback(data) - elif len(data) < self.contentLength: - self.contentLength -= len(data) - self.dataCallback(data) - else: - # Make the state consistent before invoking any code belonging to - # anyone else in case noMoreData ends up being called beneath this - # stack frame. - contentLength = self.contentLength - dataCallback = self.dataCallback - finishCallback = self.finishCallback - self.dataCallback = self.finishCallback = None - self.contentLength = 0 - - dataCallback(data[:contentLength]) - finishCallback(data[contentLength:]) - - - def noMoreData(self): - """ - All data which will be delivered to this decoder has been. Check to - make sure as much data as was expected has been received. - - @raise PotentialDataLoss: If the content length is unknown. - @raise _DataLoss: If the content length is known and fewer than that - many bytes have been delivered. - - @return: C{None} - """ - finishCallback = self.finishCallback - self.dataCallback = self.finishCallback = None - if self.contentLength is None: - finishCallback('') - raise PotentialDataLoss() - elif self.contentLength != 0: - raise _DataLoss() - - - -class _ChunkedTransferDecoder(object): - """ - Protocol for decoding I{chunked} Transfer-Encoding, as defined by RFC 2616, - section 3.6.1. This protocol can interpret the contents of a request or - response body which uses the I{chunked} Transfer-Encoding. It cannot - interpret any of the rest of the HTTP protocol. - - It may make sense for _ChunkedTransferDecoder to be an actual IProtocol - implementation. Currently, the only user of this class will only ever - call dataReceived on it. However, it might be an improvement if the - user could connect this to a transport and deliver connection lost - notification. This way, `dataCallback` becomes `self.transport.write` - and perhaps `finishCallback` becomes `self.transport.loseConnection()` - (although I'm not sure where the extra data goes in that case). This - could also allow this object to indicate to the receiver of data that - the stream was not completely received, an error case which should be - noticed. -exarkun - - @ivar dataCallback: A one-argument callable which will be invoked each - time application data is received. - - @ivar finishCallback: A one-argument callable which will be invoked when - the terminal chunk is received. It will be invoked with all bytes - which were delivered to this protocol which came after the terminal - chunk. - - @ivar length: Counter keeping track of how many more bytes in a chunk there - are to receive. - - @ivar state: One of C{'CHUNK_LENGTH'}, C{'CRLF'}, C{'TRAILER'}, - C{'BODY'}, or C{'FINISHED'}. For C{'CHUNK_LENGTH'}, data for the - chunk length line is currently being read. For C{'CRLF'}, the CR LF - pair which follows each chunk is being read. For C{'TRAILER'}, the CR - LF pair which follows the terminal 0-length chunk is currently being - read. For C{'BODY'}, the contents of a chunk are being read. For - C{'FINISHED'}, the last chunk has been completely read and no more - input is valid. - """ - state = 'CHUNK_LENGTH' - - def __init__(self, dataCallback, finishCallback): - self.dataCallback = dataCallback - self.finishCallback = finishCallback - self._buffer = '' - - def _dataReceived_CHUNK_LENGTH(self, data): - if '\r\n' in data: - line, rest = data.split('\r\n', 1) - parts = line.split(';') - self.length = int(parts[0], 16) - if self.length == 0: - self.state = 'TRAILER' - else: - self.state = 'BODY' - return rest - else: - self._buffer = data - return '' - - def _dataReceived_CRLF(self, data): - if data.startswith('\r\n'): - self.state = 'CHUNK_LENGTH' - return data[2:] - else: - self._buffer = data - return '' - - def _dataReceived_TRAILER(self, data): - if data.startswith('\r\n'): - data = data[2:] - self.state = 'FINISHED' - self.finishCallback(data) - else: - self._buffer = data - return '' - - def _dataReceived_BODY(self, data): - if len(data) >= self.length: - chunk, data = data[:self.length], data[self.length:] - self.dataCallback(chunk) - self.state = 'CRLF' - return data - elif len(data) < self.length: - self.length -= len(data) - self.dataCallback(data) - return '' - - def _dataReceived_FINISHED(self, data): - raise RuntimeError( - "_ChunkedTransferDecoder.dataReceived called after last " - "chunk was processed") - - - def dataReceived(self, data): - """ - Interpret data from a request or response body which uses the - I{chunked} Transfer-Encoding. - """ - data = self._buffer + data - self._buffer = '' - while data: - data = getattr(self, '_dataReceived_%s' % (self.state,))(data) - - - def noMoreData(self): - """ - Verify that all data has been received. If it has not been, raise - L{_DataLoss}. - """ - if self.state != 'FINISHED': - raise _DataLoss( - "Chunked decoder in %r state, still expecting more data to " - "get to 'FINISHED' state." % (self.state,)) - - - -class HTTPChannel(basic.LineReceiver, policies.TimeoutMixin): - """ - A receiver for HTTP requests. - - @ivar _transferDecoder: C{None} or an instance of - L{_ChunkedTransferDecoder} if the request body uses the I{chunked} - Transfer-Encoding. - """ - - maxHeaders = 500 # max number of headers allowed per request - - length = 0 - persistent = 1 - __header = '' - __first_line = 1 - __content = None - - # set in instances or subclasses - requestFactory = Request - - _savedTimeOut = None - _receivedHeaderCount = 0 - - def __init__(self): - # the request queue - self.requests = [] - self._transferDecoder = None - - - def connectionMade(self): - self.setTimeout(self.timeOut) - - def lineReceived(self, line): - self.resetTimeout() - - if self.__first_line: - # if this connection is not persistent, drop any data which - # the client (illegally) sent after the last request. - if not self.persistent: - self.dataReceived = self.lineReceived = lambda *args: None - return - - # IE sends an extraneous empty line (\r\n) after a POST request; - # eat up such a line, but only ONCE - if not line and self.__first_line == 1: - self.__first_line = 2 - return - - # create a new Request object - request = self.requestFactory(self, len(self.requests)) - self.requests.append(request) - - self.__first_line = 0 - parts = line.split() - if len(parts) != 3: - self.transport.write("HTTP/1.1 400 Bad Request\r\n\r\n") - self.transport.loseConnection() - return - command, request, version = parts - self._command = command - self._path = request - self._version = version - elif line == '': - if self.__header: - self.headerReceived(self.__header) - self.__header = '' - self.allHeadersReceived() - if self.length == 0: - self.allContentReceived() - else: - self.setRawMode() - elif line[0] in ' \t': - self.__header = self.__header+'\n'+line - else: - if self.__header: - self.headerReceived(self.__header) - self.__header = line - - - def _finishRequestBody(self, data): - self.allContentReceived() - self.setLineMode(data) - - - def headerReceived(self, line): - """ - Do pre-processing (for content-length) and store this header away. - Enforce the per-request header limit. - - @type line: C{str} - @param line: A line from the header section of a request, excluding the - line delimiter. - """ - header, data = line.split(':', 1) - header = header.lower() - data = data.strip() - if header == 'content-length': - self.length = int(data) - self._transferDecoder = _IdentityTransferDecoder( - self.length, self.requests[-1].handleContentChunk, self._finishRequestBody) - elif header == 'transfer-encoding' and data.lower() == 'chunked': - self.length = None - self._transferDecoder = _ChunkedTransferDecoder( - self.requests[-1].handleContentChunk, self._finishRequestBody) - reqHeaders = self.requests[-1].requestHeaders - values = reqHeaders.getRawHeaders(header) - if values is not None: - values.append(data) - else: - reqHeaders.setRawHeaders(header, [data]) - - self._receivedHeaderCount += 1 - if self._receivedHeaderCount > self.maxHeaders: - self.transport.write("HTTP/1.1 400 Bad Request\r\n\r\n") - self.transport.loseConnection() - - - def allContentReceived(self): - command = self._command - path = self._path - version = self._version - - # reset ALL state variables, so we don't interfere with next request - self.length = 0 - self._receivedHeaderCount = 0 - self.__first_line = 1 - self._transferDecoder = None - del self._command, self._path, self._version - - # Disable the idle timeout, in case this request takes a long - # time to finish generating output. - if self.timeOut: - self._savedTimeOut = self.setTimeout(None) - - req = self.requests[-1] - req.requestReceived(command, path, version) - - def rawDataReceived(self, data): - self.resetTimeout() - self._transferDecoder.dataReceived(data) - - - def allHeadersReceived(self): - req = self.requests[-1] - req.parseCookies() - self.persistent = self.checkPersistence(req, self._version) - req.gotLength(self.length) - # Handle 'Expect: 100-continue' with automated 100 response code, - # a simplistic implementation of RFC 2686 8.2.3: - expectContinue = req.requestHeaders.getRawHeaders('expect') - if (expectContinue and expectContinue[0].lower() == '100-continue' and - self._version == 'HTTP/1.1'): - req.transport.write("HTTP/1.1 100 Continue\r\n\r\n") - - - def checkPersistence(self, request, version): - """ - Check if the channel should close or not. - - @param request: The request most recently received over this channel - against which checks will be made to determine if this connection - can remain open after a matching response is returned. - - @type version: C{str} - @param version: The version of the request. - - @rtype: C{bool} - @return: A flag which, if C{True}, indicates that this connection may - remain open to receive another request; if C{False}, the connection - must be closed in order to indicate the completion of the response - to C{request}. - """ - connection = request.requestHeaders.getRawHeaders('connection') - if connection: - tokens = map(str.lower, connection[0].split(' ')) - else: - tokens = [] - - # HTTP 1.0 persistent connection support is currently disabled, - # since we need a way to disable pipelining. HTTP 1.0 can't do - # pipelining since we can't know in advance if we'll have a - # content-length header, if we don't have the header we need to close the - # connection. In HTTP 1.1 this is not an issue since we use chunked - # encoding if content-length is not available. - - #if version == "HTTP/1.0": - # if 'keep-alive' in tokens: - # request.setHeader('connection', 'Keep-Alive') - # return 1 - # else: - # return 0 - if version == "HTTP/1.1": - if 'close' in tokens: - request.responseHeaders.setRawHeaders('connection', ['close']) - return False - else: - return True - else: - return False - - - def requestDone(self, request): - """ - Called by first request in queue when it is done. - """ - if request != self.requests[0]: raise TypeError - del self.requests[0] - - if self.persistent: - # notify next request it can start writing - if self.requests: - self.requests[0].noLongerQueued() - else: - if self._savedTimeOut: - self.setTimeout(self._savedTimeOut) - else: - self.transport.loseConnection() - - def timeoutConnection(self): - log.msg("Timing out client: %s" % str(self.transport.getPeer())) - policies.TimeoutMixin.timeoutConnection(self) - - def connectionLost(self, reason): - self.setTimeout(None) - for request in self.requests: - request.connectionLost(reason) - - -class HTTPFactory(protocol.ServerFactory): - """ - Factory for HTTP server. - - @ivar _logDateTime: A cached datetime string for log messages, updated by - C{_logDateTimeCall}. - @type _logDateTime: L{str} - - @ivar _logDateTimeCall: A delayed call for the next update to the cached log - datetime string. - @type _logDateTimeCall: L{IDelayedCall} provided - """ - - protocol = HTTPChannel - - logPath = None - - timeOut = 60 * 60 * 12 - - def __init__(self, logPath=None, timeout=60*60*12): - if logPath is not None: - logPath = os.path.abspath(logPath) - self.logPath = logPath - self.timeOut = timeout - - # For storing the cached log datetime and the callback to update it - self._logDateTime = None - self._logDateTimeCall = None - - - def _updateLogDateTime(self): - """ - Update log datetime periodically, so we aren't always recalculating it. - """ - self._logDateTime = datetimeToLogString() - self._logDateTimeCall = reactor.callLater(1, self._updateLogDateTime) - - - def buildProtocol(self, addr): - p = protocol.ServerFactory.buildProtocol(self, addr) - # timeOut needs to be on the Protocol instance cause - # TimeoutMixin expects it there - p.timeOut = self.timeOut - return p - - - def startFactory(self): - """ - Set up request logging if necessary. - """ - if self._logDateTimeCall is None: - self._updateLogDateTime() - - if self.logPath: - self.logFile = self._openLogFile(self.logPath) - else: - self.logFile = log.logfile - - - def stopFactory(self): - if hasattr(self, "logFile"): - if self.logFile != log.logfile: - self.logFile.close() - del self.logFile - - if self._logDateTimeCall is not None and self._logDateTimeCall.active(): - self._logDateTimeCall.cancel() - self._logDateTimeCall = None - - - def _openLogFile(self, path): - """ - Override in subclasses, e.g. to use twisted.python.logfile. - """ - f = open(path, "a", 1) - return f - - def _escape(self, s): - # pain in the ass. Return a string like python repr, but always - # escaped as if surrounding quotes were "". - r = repr(s) - if r[0] == "'": - return r[1:-1].replace('"', '\\"').replace("\\'", "'") - return r[1:-1] - - def log(self, request): - """ - Log a request's result to the logfile, by default in combined log format. - """ - if hasattr(self, "logFile"): - line = '%s - - %s "%s" %d %s "%s" "%s"\n' % ( - request.getClientIP(), - # request.getUser() or "-", # the remote user is almost never important - self._logDateTime, - '%s %s %s' % (self._escape(request.method), - self._escape(request.uri), - self._escape(request.clientproto)), - request.code, - request.sentLength or "-", - self._escape(request.getHeader("referer") or "-"), - self._escape(request.getHeader("user-agent") or "-")) - self.logFile.write(line) |