10a8c90248264a8b26970b4473770bcc3df8515fJosh Gao# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
20a8c90248264a8b26970b4473770bcc3df8515fJosh Gao# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
30a8c90248264a8b26970b4473770bcc3df8515fJosh Gao# Also licenced under the Apache License, 2.0: http://opensource.org/licenses/apache2.0.php
40a8c90248264a8b26970b4473770bcc3df8515fJosh Gao# Licensed to PSF under a Contributor Agreement
50a8c90248264a8b26970b4473770bcc3df8515fJosh Gao"""
60a8c90248264a8b26970b4473770bcc3df8515fJosh GaoMiddleware to check for obedience to the WSGI specification.
70a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
80a8c90248264a8b26970b4473770bcc3df8515fJosh GaoSome of the things this checks:
90a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
100a8c90248264a8b26970b4473770bcc3df8515fJosh Gao* Signature of the application and start_response (including that
110a8c90248264a8b26970b4473770bcc3df8515fJosh Gao  keyword arguments are not used).
120a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
130a8c90248264a8b26970b4473770bcc3df8515fJosh Gao* Environment checks:
140a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
150a8c90248264a8b26970b4473770bcc3df8515fJosh Gao  - Environment is a dictionary (and not a subclass).
160a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
170a8c90248264a8b26970b4473770bcc3df8515fJosh Gao  - That all the required keys are in the environment: REQUEST_METHOD,
180a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    SERVER_NAME, SERVER_PORT, wsgi.version, wsgi.input, wsgi.errors,
190a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    wsgi.multithread, wsgi.multiprocess, wsgi.run_once
200a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
210a8c90248264a8b26970b4473770bcc3df8515fJosh Gao  - That HTTP_CONTENT_TYPE and HTTP_CONTENT_LENGTH are not in the
220a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    environment (these headers should appear as CONTENT_LENGTH and
230a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    CONTENT_TYPE).
240a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
250a8c90248264a8b26970b4473770bcc3df8515fJosh Gao  - Warns if QUERY_STRING is missing, as the cgi module acts
260a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    unpredictably in that case.
270a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
280a8c90248264a8b26970b4473770bcc3df8515fJosh Gao  - That CGI-style variables (that don't contain a .) have
290a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    (non-unicode) string values
300a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
310a8c90248264a8b26970b4473770bcc3df8515fJosh Gao  - That wsgi.version is a tuple
320a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
330a8c90248264a8b26970b4473770bcc3df8515fJosh Gao  - That wsgi.url_scheme is 'http' or 'https' (@@: is this too
340a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    restrictive?)
350a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
360a8c90248264a8b26970b4473770bcc3df8515fJosh Gao  - Warns if the REQUEST_METHOD is not known (@@: probably too
370a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    restrictive).
380a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
390a8c90248264a8b26970b4473770bcc3df8515fJosh Gao  - That SCRIPT_NAME and PATH_INFO are empty or start with /
400a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
410a8c90248264a8b26970b4473770bcc3df8515fJosh Gao  - That at least one of SCRIPT_NAME or PATH_INFO are set.
420a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
430a8c90248264a8b26970b4473770bcc3df8515fJosh Gao  - That CONTENT_LENGTH is a positive integer.
440a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
450a8c90248264a8b26970b4473770bcc3df8515fJosh Gao  - That SCRIPT_NAME is not '/' (it should be '', and PATH_INFO should
460a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    be '/').
470a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
480a8c90248264a8b26970b4473770bcc3df8515fJosh Gao  - That wsgi.input has the methods read, readline, readlines, and
490a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    __iter__
500a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
510a8c90248264a8b26970b4473770bcc3df8515fJosh Gao  - That wsgi.errors has the methods flush, write, writelines
520a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
530a8c90248264a8b26970b4473770bcc3df8515fJosh Gao* The status is a string, contains a space, starts with an integer,
540a8c90248264a8b26970b4473770bcc3df8515fJosh Gao  and that integer is in range (> 100).
550a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
560a8c90248264a8b26970b4473770bcc3df8515fJosh Gao* That the headers is a list (not a subclass, not another kind of
570a8c90248264a8b26970b4473770bcc3df8515fJosh Gao  sequence).
580a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
590a8c90248264a8b26970b4473770bcc3df8515fJosh Gao* That the items of the headers are tuples of strings.
600a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
610a8c90248264a8b26970b4473770bcc3df8515fJosh Gao* That there is no 'status' header (that is used in CGI, but not in
620a8c90248264a8b26970b4473770bcc3df8515fJosh Gao  WSGI).
630a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
640a8c90248264a8b26970b4473770bcc3df8515fJosh Gao* That the headers don't contain newlines or colons, end in _ or -, or
650a8c90248264a8b26970b4473770bcc3df8515fJosh Gao  contain characters codes below 037.
660a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
670a8c90248264a8b26970b4473770bcc3df8515fJosh Gao* That Content-Type is given if there is content (CGI often has a
680a8c90248264a8b26970b4473770bcc3df8515fJosh Gao  default content type, but WSGI does not).
690a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
700a8c90248264a8b26970b4473770bcc3df8515fJosh Gao* That no Content-Type is given when there is no content (@@: is this
710a8c90248264a8b26970b4473770bcc3df8515fJosh Gao  too restrictive?)
720a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
730a8c90248264a8b26970b4473770bcc3df8515fJosh Gao* That the exc_info argument to start_response is a tuple or None.
740a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
750a8c90248264a8b26970b4473770bcc3df8515fJosh Gao* That all calls to the writer are with strings, and no other methods
760a8c90248264a8b26970b4473770bcc3df8515fJosh Gao  on the writer are accessed.
770a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
780a8c90248264a8b26970b4473770bcc3df8515fJosh Gao* That wsgi.input is used properly:
790a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
800a8c90248264a8b26970b4473770bcc3df8515fJosh Gao  - .read() is called with zero or one argument
810a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
820a8c90248264a8b26970b4473770bcc3df8515fJosh Gao  - That it returns a string
830a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
840a8c90248264a8b26970b4473770bcc3df8515fJosh Gao  - That readline, readlines, and __iter__ return strings
850a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
860a8c90248264a8b26970b4473770bcc3df8515fJosh Gao  - That .close() is not called
870a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
880a8c90248264a8b26970b4473770bcc3df8515fJosh Gao  - No other methods are provided
890a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
900a8c90248264a8b26970b4473770bcc3df8515fJosh Gao* That wsgi.errors is used properly:
910a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
920a8c90248264a8b26970b4473770bcc3df8515fJosh Gao  - .write() and .writelines() is called with a string
930a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
940a8c90248264a8b26970b4473770bcc3df8515fJosh Gao  - That .close() is not called, and no other methods are provided.
950a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
960a8c90248264a8b26970b4473770bcc3df8515fJosh Gao* The response iterator:
970a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
980a8c90248264a8b26970b4473770bcc3df8515fJosh Gao  - That it is not a string (it should be a list of a single string; a
990a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    string will work, but perform horribly).
1000a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
1010a8c90248264a8b26970b4473770bcc3df8515fJosh Gao  - That .next() returns a string
1020a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
1030a8c90248264a8b26970b4473770bcc3df8515fJosh Gao  - That the iterator is not iterated over until start_response has
1040a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    been called (that can signal either a server or application
1050a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    error).
1060a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
1070a8c90248264a8b26970b4473770bcc3df8515fJosh Gao  - That .close() is called (doesn't raise exception, only prints to
1080a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    sys.stderr, because we only know it isn't called when the object
1090a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    is garbage collected).
1100a8c90248264a8b26970b4473770bcc3df8515fJosh Gao"""
1110a8c90248264a8b26970b4473770bcc3df8515fJosh Gao__all__ = ['validator']
1120a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
1130a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
1140a8c90248264a8b26970b4473770bcc3df8515fJosh Gaoimport re
1150a8c90248264a8b26970b4473770bcc3df8515fJosh Gaoimport sys
1160a8c90248264a8b26970b4473770bcc3df8515fJosh Gaofrom types import DictType, StringType, TupleType, ListType
1170a8c90248264a8b26970b4473770bcc3df8515fJosh Gaoimport warnings
1180a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
1190a8c90248264a8b26970b4473770bcc3df8515fJosh Gaoheader_re = re.compile(r'^[a-zA-Z][a-zA-Z0-9\-_]*$')
1200a8c90248264a8b26970b4473770bcc3df8515fJosh Gaobad_header_value_re = re.compile(r'[\000-\037]')
1210a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
1220a8c90248264a8b26970b4473770bcc3df8515fJosh Gaoclass WSGIWarning(Warning):
1230a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    """
1240a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    Raised in response to WSGI-spec-related warnings
1250a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    """
1260a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
1270a8c90248264a8b26970b4473770bcc3df8515fJosh Gaodef assert_(cond, *args):
1280a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    if not cond:
1290a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        raise AssertionError(*args)
1300a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
1310a8c90248264a8b26970b4473770bcc3df8515fJosh Gaodef validator(application):
1320a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
1330a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    """
1340a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    When applied between a WSGI server and a WSGI application, this
1350a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    middleware will check for WSGI compliancy on a number of levels.
1360a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    This middleware does not modify the request or response in any
1370a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    way, but will raise an AssertionError if anything seems off
1380a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    (except for a failure to close the application iterator, which
1390a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    will be printed to stderr -- there's no way to raise an exception
1400a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    at that point).
1410a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    """
1420a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
1430a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    def lint_app(*args, **kw):
1440a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        assert_(len(args) == 2, "Two arguments required")
1450a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        assert_(not kw, "No keyword arguments allowed")
1460a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        environ, start_response = args
1470a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
1480a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        check_environ(environ)
1490a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
1500a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        # We use this to check if the application returns without
1510a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        # calling start_response:
1520a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        start_response_started = []
1530a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
1540a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        def start_response_wrapper(*args, **kw):
1550a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            assert_(len(args) == 2 or len(args) == 3, (
1560a8c90248264a8b26970b4473770bcc3df8515fJosh Gao                "Invalid number of arguments: %s" % (args,)))
1570a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            assert_(not kw, "No keyword arguments allowed")
1580a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            status = args[0]
1590a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            headers = args[1]
1600a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            if len(args) == 3:
1610a8c90248264a8b26970b4473770bcc3df8515fJosh Gao                exc_info = args[2]
1620a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            else:
1630a8c90248264a8b26970b4473770bcc3df8515fJosh Gao                exc_info = None
1640a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
1650a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            check_status(status)
1660a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            check_headers(headers)
1670a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            check_content_type(status, headers)
1680a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            check_exc_info(exc_info)
1690a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
1700a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            start_response_started.append(None)
1710a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            return WriteWrapper(start_response(*args))
1720a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
1730a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        environ['wsgi.input'] = InputWrapper(environ['wsgi.input'])
1740a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        environ['wsgi.errors'] = ErrorWrapper(environ['wsgi.errors'])
1750a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
1760a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        iterator = application(environ, start_response_wrapper)
1770a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        assert_(iterator is not None and iterator != False,
1780a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            "The application must return an iterator, if only an empty list")
1790a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
1800a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        check_iterator(iterator)
1810a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
1820a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        return IteratorWrapper(iterator, start_response_started)
1830a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
1840a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    return lint_app
1850a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
1860a8c90248264a8b26970b4473770bcc3df8515fJosh Gaoclass InputWrapper:
1870a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
1880a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    def __init__(self, wsgi_input):
1890a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        self.input = wsgi_input
1900a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
1910a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    def read(self, *args):
1920a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        assert_(len(args) <= 1)
1930a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        v = self.input.read(*args)
1940a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        assert_(type(v) is type(""))
1950a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        return v
1960a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
1970a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    def readline(self):
1980a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        v = self.input.readline()
1990a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        assert_(type(v) is type(""))
2000a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        return v
2010a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
2020a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    def readlines(self, *args):
2030a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        assert_(len(args) <= 1)
2040a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        lines = self.input.readlines(*args)
2050a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        assert_(type(lines) is type([]))
2060a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        for line in lines:
2070a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            assert_(type(line) is type(""))
2080a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        return lines
2090a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
2100a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    def __iter__(self):
2110a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        while 1:
2120a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            line = self.readline()
2130a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            if not line:
2140a8c90248264a8b26970b4473770bcc3df8515fJosh Gao                return
2150a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            yield line
2160a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
2170a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    def close(self):
2180a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        assert_(0, "input.close() must not be called")
2190a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
2200a8c90248264a8b26970b4473770bcc3df8515fJosh Gaoclass ErrorWrapper:
2210a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
2220a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    def __init__(self, wsgi_errors):
2230a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        self.errors = wsgi_errors
2240a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
2250a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    def write(self, s):
2260a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        assert_(type(s) is type(""))
2270a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        self.errors.write(s)
2280a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
2290a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    def flush(self):
2300a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        self.errors.flush()
2310a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
2320a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    def writelines(self, seq):
2330a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        for line in seq:
2340a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            self.write(line)
2350a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
2360a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    def close(self):
2370a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        assert_(0, "errors.close() must not be called")
2380a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
2390a8c90248264a8b26970b4473770bcc3df8515fJosh Gaoclass WriteWrapper:
2400a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
2410a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    def __init__(self, wsgi_writer):
2420a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        self.writer = wsgi_writer
2430a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
2440a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    def __call__(self, s):
2450a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        assert_(type(s) is type(""))
2460a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        self.writer(s)
2470a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
2480a8c90248264a8b26970b4473770bcc3df8515fJosh Gaoclass PartialIteratorWrapper:
2490a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
2500a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    def __init__(self, wsgi_iterator):
2510a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        self.iterator = wsgi_iterator
2520a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
2530a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    def __iter__(self):
2540a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        # We want to make sure __iter__ is called
2550a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        return IteratorWrapper(self.iterator, None)
2560a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
2570a8c90248264a8b26970b4473770bcc3df8515fJosh Gaoclass IteratorWrapper:
2580a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
2590a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    def __init__(self, wsgi_iterator, check_start_response):
2600a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        self.original_iterator = wsgi_iterator
2610a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        self.iterator = iter(wsgi_iterator)
2620a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        self.closed = False
2630a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        self.check_start_response = check_start_response
2640a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
2650a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    def __iter__(self):
2660a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        return self
2670a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
2680a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    def next(self):
2690a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        assert_(not self.closed,
2700a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            "Iterator read after closed")
2710a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        v = self.iterator.next()
2720a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        if self.check_start_response is not None:
2730a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            assert_(self.check_start_response,
2740a8c90248264a8b26970b4473770bcc3df8515fJosh Gao                "The application returns and we started iterating over its body, but start_response has not yet been called")
2750a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            self.check_start_response = None
2760a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        return v
2770a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
2780a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    def close(self):
2790a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        self.closed = True
2800a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        if hasattr(self.original_iterator, 'close'):
2810a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            self.original_iterator.close()
2820a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
2830a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    def __del__(self):
2840a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        if not self.closed:
2850a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            sys.stderr.write(
2860a8c90248264a8b26970b4473770bcc3df8515fJosh Gao                "Iterator garbage collected without being closed")
2870a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        assert_(self.closed,
2880a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            "Iterator garbage collected without being closed")
2890a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
2900a8c90248264a8b26970b4473770bcc3df8515fJosh Gaodef check_environ(environ):
2910a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    assert_(type(environ) is DictType,
2920a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        "Environment is not of the right type: %r (environment: %r)"
2930a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        % (type(environ), environ))
2940a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
2950a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    for key in ['REQUEST_METHOD', 'SERVER_NAME', 'SERVER_PORT',
2960a8c90248264a8b26970b4473770bcc3df8515fJosh Gao                'wsgi.version', 'wsgi.input', 'wsgi.errors',
2970a8c90248264a8b26970b4473770bcc3df8515fJosh Gao                'wsgi.multithread', 'wsgi.multiprocess',
2980a8c90248264a8b26970b4473770bcc3df8515fJosh Gao                'wsgi.run_once']:
2990a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        assert_(key in environ,
3000a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            "Environment missing required key: %r" % (key,))
3010a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
3020a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    for key in ['HTTP_CONTENT_TYPE', 'HTTP_CONTENT_LENGTH']:
3030a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        assert_(key not in environ,
3040a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            "Environment should not have the key: %s "
3050a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            "(use %s instead)" % (key, key[5:]))
3060a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
3070a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    if 'QUERY_STRING' not in environ:
3080a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        warnings.warn(
3090a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            'QUERY_STRING is not in the WSGI environment; the cgi '
3100a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            'module will use sys.argv when this variable is missing, '
3110a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            'so application errors are more likely',
3120a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            WSGIWarning)
3130a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
3140a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    for key in environ.keys():
3150a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        if '.' in key:
3160a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            # Extension, we don't care about its type
3170a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            continue
3180a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        assert_(type(environ[key]) is StringType,
3190a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            "Environmental variable %s is not a string: %r (value: %r)"
3200a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            % (key, type(environ[key]), environ[key]))
3210a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
3220a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    assert_(type(environ['wsgi.version']) is TupleType,
3230a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        "wsgi.version should be a tuple (%r)" % (environ['wsgi.version'],))
3240a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    assert_(environ['wsgi.url_scheme'] in ('http', 'https'),
3250a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        "wsgi.url_scheme unknown: %r" % environ['wsgi.url_scheme'])
3260a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
3270a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    check_input(environ['wsgi.input'])
3280a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    check_errors(environ['wsgi.errors'])
3290a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
3300a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    # @@: these need filling out:
3310a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    if environ['REQUEST_METHOD'] not in (
3320a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        'GET', 'HEAD', 'POST', 'OPTIONS','PUT','DELETE','TRACE'):
3330a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        warnings.warn(
3340a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            "Unknown REQUEST_METHOD: %r" % environ['REQUEST_METHOD'],
3350a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            WSGIWarning)
3360a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
3370a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    assert_(not environ.get('SCRIPT_NAME')
3380a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            or environ['SCRIPT_NAME'].startswith('/'),
3390a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        "SCRIPT_NAME doesn't start with /: %r" % environ['SCRIPT_NAME'])
3400a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    assert_(not environ.get('PATH_INFO')
3410a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            or environ['PATH_INFO'].startswith('/'),
3420a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        "PATH_INFO doesn't start with /: %r" % environ['PATH_INFO'])
3430a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    if environ.get('CONTENT_LENGTH'):
3440a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        assert_(int(environ['CONTENT_LENGTH']) >= 0,
3450a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            "Invalid CONTENT_LENGTH: %r" % environ['CONTENT_LENGTH'])
3460a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
3470a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    if not environ.get('SCRIPT_NAME'):
3480a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        assert_('PATH_INFO' in environ,
3490a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            "One of SCRIPT_NAME or PATH_INFO are required (PATH_INFO "
3500a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            "should at least be '/' if SCRIPT_NAME is empty)")
3510a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    assert_(environ.get('SCRIPT_NAME') != '/',
3520a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        "SCRIPT_NAME cannot be '/'; it should instead be '', and "
3530a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        "PATH_INFO should be '/'")
3540a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
3550a8c90248264a8b26970b4473770bcc3df8515fJosh Gaodef check_input(wsgi_input):
3560a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    for attr in ['read', 'readline', 'readlines', '__iter__']:
3570a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        assert_(hasattr(wsgi_input, attr),
3580a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            "wsgi.input (%r) doesn't have the attribute %s"
3590a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            % (wsgi_input, attr))
3600a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
3610a8c90248264a8b26970b4473770bcc3df8515fJosh Gaodef check_errors(wsgi_errors):
3620a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    for attr in ['flush', 'write', 'writelines']:
3630a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        assert_(hasattr(wsgi_errors, attr),
3640a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            "wsgi.errors (%r) doesn't have the attribute %s"
3650a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            % (wsgi_errors, attr))
3660a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
3670a8c90248264a8b26970b4473770bcc3df8515fJosh Gaodef check_status(status):
3680a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    assert_(type(status) is StringType,
3690a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        "Status must be a string (not %r)" % status)
3700a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    # Implicitly check that we can turn it into an integer:
3710a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    status_code = status.split(None, 1)[0]
3720a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    assert_(len(status_code) == 3,
3730a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        "Status codes must be three characters: %r" % status_code)
3740a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    status_int = int(status_code)
3750a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    assert_(status_int >= 100, "Status code is invalid: %r" % status_int)
3760a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    if len(status) < 4 or status[3] != ' ':
3770a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        warnings.warn(
3780a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            "The status string (%r) should be a three-digit integer "
3790a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            "followed by a single space and a status explanation"
3800a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            % status, WSGIWarning)
3810a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
3820a8c90248264a8b26970b4473770bcc3df8515fJosh Gaodef check_headers(headers):
3830a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    assert_(type(headers) is ListType,
3840a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        "Headers (%r) must be of type list: %r"
3850a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        % (headers, type(headers)))
3860a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    header_names = {}
3870a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    for item in headers:
3880a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        assert_(type(item) is TupleType,
3890a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            "Individual headers (%r) must be of type tuple: %r"
3900a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            % (item, type(item)))
3910a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        assert_(len(item) == 2)
3920a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        name, value = item
3930a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        assert_(name.lower() != 'status',
3940a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            "The Status header cannot be used; it conflicts with CGI "
3950a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            "script, and HTTP status is not given through headers "
3960a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            "(value: %r)." % value)
3970a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        header_names[name.lower()] = None
3980a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        assert_('\n' not in name and ':' not in name,
3990a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            "Header names may not contain ':' or '\\n': %r" % name)
4000a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        assert_(header_re.search(name), "Bad header name: %r" % name)
4010a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        assert_(not name.endswith('-') and not name.endswith('_'),
4020a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            "Names may not end in '-' or '_': %r" % name)
4030a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        if bad_header_value_re.search(value):
4040a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            assert_(0, "Bad header value: %r (bad char: %r)"
4050a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            % (value, bad_header_value_re.search(value).group(0)))
4060a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
4070a8c90248264a8b26970b4473770bcc3df8515fJosh Gaodef check_content_type(status, headers):
4080a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    code = int(status.split(None, 1)[0])
4090a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    # @@: need one more person to verify this interpretation of RFC 2616
4100a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    #     http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
4110a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    NO_MESSAGE_BODY = (204, 304)
4120a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    for name, value in headers:
4130a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        if name.lower() == 'content-type':
4140a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            if code not in NO_MESSAGE_BODY:
4150a8c90248264a8b26970b4473770bcc3df8515fJosh Gao                return
4160a8c90248264a8b26970b4473770bcc3df8515fJosh Gao            assert_(0, ("Content-Type header found in a %s response, "
4170a8c90248264a8b26970b4473770bcc3df8515fJosh Gao                        "which must not return content.") % code)
4180a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    if code not in NO_MESSAGE_BODY:
4190a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        assert_(0, "No Content-Type header found in headers (%s)" % headers)
4200a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
4210a8c90248264a8b26970b4473770bcc3df8515fJosh Gaodef check_exc_info(exc_info):
4220a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    assert_(exc_info is None or type(exc_info) is type(()),
4230a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        "exc_info (%r) is not a tuple: %r" % (exc_info, type(exc_info)))
4240a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    # More exc_info checks?
4250a8c90248264a8b26970b4473770bcc3df8515fJosh Gao
4260a8c90248264a8b26970b4473770bcc3df8515fJosh Gaodef check_iterator(iterator):
4270a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    # Technically a string is legal, which is why it's a really bad
4280a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    # idea, because it may cause the response to be returned
4290a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    # character-by-character
4300a8c90248264a8b26970b4473770bcc3df8515fJosh Gao    assert_(not isinstance(iterator, str),
4310a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        "You should not return a string as your application iterator, "
4320a8c90248264a8b26970b4473770bcc3df8515fJosh Gao        "instead return a single-item list containing that string.")
433