1ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craikimport binascii
2ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craikimport io
3ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craikimport os
4ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craikimport re
5ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craikimport sys
6ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craikimport tempfile
7ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craikimport mimetypes
8ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craiktry:
9ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    import simplejson as json
10ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craikexcept ImportError:
11ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    import json
12ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craikimport warnings
13ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
14ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craikfrom webob.acceptparse import (
15ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    AcceptLanguage,
16ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    AcceptCharset,
17ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    MIMEAccept,
18ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    MIMENilAccept,
19ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    NoAccept,
20ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    accept_property,
21ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    )
22ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
23ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craikfrom webob.cachecontrol import (
24ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    CacheControl,
25ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    serialize_cache_control,
26ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    )
27ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
28ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craikfrom webob.compat import (
29ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    PY3,
30ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    bytes_,
31ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    integer_types,
32ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    native_,
33ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    parse_qsl_text,
34ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    reraise,
35ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    text_type,
36ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    url_encode,
37ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    url_quote,
38ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    url_unquote,
39ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    quote_plus,
40ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    urlparse,
41ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    cgi_FieldStorage
42ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    )
43ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
44ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craikfrom webob.cookies import RequestCookies
45ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
46ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craikfrom webob.descriptors import (
47ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    CHARSET_RE,
48ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    SCHEME_RE,
49ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    converter,
50ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    converter_date,
51ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    environ_getter,
52ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    environ_decoder,
53ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    parse_auth,
54ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    parse_int,
55ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    parse_int_safe,
56ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    parse_range,
57ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    serialize_auth,
58ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    serialize_if_range,
59ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    serialize_int,
60ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    serialize_range,
61ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    upath_property,
62ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    deprecated_property,
63ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    )
64ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
65ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craikfrom webob.etag import (
66ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    IfRange,
67ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    AnyETag,
68ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    NoETag,
69ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    etag_property,
70ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    )
71ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
72ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craikfrom webob.headers import EnvironHeaders
73ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
74ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craikfrom webob.multidict import (
75ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    NestedMultiDict,
76ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    MultiDict,
77ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    NoVars,
78ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    GetDict,
79ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    )
80ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
81ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craikfrom webob.util import warn_deprecation
82ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
83ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik__all__ = ['BaseRequest', 'Request', 'LegacyRequest']
84ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
85ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craikclass _NoDefault:
86ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def __repr__(self):
87ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        return '(No Default)'
88ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris CraikNoDefault = _NoDefault()
89ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
90ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris CraikPATH_SAFE = '/:@&+$,'
91ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
92ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craikhttp_method_probably_has_body = dict.fromkeys(
93ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    ('GET', 'HEAD', 'DELETE', 'TRACE'), False)
94ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craikhttp_method_probably_has_body.update(
95ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    dict.fromkeys(('POST', 'PUT', 'PATCH'), True))
96ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
97ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik_LATIN_ENCODINGS = (
98ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    'ascii', 'latin-1', 'latin', 'latin_1', 'l1', 'latin1',
99ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    'iso-8859-1', 'iso8859_1', 'iso_8859_1', 'iso8859', '8859',
100ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    )
101ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
102ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craikclass BaseRequest(object):
103ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    ## The limit after which request bodies should be stored on disk
104ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    ## if they are read in (under this, and the request body is stored
105ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    ## in memory):
106ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    request_body_tempfile_limit = 10*1024
107ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
108ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    _charset = None
109ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
110ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def __init__(self, environ, charset=None, unicode_errors=None,
111ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                 decode_param_names=None, **kw):
112ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
113ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if type(environ) is not dict:
114ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            raise TypeError(
115ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                "WSGI environ must be a dict; you passed %r" % (environ,))
116ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if unicode_errors is not None:
117ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            warnings.warn(
118ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                "You unicode_errors=%r to the Request constructor.  Passing a "
119ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                "``unicode_errors`` value to the Request is no longer "
120ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                "supported in WebOb 1.2+.  This value has been ignored " % (
121ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                    unicode_errors,),
122ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                DeprecationWarning
123ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                )
124ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if decode_param_names is not None:
125ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            warnings.warn(
126ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                "You passed decode_param_names=%r to the Request constructor. "
127ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                "Passing a ``decode_param_names`` value to the Request "
128ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                "is no longer supported in WebOb 1.2+.  This value has "
129ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                "been ignored " % (decode_param_names,),
130ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                DeprecationWarning
131ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                )
132ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if not _is_utf8(charset):
133ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            raise DeprecationWarning(
134ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                "You passed charset=%r to the Request constructor. As of "
135ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                "WebOb 1.2, if your application needs a non-UTF-8 request "
136ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                "charset, please construct the request without a charset or "
137ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                "with a charset of 'None',  then use ``req = "
138ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                "req.decode(charset)``" % charset
139ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
140ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            )
141ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        d = self.__dict__
142ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        d['environ'] = environ
143ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if kw:
144ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            cls = self.__class__
145ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            if 'method' in kw:
146ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                # set method first, because .body setters
147ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                # depend on it for checks
148ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                self.method = kw.pop('method')
149ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            for name, value in kw.items():
150ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                if not hasattr(cls, name):
151ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                    raise TypeError(
152ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                        "Unexpected keyword: %s=%r" % (name, value))
153ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                setattr(self, name, value)
154ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
155ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    if PY3: # pragma: no cover
156ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        def encget(self, key, default=NoDefault, encattr=None):
157ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            val = self.environ.get(key, default)
158ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            if val is NoDefault:
159ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                raise KeyError(key)
160ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            if val is default:
161ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                return default
162ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            if not encattr:
163ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                return val
164ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            encoding = getattr(self, encattr)
165ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            if encoding in _LATIN_ENCODINGS: # shortcut
166ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                return val
167ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            return bytes_(val, 'latin-1').decode(encoding)
168ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    else:
169ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        def encget(self, key, default=NoDefault, encattr=None):
170ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            val = self.environ.get(key, default)
171ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            if val is NoDefault:
172ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                raise KeyError(key)
173ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            if val is default:
174ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                return default
175ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            if encattr is None:
176ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                return val
177ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            encoding = getattr(self, encattr)
178ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            return val.decode(encoding)
179ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
180ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def encset(self, key, val, encattr=None):
181ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if encattr:
182ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            encoding = getattr(self, encattr)
183ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        else:
184ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            encoding = 'ascii'
185ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if PY3: # pragma: no cover
186ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            self.environ[key] = bytes_(val, encoding).decode('latin-1')
187ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        else:
188ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            self.environ[key] = bytes_(val, encoding)
189ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
190ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    @property
191ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def charset(self):
192ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if self._charset is None:
193ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            charset = detect_charset(self._content_type_raw)
194ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            if _is_utf8(charset):
195ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                charset = 'UTF-8'
196ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            self._charset = charset
197ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        return self._charset
198ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
199ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    @charset.setter
200ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def charset(self, charset):
201ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if _is_utf8(charset):
202ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            charset = 'UTF-8'
203ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if charset != self.charset:
204ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            raise DeprecationWarning("Use req = req.decode(%r)" % charset)
205ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
206ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def decode(self, charset=None, errors='strict'):
207ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        charset = charset or self.charset
208ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if charset == 'UTF-8':
209ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            return self
210ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        # cookies and path are always utf-8
211ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        t = Transcoder(charset, errors)
212ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
213ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        new_content_type = CHARSET_RE.sub('; charset="UTF-8"',
214ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                                          self._content_type_raw)
215ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        content_type = self.content_type
216ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        r = self.__class__(
217ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            self.environ.copy(),
218ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            query_string=t.transcode_query(self.query_string),
219ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            content_type=new_content_type,
220ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        )
221ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
222ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if content_type == 'application/x-www-form-urlencoded':
223ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            r.body = bytes_(t.transcode_query(native_(r.body)))
224ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            return r
225ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        elif content_type != 'multipart/form-data':
226ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            return r
227ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
228ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        fs_environ = self.environ.copy()
229ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        fs_environ.setdefault('CONTENT_LENGTH', '0')
230ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        fs_environ['QUERY_STRING'] = ''
231ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if PY3: # pragma: no cover
232ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            fs = cgi_FieldStorage(fp=self.body_file,
233ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                                  environ=fs_environ,
234ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                                  keep_blank_values=True,
235ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                                  encoding=charset,
236ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                                  errors=errors)
237ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        else:
238ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            fs = cgi_FieldStorage(fp=self.body_file,
239ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                                  environ=fs_environ,
240ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                                  keep_blank_values=True)
241ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
242ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
243ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        fout = t.transcode_fs(fs, r._content_type_raw)
244ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
245ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        # this order is important, because setting body_file
246ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        # resets content_length
247ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        r.body_file = fout
248ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        r.content_length = fout.tell()
249ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        fout.seek(0)
250ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        return r
251ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
252ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
253ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    # this is necessary for correct warnings depth for both
254ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    # BaseRequest and Request (due to AdhocAttrMixin.__setattr__)
255ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    _setattr_stacklevel = 2
256ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
257ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def _body_file__get(self):
258ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
259ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            Input stream of the request (wsgi.input).
260ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            Setting this property resets the content_length and seekable flag
261ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            (unlike setting req.body_file_raw).
262ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
263ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if not self.is_body_readable:
264ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            return io.BytesIO()
265ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        r = self.body_file_raw
266ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        clen = self.content_length
267ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if not self.is_body_seekable and clen is not None:
268ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            # we need to wrap input in LimitedLengthFile
269ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            # but we have to cache the instance as well
270ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            # otherwise this would stop working
271ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            # (.remaining counter would reset between calls):
272ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            #   req.body_file.read(100)
273ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            #   req.body_file.read(100)
274ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            env = self.environ
275ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            wrapped, raw = env.get('webob._body_file', (0,0))
276ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            if raw is not r:
277ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                wrapped = LimitedLengthFile(r, clen)
278ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                wrapped = io.BufferedReader(wrapped)
279ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                env['webob._body_file'] = wrapped, r
280ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            r = wrapped
281ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        return r
282ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
283ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def _body_file__set(self, value):
284ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if isinstance(value, bytes):
285ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            warn_deprecation(
286ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                "Please use req.body = b'bytes' or req.body_file = fileobj",
287ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                '1.2',
288ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                self._setattr_stacklevel
289ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            )
290ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        self.content_length = None
291ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        self.body_file_raw = value
292ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        self.is_body_seekable = False
293ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        self.is_body_readable = True
294ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def _body_file__del(self):
295ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        self.body = b''
296ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    body_file = property(_body_file__get,
297ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                         _body_file__set,
298ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                         _body_file__del,
299ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                         doc=_body_file__get.__doc__)
300ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    body_file_raw = environ_getter('wsgi.input')
301ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    @property
302ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def body_file_seekable(self):
303ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
304ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            Get the body of the request (wsgi.input) as a seekable file-like
305ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            object. Middleware and routing applications should use this
306ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            attribute over .body_file.
307ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
308ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            If you access this value, CONTENT_LENGTH will also be updated.
309ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
310ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if not self.is_body_seekable:
311ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            self.make_body_seekable()
312ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        return self.body_file_raw
313ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
314ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    url_encoding = environ_getter('webob.url_encoding', 'UTF-8')
315ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    scheme = environ_getter('wsgi.url_scheme')
316ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    method = environ_getter('REQUEST_METHOD', 'GET')
317ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    http_version = environ_getter('SERVER_PROTOCOL')
318ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    content_length = converter(
319ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        environ_getter('CONTENT_LENGTH', None, '14.13'),
320ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        parse_int_safe, serialize_int, 'int')
321ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    remote_user = environ_getter('REMOTE_USER', None)
322ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    remote_addr = environ_getter('REMOTE_ADDR', None)
323ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    query_string = environ_getter('QUERY_STRING', '')
324ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    server_name = environ_getter('SERVER_NAME')
325ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    server_port = converter(
326ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        environ_getter('SERVER_PORT'),
327ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        parse_int, serialize_int, 'int')
328ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
329ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    script_name = environ_decoder('SCRIPT_NAME', '', encattr='url_encoding')
330ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    path_info = environ_decoder('PATH_INFO', encattr='url_encoding')
331ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
332ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    # bw compat
333ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    uscript_name = script_name
334ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    upath_info = path_info
335ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
336ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    _content_type_raw = environ_getter('CONTENT_TYPE', '')
337ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
338ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def _content_type__get(self):
339ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """Return the content type, but leaving off any parameters (like
340ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        charset, but also things like the type in ``application/atom+xml;
341ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        type=entry``)
342ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
343ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        If you set this property, you can include parameters, or if
344ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        you don't include any parameters in the value then existing
345ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        parameters will be preserved.
346ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
347ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        return self._content_type_raw.split(';', 1)[0]
348ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def _content_type__set(self, value=None):
349ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if value is not None:
350ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            value = str(value)
351ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            if ';' not in value:
352ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                content_type = self._content_type_raw
353ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                if ';' in content_type:
354ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                    value += ';' + content_type.split(';', 1)[1]
355ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        self._content_type_raw = value
356ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
357ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    content_type = property(_content_type__get,
358ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                            _content_type__set,
359ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                            _content_type__set,
360ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                            _content_type__get.__doc__)
361ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
362ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    _headers = None
363ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
364ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def _headers__get(self):
365ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
366ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        All the request headers as a case-insensitive dictionary-like
367ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        object.
368ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
369ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if self._headers is None:
370ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            self._headers = EnvironHeaders(self.environ)
371ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        return self._headers
372ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
373ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def _headers__set(self, value):
374ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        self.headers.clear()
375ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        self.headers.update(value)
376ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
377ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    headers = property(_headers__get, _headers__set, doc=_headers__get.__doc__)
378ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
379ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    @property
380ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def client_addr(self):
381ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
382ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        The effective client IP address as a string.  If the
383ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        ``HTTP_X_FORWARDED_FOR`` header exists in the WSGI environ, this
384ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        attribute returns the client IP address present in that header
385ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        (e.g. if the header value is ``192.168.1.1, 192.168.1.2``, the value
386ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        will be ``192.168.1.1``). If no ``HTTP_X_FORWARDED_FOR`` header is
387ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        present in the environ at all, this attribute will return the value
388ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        of the ``REMOTE_ADDR`` header.  If the ``REMOTE_ADDR`` header is
389ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        unset, this attribute will return the value ``None``.
390ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
391ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        .. warning::
392ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
393ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik           It is possible for user agents to put someone else's IP or just
394ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik           any string in ``HTTP_X_FORWARDED_FOR`` as it is a normal HTTP
395ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik           header. Forward proxies can also provide incorrect values (private
396ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik           IP addresses etc).  You cannot "blindly" trust the result of this
397ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik           method to provide you with valid data unless you're certain that
398ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik           ``HTTP_X_FORWARDED_FOR`` has the correct values.  The WSGI server
399ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik           must be behind a trusted proxy for this to be true.
400ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
401ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        e = self.environ
402ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        xff = e.get('HTTP_X_FORWARDED_FOR')
403ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if xff is not None:
404ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            addr = xff.split(',')[0].strip()
405ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        else:
406ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            addr = e.get('REMOTE_ADDR')
407ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        return addr
408ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
409ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    @property
410ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def host_port(self):
411ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
412ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        The effective server port number as a string.  If the ``HTTP_HOST``
413ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        header exists in the WSGI environ, this attribute returns the port
414ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        number present in that header. If the ``HTTP_HOST`` header exists but
415ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        contains no explicit port number: if the WSGI url scheme is "https" ,
416ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        this attribute returns "443", if the WSGI url scheme is "http", this
417ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        attribute returns "80" .  If no ``HTTP_HOST`` header is present in
418ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        the environ at all, this attribute will return the value of the
419ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        ``SERVER_PORT`` header (which is guaranteed to be present).
420ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
421ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        e = self.environ
422ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        host = e.get('HTTP_HOST')
423ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if host is not None:
424ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            if ':' in host:
425ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                host, port = host.split(':', 1)
426ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            else:
427ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                url_scheme = e['wsgi.url_scheme']
428ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                if url_scheme == 'https':
429ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                    port = '443'
430ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                else:
431ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                    port = '80'
432ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        else:
433ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            port = e['SERVER_PORT']
434ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        return port
435ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
436ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    @property
437ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def host_url(self):
438ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
439ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        The URL through the host (no path)
440ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
441ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        e = self.environ
442ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        scheme = e.get('wsgi.url_scheme')
443ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        url = scheme + '://'
444ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        host = e.get('HTTP_HOST')
445ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if host is not None:
446ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            if ':' in host:
447ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                host, port = host.split(':', 1)
448ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            else:
449ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                port = None
450ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        else:
451ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            host = e.get('SERVER_NAME')
452ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            port = e.get('SERVER_PORT')
453ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if scheme == 'https':
454ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            if port == '443':
455ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                port = None
456ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        elif scheme == 'http':
457ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            if port == '80':
458ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                port = None
459ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        url += host
460ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if port:
461ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            url += ':%s' % port
462ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        return url
463ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
464ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    @property
465ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def application_url(self):
466ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
467ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        The URL including SCRIPT_NAME (no PATH_INFO or query string)
468ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
469ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        bscript_name = bytes_(self.script_name, self.url_encoding)
470ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        return self.host_url + url_quote(bscript_name, PATH_SAFE)
471ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
472ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    @property
473ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def path_url(self):
474ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
475ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        The URL including SCRIPT_NAME and PATH_INFO, but not QUERY_STRING
476ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
477ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        bpath_info = bytes_(self.path_info, self.url_encoding)
478ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        return self.application_url + url_quote(bpath_info, PATH_SAFE)
479ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
480ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    @property
481ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def path(self):
482ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
483ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        The path of the request, without host or query string
484ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
485ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        bscript = bytes_(self.script_name, self.url_encoding)
486ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        bpath = bytes_(self.path_info, self.url_encoding)
487ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        return url_quote(bscript, PATH_SAFE) + url_quote(bpath, PATH_SAFE)
488ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
489ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    @property
490ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def path_qs(self):
491ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
492ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        The path of the request, without host but with query string
493ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
494ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        path = self.path
495ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        qs = self.environ.get('QUERY_STRING')
496ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if qs:
497ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            path += '?' + qs
498ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        return path
499ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
500ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    @property
501ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def url(self):
502ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
503ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        The full request URL, including QUERY_STRING
504ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
505ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        url = self.path_url
506ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        qs = self.environ.get('QUERY_STRING')
507ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if qs:
508ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            url += '?' + qs
509ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        return url
510ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
511ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def relative_url(self, other_url, to_application=False):
512ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
513ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        Resolve other_url relative to the request URL.
514ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
515ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        If ``to_application`` is True, then resolve it relative to the
516ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        URL with only SCRIPT_NAME
517ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
518ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if to_application:
519ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            url = self.application_url
520ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            if not url.endswith('/'):
521ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                url += '/'
522ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        else:
523ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            url = self.path_url
524ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        return urlparse.urljoin(url, other_url)
525ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
526ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def path_info_pop(self, pattern=None):
527ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
528ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        'Pops' off the next segment of PATH_INFO, pushing it onto
529ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        SCRIPT_NAME, and returning the popped segment.  Returns None if
530ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        there is nothing left on PATH_INFO.
531ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
532ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        Does not return ``''`` when there's an empty segment (like
533ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        ``/path//path``); these segments are just ignored.
534ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
535ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        Optional ``pattern`` argument is a regexp to match the return value
536ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        before returning. If there is no match, no changes are made to the
537ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        request and None is returned.
538ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
539ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        path = self.path_info
540ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if not path:
541ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            return None
542ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        slashes = ''
543ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        while path.startswith('/'):
544ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            slashes += '/'
545ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            path = path[1:]
546ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        idx = path.find('/')
547ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if idx == -1:
548ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            idx = len(path)
549ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        r = path[:idx]
550ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if pattern is None or re.match(pattern, r):
551ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            self.script_name += slashes + r
552ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            self.path_info = path[idx:]
553ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            return r
554ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
555ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def path_info_peek(self):
556ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
557ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        Returns the next segment on PATH_INFO, or None if there is no
558ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        next segment.  Doesn't modify the environment.
559ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
560ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        path = self.path_info
561ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if not path:
562ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            return None
563ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        path = path.lstrip('/')
564ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        return path.split('/', 1)[0]
565ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
566ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def _urlvars__get(self):
567ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
568ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        Return any *named* variables matched in the URL.
569ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
570ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        Takes values from ``environ['wsgiorg.routing_args']``.
571ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        Systems like ``routes`` set this value.
572ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
573ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if 'paste.urlvars' in self.environ:
574ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            return self.environ['paste.urlvars']
575ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        elif 'wsgiorg.routing_args' in self.environ:
576ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            return self.environ['wsgiorg.routing_args'][1]
577ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        else:
578ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            result = {}
579ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            self.environ['wsgiorg.routing_args'] = ((), result)
580ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            return result
581ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
582ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def _urlvars__set(self, value):
583ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        environ = self.environ
584ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if 'wsgiorg.routing_args' in environ:
585ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            environ['wsgiorg.routing_args'] = (
586ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                    environ['wsgiorg.routing_args'][0], value)
587ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            if 'paste.urlvars' in environ:
588ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                del environ['paste.urlvars']
589ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        elif 'paste.urlvars' in environ:
590ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            environ['paste.urlvars'] = value
591ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        else:
592ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            environ['wsgiorg.routing_args'] = ((), value)
593ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
594ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def _urlvars__del(self):
595ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if 'paste.urlvars' in self.environ:
596ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            del self.environ['paste.urlvars']
597ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if 'wsgiorg.routing_args' in self.environ:
598ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            if not self.environ['wsgiorg.routing_args'][0]:
599ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                del self.environ['wsgiorg.routing_args']
600ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            else:
601ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                self.environ['wsgiorg.routing_args'] = (
602ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                        self.environ['wsgiorg.routing_args'][0], {})
603ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
604ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    urlvars = property(_urlvars__get,
605ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                       _urlvars__set,
606ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                       _urlvars__del,
607ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                       doc=_urlvars__get.__doc__)
608ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
609ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def _urlargs__get(self):
610ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
611ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        Return any *positional* variables matched in the URL.
612ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
613ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        Takes values from ``environ['wsgiorg.routing_args']``.
614ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        Systems like ``routes`` set this value.
615ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
616ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if 'wsgiorg.routing_args' in self.environ:
617ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            return self.environ['wsgiorg.routing_args'][0]
618ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        else:
619ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            # Since you can't update this value in-place, we don't need
620ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            # to set the key in the environment
621ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            return ()
622ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
623ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def _urlargs__set(self, value):
624ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        environ = self.environ
625ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if 'paste.urlvars' in environ:
626ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            # Some overlap between this and wsgiorg.routing_args; we need
627ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            # wsgiorg.routing_args to make this work
628ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            routing_args = (value, environ.pop('paste.urlvars'))
629ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        elif 'wsgiorg.routing_args' in environ:
630ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            routing_args = (value, environ['wsgiorg.routing_args'][1])
631ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        else:
632ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            routing_args = (value, {})
633ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        environ['wsgiorg.routing_args'] = routing_args
634ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
635ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def _urlargs__del(self):
636ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if 'wsgiorg.routing_args' in self.environ:
637ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            if not self.environ['wsgiorg.routing_args'][1]:
638ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                del self.environ['wsgiorg.routing_args']
639ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            else:
640ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                self.environ['wsgiorg.routing_args'] = (
641ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                        (), self.environ['wsgiorg.routing_args'][1])
642ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
643ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    urlargs = property(_urlargs__get,
644ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                       _urlargs__set,
645ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                       _urlargs__del,
646ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                       _urlargs__get.__doc__)
647ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
648ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    @property
649ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def is_xhr(self):
650ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """Is X-Requested-With header present and equal to ``XMLHttpRequest``?
651ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
652ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        Note: this isn't set by every XMLHttpRequest request, it is
653ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        only set if you are using a Javascript library that sets it
654ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        (or you set the header yourself manually).  Currently
655ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        Prototype and jQuery are known to set this header."""
656ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        return self.environ.get('HTTP_X_REQUESTED_WITH', '') == 'XMLHttpRequest'
657ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
658ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def _host__get(self):
659ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """Host name provided in HTTP_HOST, with fall-back to SERVER_NAME"""
660ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if 'HTTP_HOST' in self.environ:
661ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            return self.environ['HTTP_HOST']
662ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        else:
663ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            return '%(SERVER_NAME)s:%(SERVER_PORT)s' % self.environ
664ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def _host__set(self, value):
665ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        self.environ['HTTP_HOST'] = value
666ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def _host__del(self):
667ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if 'HTTP_HOST' in self.environ:
668ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            del self.environ['HTTP_HOST']
669ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    host = property(_host__get, _host__set, _host__del, doc=_host__get.__doc__)
670ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
671ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    @property
672ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def domain(self):
673ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """ Returns the domain portion of the host value.  Equivalent to:
674ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
675ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        .. code-block:: python
676ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
677ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik           domain = request.host
678ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik           if ':' in domain:
679ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik               domain = domain.split(':', 1)[0]
680ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
681ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        This will be equivalent to the domain portion of the ``HTTP_HOST``
682ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        value in the environment if it exists, or the ``SERVER_NAME`` value in
683ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        the environment if it doesn't.  For example, if the environment
684ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        contains an ``HTTP_HOST`` value of ``foo.example.com:8000``,
685ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        ``request.domain`` will return ``foo.example.com``.
686ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
687ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        Note that this value cannot be *set* on the request.  To set the host
688ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        value use :meth:`webob.request.Request.host` instead.
689ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
690ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        domain = self.host
691ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if ':' in domain:
692ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik             domain = domain.split(':', 1)[0]
693ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        return domain
694ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
695ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def _body__get(self):
696ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
697ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        Return the content of the request body.
698ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
699ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if not self.is_body_readable:
700ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            return b''
701ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        self.make_body_seekable() # we need this to have content_length
702ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        r = self.body_file.read(self.content_length)
703ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        self.body_file_raw.seek(0)
704ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        return r
705ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def _body__set(self, value):
706ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if value is None:
707ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            value = b''
708ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if not isinstance(value, bytes):
709ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            raise TypeError("You can only set Request.body to bytes (not %r)"
710ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                                % type(value))
711ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if not http_method_probably_has_body.get(self.method, True):
712ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            if not value:
713ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                self.content_length = None
714ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                self.body_file_raw = io.BytesIO()
715ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                return
716ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        self.content_length = len(value)
717ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        self.body_file_raw = io.BytesIO(value)
718ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        self.is_body_seekable = True
719ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def _body__del(self):
720ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        self.body = b''
721ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    body = property(_body__get, _body__set, _body__del, doc=_body__get.__doc__)
722ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
723ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def _json_body__get(self):
724ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """Access the body of the request as JSON"""
725ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        return json.loads(self.body.decode(self.charset))
726ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
727ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def _json_body__set(self, value):
728ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        self.body = json.dumps(value, separators=(',', ':')).encode(self.charset)
729ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
730ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def _json_body__del(self):
731ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        del self.body
732ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
733ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    json = json_body = property(_json_body__get, _json_body__set, _json_body__del)
734ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
735ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def _text__get(self):
736ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
737ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        Get/set the text value of the body
738ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
739ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if not self.charset:
740ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            raise AttributeError(
741ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                "You cannot access Request.text unless charset is set")
742ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        body = self.body
743ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        return body.decode(self.charset)
744ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
745ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def _text__set(self, value):
746ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if not self.charset:
747ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            raise AttributeError(
748ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                "You cannot access Response.text unless charset is set")
749ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if not isinstance(value, text_type):
750ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            raise TypeError(
751ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                "You can only set Request.text to a unicode string "
752ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                "(not %s)" % type(value))
753ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        self.body = value.encode(self.charset)
754ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
755ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def _text__del(self):
756ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        del self.body
757ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
758ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    text = property(_text__get, _text__set, _text__del, doc=_text__get.__doc__)
759ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
760ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
761ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    @property
762ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def POST(self):
763ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
764ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        Return a MultiDict containing all the variables from a form
765ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        request. Returns an empty dict-like object for non-form requests.
766ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
767ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        Form requests are typically POST requests, however PUT & PATCH requests
768ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        with an appropriate Content-Type are also supported.
769ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
770ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        env = self.environ
771ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if self.method not in ('POST', 'PUT', 'PATCH'):
772ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            return NoVars('Not a form request')
773ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if 'webob._parsed_post_vars' in env:
774ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            vars, body_file = env['webob._parsed_post_vars']
775ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            if body_file is self.body_file_raw:
776ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                return vars
777ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        content_type = self.content_type
778ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if ((self.method == 'PUT' and not content_type)
779ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            or content_type not in
780ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                ('',
781ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                 'application/x-www-form-urlencoded',
782ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                 'multipart/form-data')
783ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                 ):
784ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            # Not an HTML form submission
785ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            return NoVars('Not an HTML form submission (Content-Type: %s)'
786ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                          % content_type)
787ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        self._check_charset()
788ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
789ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        self.make_body_seekable()
790ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        self.body_file_raw.seek(0)
791ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
792ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        fs_environ = env.copy()
793ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        # FieldStorage assumes a missing CONTENT_LENGTH, but a
794ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        # default of 0 is better:
795ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        fs_environ.setdefault('CONTENT_LENGTH', '0')
796ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        fs_environ['QUERY_STRING'] = ''
797ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if PY3: # pragma: no cover
798ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            fs = cgi_FieldStorage(
799ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                fp=self.body_file,
800ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                environ=fs_environ,
801ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                keep_blank_values=True,
802ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                encoding='utf8')
803ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            vars = MultiDict.from_fieldstorage(fs)
804ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        else:
805ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            fs = cgi_FieldStorage(
806ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                fp=self.body_file,
807ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                environ=fs_environ,
808ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                keep_blank_values=True)
809ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            vars = MultiDict.from_fieldstorage(fs)
810ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
811ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        env['webob._parsed_post_vars'] = (vars, self.body_file_raw)
812ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        return vars
813ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
814ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    @property
815ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def GET(self):
816ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
817ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        Return a MultiDict containing all the variables from the
818ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        QUERY_STRING.
819ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
820ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        env = self.environ
821ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        source = env.get('QUERY_STRING', '')
822ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if 'webob._parsed_query_vars' in env:
823ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            vars, qs = env['webob._parsed_query_vars']
824ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            if qs == source:
825ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                return vars
826ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
827ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        data = []
828ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if source:
829ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            # this is disabled because we want to access req.GET
830ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            # for text/plain; charset=ascii uploads for example
831ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            #self._check_charset()
832ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            data = parse_qsl_text(source)
833ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            #d = lambda b: b.decode('utf8')
834ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            #data = [(d(k), d(v)) for k,v in data]
835ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        vars = GetDict(data, env)
836ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        env['webob._parsed_query_vars'] = (vars, source)
837ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        return vars
838ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
839ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def _check_charset(self):
840ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if self.charset != 'UTF-8':
841ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            raise DeprecationWarning(
842ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                "Requests are expected to be submitted in UTF-8, not %s. "
843ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                "You can fix this by doing req = req.decode('%s')" % (
844ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                    self.charset, self.charset)
845ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            )
846ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
847ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    @property
848ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def params(self):
849ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
850ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        A dictionary-like object containing both the parameters from
851ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        the query string and request body.
852ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
853ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        params = NestedMultiDict(self.GET, self.POST)
854ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        return params
855ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
856ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
857ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    @property
858ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def cookies(self):
859ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
860ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        Return a dictionary of cookies as found in the request.
861ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
862ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        return RequestCookies(self.environ)
863ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
864ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    @cookies.setter
865ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def cookies(self, val):
866ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        self.environ.pop('HTTP_COOKIE', None)
867ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        r = RequestCookies(self.environ)
868ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        r.update(val)
869ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
870ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def copy(self):
871ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
872ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        Copy the request and environment object.
873ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
874ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        This only does a shallow copy, except of wsgi.input
875ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
876ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        self.make_body_seekable()
877ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        env = self.environ.copy()
878ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        new_req = self.__class__(env)
879ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        new_req.copy_body()
880ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        return new_req
881ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
882ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def copy_get(self):
883ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
884ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        Copies the request and environment object, but turning this request
885ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        into a GET along the way.  If this was a POST request (or any other
886ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        verb) then it becomes GET, and the request body is thrown away.
887ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
888ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        env = self.environ.copy()
889ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        return self.__class__(env, method='GET', content_type=None,
890ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                              body=b'')
891ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
892ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    # webob.is_body_seekable marks input streams that are seekable
893ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    # this way we can have seekable input without testing the .seek() method
894ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    is_body_seekable = environ_getter('webob.is_body_seekable', False)
895ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
896ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    #is_body_readable = environ_getter('webob.is_body_readable', False)
897ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
898ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def _is_body_readable__get(self):
899ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
900ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            webob.is_body_readable is a flag that tells us
901ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            that we can read the input stream even though
902ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            CONTENT_LENGTH is missing. This allows FakeCGIBody
903ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            to work and can be used by servers to support
904ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            chunked encoding in requests.
905ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            For background see https://bitbucket.org/ianb/webob/issue/6
906ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
907ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if http_method_probably_has_body.get(self.method):
908ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            # known HTTP method with body
909ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            return True
910ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        elif self.content_length is not None:
911ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            # unknown HTTP method, but the Content-Length
912ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            # header is present
913ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            return True
914ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        else:
915ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            # last resort -- rely on the special flag
916ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            return self.environ.get('webob.is_body_readable', False)
917ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
918ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def _is_body_readable__set(self, flag):
919ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        self.environ['webob.is_body_readable'] = bool(flag)
920ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
921ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    is_body_readable = property(_is_body_readable__get, _is_body_readable__set,
922ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        doc=_is_body_readable__get.__doc__
923ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    )
924ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
925ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
926ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
927ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def make_body_seekable(self):
928ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
929ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        This forces ``environ['wsgi.input']`` to be seekable.
930ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        That means that, the content is copied into a BytesIO or temporary
931ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        file and flagged as seekable, so that it will not be unnecessarily
932ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        copied again.
933ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
934ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        After calling this method the .body_file is always seeked to the
935ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        start of file and .content_length is not None.
936ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
937ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        The choice to copy to BytesIO is made from
938ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        ``self.request_body_tempfile_limit``
939ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
940ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if self.is_body_seekable:
941ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            self.body_file_raw.seek(0)
942ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        else:
943ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            self.copy_body()
944ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
945ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
946ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def copy_body(self):
947ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
948ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        Copies the body, in cases where it might be shared with
949ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        another request object and that is not desired.
950ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
951ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        This copies the body in-place, either into a BytesIO object
952ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        or a temporary file.
953ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
954ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if not self.is_body_readable:
955ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            # there's no body to copy
956ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            self.body = b''
957ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        elif self.content_length is None:
958ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            # chunked body or FakeCGIBody
959ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            self.body = self.body_file_raw.read()
960ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            self._copy_body_tempfile()
961ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        else:
962ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            # try to read body into tempfile
963ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            did_copy = self._copy_body_tempfile()
964ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            if not did_copy:
965ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                # it wasn't necessary, so just read it into memory
966ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                self.body = self.body_file.read(self.content_length)
967ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
968ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def _copy_body_tempfile(self):
969ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
970ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            Copy wsgi.input to tempfile if necessary. Returns True if it did.
971ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
972ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        tempfile_limit = self.request_body_tempfile_limit
973ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        todo = self.content_length
974ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        assert isinstance(todo, integer_types), todo
975ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if not tempfile_limit or todo <= tempfile_limit:
976ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            return False
977ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        fileobj = self.make_tempfile()
978ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        input = self.body_file
979ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        while todo > 0:
980ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            data = input.read(min(todo, 65536))
981ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            if not data:
982ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                # Normally this should not happen, because LimitedLengthFile
983ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                # should have raised an exception by now.
984ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                # It can happen if the is_body_seekable flag is incorrect.
985ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                raise DisconnectionError(
986ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                    "Client disconnected (%s more bytes were expected)"
987ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                    % todo
988ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                )
989ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            fileobj.write(data)
990ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            todo -= len(data)
991ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        fileobj.seek(0)
992ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        self.body_file_raw = fileobj
993ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        self.is_body_seekable = True
994ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        return True
995ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
996ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def make_tempfile(self):
997ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
998ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            Create a tempfile to store big request body.
999ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            This API is not stable yet. A 'size' argument might be added.
1000ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
1001ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        return tempfile.TemporaryFile()
1002ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1003ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1004ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def remove_conditional_headers(self,
1005ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                                   remove_encoding=True,
1006ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                                   remove_range=True,
1007ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                                   remove_match=True,
1008ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                                   remove_modified=True):
1009ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
1010ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        Remove headers that make the request conditional.
1011ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1012ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        These headers can cause the response to be 304 Not Modified,
1013ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        which in some cases you may not want to be possible.
1014ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1015ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        This does not remove headers like If-Match, which are used for
1016ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        conflict detection.
1017ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
1018ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        check_keys = []
1019ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if remove_range:
1020ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            check_keys += ['HTTP_IF_RANGE', 'HTTP_RANGE']
1021ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if remove_match:
1022ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            check_keys.append('HTTP_IF_NONE_MATCH')
1023ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if remove_modified:
1024ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            check_keys.append('HTTP_IF_MODIFIED_SINCE')
1025ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if remove_encoding:
1026ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            check_keys.append('HTTP_ACCEPT_ENCODING')
1027ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1028ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        for key in check_keys:
1029ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            if key in self.environ:
1030ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                del self.environ[key]
1031ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1032ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1033ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    accept = accept_property('Accept', '14.1', MIMEAccept, MIMENilAccept)
1034ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    accept_charset = accept_property('Accept-Charset', '14.2', AcceptCharset)
1035ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    accept_encoding = accept_property('Accept-Encoding', '14.3',
1036ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                                      NilClass=NoAccept)
1037ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    accept_language = accept_property('Accept-Language', '14.4', AcceptLanguage)
1038ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1039ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    authorization = converter(
1040ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        environ_getter('HTTP_AUTHORIZATION', None, '14.8'),
1041ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        parse_auth, serialize_auth,
1042ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    )
1043ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1044ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1045ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def _cache_control__get(self):
1046ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
1047ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        Get/set/modify the Cache-Control header (`HTTP spec section 14.9
1048ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9>`_)
1049ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
1050ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        env = self.environ
1051ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        value = env.get('HTTP_CACHE_CONTROL', '')
1052ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        cache_header, cache_obj = env.get('webob._cache_control', (None, None))
1053ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if cache_obj is not None and cache_header == value:
1054ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            return cache_obj
1055ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        cache_obj = CacheControl.parse(value,
1056ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                                       updates_to=self._update_cache_control,
1057ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                                       type='request')
1058ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        env['webob._cache_control'] = (value, cache_obj)
1059ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        return cache_obj
1060ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1061ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def _cache_control__set(self, value):
1062ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        env = self.environ
1063ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        value = value or ''
1064ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if isinstance(value, dict):
1065ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            value = CacheControl(value, type='request')
1066ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if isinstance(value, CacheControl):
1067ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            str_value = str(value)
1068ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            env['HTTP_CACHE_CONTROL'] = str_value
1069ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            env['webob._cache_control'] = (str_value, value)
1070ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        else:
1071ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            env['HTTP_CACHE_CONTROL'] = str(value)
1072ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            env['webob._cache_control'] = (None, None)
1073ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1074ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def _cache_control__del(self):
1075ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        env = self.environ
1076ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if 'HTTP_CACHE_CONTROL' in env:
1077ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            del env['HTTP_CACHE_CONTROL']
1078ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if 'webob._cache_control' in env:
1079ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            del env['webob._cache_control']
1080ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1081ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def _update_cache_control(self, prop_dict):
1082ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        self.environ['HTTP_CACHE_CONTROL'] = serialize_cache_control(prop_dict)
1083ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1084ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    cache_control = property(_cache_control__get,
1085ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                             _cache_control__set,
1086ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                             _cache_control__del,
1087ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                             doc=_cache_control__get.__doc__)
1088ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1089ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1090ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    if_match = etag_property('HTTP_IF_MATCH', AnyETag, '14.24')
1091ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    if_none_match = etag_property('HTTP_IF_NONE_MATCH', NoETag, '14.26',
1092ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                                  strong=False)
1093ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1094ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    date = converter_date(environ_getter('HTTP_DATE', None, '14.8'))
1095ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    if_modified_since = converter_date(
1096ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                    environ_getter('HTTP_IF_MODIFIED_SINCE', None, '14.25'))
1097ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    if_unmodified_since = converter_date(
1098ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                    environ_getter('HTTP_IF_UNMODIFIED_SINCE', None, '14.28'))
1099ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    if_range = converter(
1100ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        environ_getter('HTTP_IF_RANGE', None, '14.27'),
1101ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        IfRange.parse, serialize_if_range, 'IfRange object')
1102ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1103ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1104ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    max_forwards = converter(
1105ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        environ_getter('HTTP_MAX_FORWARDS', None, '14.31'),
1106ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        parse_int, serialize_int, 'int')
1107ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1108ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    pragma = environ_getter('HTTP_PRAGMA', None, '14.32')
1109ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1110ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    range = converter(
1111ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        environ_getter('HTTP_RANGE', None, '14.35'),
1112ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        parse_range, serialize_range, 'Range object')
1113ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1114ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    referer = environ_getter('HTTP_REFERER', None, '14.36')
1115ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    referrer = referer
1116ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1117ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    user_agent = environ_getter('HTTP_USER_AGENT', None, '14.43')
1118ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1119ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def __repr__(self):
1120ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        try:
1121ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            name = '%s %s' % (self.method, self.url)
1122ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        except KeyError:
1123ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            name = '(invalid WSGI environ)'
1124ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        msg = '<%s at 0x%x %s>' % (
1125ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            self.__class__.__name__,
1126ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            abs(id(self)), name)
1127ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        return msg
1128ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1129ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def as_bytes(self, skip_body=False):
1130ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
1131ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            Return HTTP bytes representing this request.
1132ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            If skip_body is True, exclude the body.
1133ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            If skip_body is an integer larger than one, skip body
1134ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            only if its length is bigger than that number.
1135ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
1136ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        url = self.url
1137ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        host = self.host_url
1138ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        assert url.startswith(host)
1139ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        url = url[len(host):]
1140ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        parts = [bytes_('%s %s %s' % (self.method, url, self.http_version))]
1141ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        #self.headers.setdefault('Host', self.host)
1142ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1143ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        # acquire body before we handle headers so that
1144ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        # content-length will be set
1145ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        body = None
1146ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if http_method_probably_has_body.get(self.method):
1147ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            if skip_body > 1:
1148ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                if len(self.body) > skip_body:
1149ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                    body = bytes_('<body skipped (len=%s)>' % len(self.body))
1150ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                else:
1151ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                    skip_body = False
1152ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            if not skip_body:
1153ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                body = self.body
1154ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1155ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        for k, v in sorted(self.headers.items()):
1156ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            header = bytes_('%s: %s'  % (k, v))
1157ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            parts.append(header)
1158ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1159ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if body:
1160ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            parts.extend([b'', body])
1161ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        # HTTP clearly specifies CRLF
1162ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        return b'\r\n'.join(parts)
1163ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1164ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def as_string(self, skip_body=False):
1165ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        # TODO: Remove in 1.4
1166ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        warn_deprecation(
1167ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            "Please use req.as_bytes",
1168ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            '1.3',
1169ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            self._setattr_stacklevel
1170ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            )
1171ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1172ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def as_text(self):
1173ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        bytes = self.as_bytes()
1174ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        return bytes.decode(self.charset)
1175ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1176ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    __str__ = as_text
1177ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1178ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    @classmethod
1179ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def from_bytes(cls, b):
1180ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
1181ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            Create a request from HTTP bytes data. If the bytes contain
1182ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            extra data after the request, raise a ValueError.
1183ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
1184ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        f = io.BytesIO(b)
1185ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        r = cls.from_file(f)
1186ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if f.tell() != len(b):
1187ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            raise ValueError("The string contains more data than expected")
1188ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        return r
1189ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1190ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    @classmethod
1191ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def from_string(cls, b):
1192ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        # TODO: Remove in 1.4
1193ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        warn_deprecation(
1194ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            "Please use req.from_bytes",
1195ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            '1.3',
1196ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            cls._setattr_stacklevel
1197ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            )
1198ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1199ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    @classmethod
1200ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def from_text(cls, s):
1201ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        b = bytes_(s, 'utf-8')
1202ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        return cls.from_bytes(b)
1203ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1204ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    @classmethod
1205ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def from_file(cls, fp):
1206ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """Read a request from a file-like object (it must implement
1207ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        ``.read(size)`` and ``.readline()``).
1208ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1209ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        It will read up to the end of the request, not the end of the
1210ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        file (unless the request is a POST or PUT and has no
1211ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        Content-Length, in that case, the entire file is read).
1212ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1213ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        This reads the request as represented by ``str(req)``; it may
1214ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        not read every valid HTTP request properly.
1215ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
1216ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        start_line = fp.readline()
1217ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        is_text = isinstance(start_line, text_type)
1218ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if is_text:
1219ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            crlf = '\r\n'
1220ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            colon = ':'
1221ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        else:
1222ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            crlf = b'\r\n'
1223ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            colon = b':'
1224ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        try:
1225ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            header = start_line.rstrip(crlf)
1226ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            method, resource, http_version = header.split(None, 2)
1227ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            method = native_(method, 'utf-8')
1228ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            resource = native_(resource, 'utf-8')
1229ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            http_version = native_(http_version, 'utf-8')
1230ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        except ValueError:
1231ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            raise ValueError('Bad HTTP request line: %r' % start_line)
1232ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        r = cls(environ_from_url(resource),
1233ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                http_version=http_version,
1234ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                method=method.upper()
1235ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                )
1236ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        del r.environ['HTTP_HOST']
1237ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        while 1:
1238ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            line = fp.readline()
1239ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            if not line.strip():
1240ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                # end of headers
1241ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                break
1242ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            hname, hval = line.split(colon, 1)
1243ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            hname = native_(hname, 'utf-8')
1244ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            hval = native_(hval, 'utf-8').strip()
1245ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            if hname in r.headers:
1246ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                hval = r.headers[hname] + ', ' + hval
1247ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            r.headers[hname] = hval
1248ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if http_method_probably_has_body.get(r.method):
1249ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            clen = r.content_length
1250ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            if clen is None:
1251ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                body = fp.read()
1252ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            else:
1253ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                body = fp.read(clen)
1254ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            if is_text:
1255ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                body = bytes_(body, 'utf-8')
1256ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            r.body = body
1257ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        return r
1258ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1259ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def call_application(self, application, catch_exc_info=False):
1260ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
1261ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        Call the given WSGI application, returning ``(status_string,
1262ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        headerlist, app_iter)``
1263ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1264ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        Be sure to call ``app_iter.close()`` if it's there.
1265ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1266ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        If catch_exc_info is true, then returns ``(status_string,
1267ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        headerlist, app_iter, exc_info)``, where the fourth item may
1268ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        be None, but won't be if there was an exception.  If you don't
1269ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        do this and there was an exception, the exception will be
1270ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        raised directly.
1271ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
1272ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if self.is_body_seekable:
1273ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            self.body_file_raw.seek(0)
1274ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        captured = []
1275ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        output = []
1276ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        def start_response(status, headers, exc_info=None):
1277ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            if exc_info is not None and not catch_exc_info:
1278ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                reraise(exc_info)
1279ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            captured[:] = [status, headers, exc_info]
1280ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            return output.append
1281ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        app_iter = application(self.environ, start_response)
1282ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if output or not captured:
1283ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            try:
1284ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                output.extend(app_iter)
1285ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            finally:
1286ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                if hasattr(app_iter, 'close'):
1287ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                    app_iter.close()
1288ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            app_iter = output
1289ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if catch_exc_info:
1290ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            return (captured[0], captured[1], app_iter, captured[2])
1291ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        else:
1292ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            return (captured[0], captured[1], app_iter)
1293ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1294ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    # Will be filled in later:
1295ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    ResponseClass = None
1296ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1297ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def send(self, application=None, catch_exc_info=False):
1298ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
1299ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        Like ``.call_application(application)``, except returns a
1300ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        response object with ``.status``, ``.headers``, and ``.body``
1301ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        attributes.
1302ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1303ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        This will use ``self.ResponseClass`` to figure out the class
1304ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        of the response object to return.
1305ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1306ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        If ``application`` is not given, this will send the request to
1307ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        ``self.make_default_send_app()``
1308ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
1309ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if application is None:
1310ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            application = self.make_default_send_app()
1311ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if catch_exc_info:
1312ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            status, headers, app_iter, exc_info = self.call_application(
1313ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                application, catch_exc_info=True)
1314ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            del exc_info
1315ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        else:
1316ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            status, headers, app_iter = self.call_application(
1317ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                application, catch_exc_info=False)
1318ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        return self.ResponseClass(
1319ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            status=status, headerlist=list(headers), app_iter=app_iter)
1320ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1321ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    get_response = send
1322ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1323ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def make_default_send_app(self):
1324ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        global _client
1325ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        try:
1326ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            client = _client
1327ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        except NameError:
1328ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            from webob import client
1329ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            _client = client
1330ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        return client.send_request_app
1331ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1332ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    @classmethod
1333ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def blank(cls, path, environ=None, base_url=None,
1334ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik              headers=None, POST=None, **kw):
1335ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
1336ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        Create a blank request environ (and Request wrapper) with the
1337ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        given path (path should be urlencoded), and any keys from
1338ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        environ.
1339ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1340ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        The path will become path_info, with any query string split
1341ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        off and used.
1342ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1343ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        All necessary keys will be added to the environ, but the
1344ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        values you pass in will take precedence.  If you pass in
1345ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        base_url then wsgi.url_scheme, HTTP_HOST, and SCRIPT_NAME will
1346ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        be filled in from that value.
1347ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1348ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        Any extra keyword will be passed to ``__init__``.
1349ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        """
1350ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        env = environ_from_url(path)
1351ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if base_url:
1352ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            scheme, netloc, path, query, fragment = urlparse.urlsplit(base_url)
1353ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            if query or fragment:
1354ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                raise ValueError(
1355ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                    "base_url (%r) cannot have a query or fragment"
1356ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                    % base_url)
1357ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            if scheme:
1358ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                env['wsgi.url_scheme'] = scheme
1359ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            if netloc:
1360ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                if ':' not in netloc:
1361ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                    if scheme == 'http':
1362ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                        netloc += ':80'
1363ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                    elif scheme == 'https':
1364ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                        netloc += ':443'
1365ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                    else:
1366ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                        raise ValueError(
1367ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                            "Unknown scheme: %r" % scheme)
1368ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                host, port = netloc.split(':', 1)
1369ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                env['SERVER_PORT'] = port
1370ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                env['SERVER_NAME'] = host
1371ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                env['HTTP_HOST'] = netloc
1372ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            if path:
1373ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                env['SCRIPT_NAME'] = url_unquote(path)
1374ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if environ:
1375ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            env.update(environ)
1376ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        content_type = kw.get('content_type', env.get('CONTENT_TYPE'))
1377ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if headers and 'Content-Type' in headers:
1378ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            content_type = headers['Content-Type']
1379ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if content_type is not None:
1380ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            kw['content_type'] = content_type
1381ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        environ_add_POST(env, POST, content_type=content_type)
1382ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        obj = cls(env, **kw)
1383ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if headers is not None:
1384ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            obj.headers.update(headers)
1385ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        return obj
1386ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1387ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craikclass LegacyRequest(BaseRequest):
1388ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    uscript_name = upath_property('SCRIPT_NAME')
1389ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    upath_info = upath_property('PATH_INFO')
1390ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1391ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def encget(self, key, default=NoDefault, encattr=None):
1392ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        val = self.environ.get(key, default)
1393ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if val is NoDefault:
1394ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            raise KeyError(key)
1395ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if val is default:
1396ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            return default
1397ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        return val
1398ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1399ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craikclass AdhocAttrMixin(object):
1400ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    _setattr_stacklevel = 3
1401ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1402ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def __setattr__(self, attr, value, DEFAULT=object()):
1403ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if (getattr(self.__class__, attr, DEFAULT) is not DEFAULT or
1404ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                    attr.startswith('_')):
1405ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            object.__setattr__(self, attr, value)
1406ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        else:
1407ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            self.environ.setdefault('webob.adhoc_attrs', {})[attr] = value
1408ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1409ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def __getattr__(self, attr, DEFAULT=object()):
1410ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        try:
1411ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            return self.environ['webob.adhoc_attrs'][attr]
1412ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        except KeyError:
1413ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            raise AttributeError(attr)
1414ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1415ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def __delattr__(self, attr, DEFAULT=object()):
1416ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if getattr(self.__class__, attr, DEFAULT) is not DEFAULT:
1417ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            return object.__delattr__(self, attr)
1418ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        try:
1419ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            del self.environ['webob.adhoc_attrs'][attr]
1420ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        except KeyError:
1421ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            raise AttributeError(attr)
1422ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1423ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craikclass Request(AdhocAttrMixin, BaseRequest):
1424ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    """ The default request implementation """
1425ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1426ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craikdef environ_from_url(path):
1427ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    if SCHEME_RE.search(path):
1428ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        scheme, netloc, path, qs, fragment = urlparse.urlsplit(path)
1429ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if fragment:
1430ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            raise TypeError("Path cannot contain a fragment (%r)" % fragment)
1431ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if qs:
1432ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            path += '?' + qs
1433ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if ':' not in netloc:
1434ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            if scheme == 'http':
1435ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                netloc += ':80'
1436ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            elif scheme == 'https':
1437ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                netloc += ':443'
1438ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            else:
1439ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                raise TypeError("Unknown scheme: %r" % scheme)
1440ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    else:
1441ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        scheme = 'http'
1442ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        netloc = 'localhost:80'
1443ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    if path and '?' in path:
1444ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        path_info, query_string = path.split('?', 1)
1445ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        path_info = url_unquote(path_info)
1446ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    else:
1447ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        path_info = url_unquote(path)
1448ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        query_string = ''
1449ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    env = {
1450ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        'REQUEST_METHOD': 'GET',
1451ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        'SCRIPT_NAME': '',
1452ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        'PATH_INFO': path_info or '',
1453ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        'QUERY_STRING': query_string,
1454ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        'SERVER_NAME': netloc.split(':')[0],
1455ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        'SERVER_PORT': netloc.split(':')[1],
1456ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        'HTTP_HOST': netloc,
1457ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        'SERVER_PROTOCOL': 'HTTP/1.0',
1458ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        'wsgi.version': (1, 0),
1459ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        'wsgi.url_scheme': scheme,
1460ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        'wsgi.input': io.BytesIO(),
1461ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        'wsgi.errors': sys.stderr,
1462ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        'wsgi.multithread': False,
1463ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        'wsgi.multiprocess': False,
1464ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        'wsgi.run_once': False,
1465ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        #'webob.is_body_seekable': True,
1466ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    }
1467ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    return env
1468ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1469ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1470ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craikdef environ_add_POST(env, data, content_type=None):
1471ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    if data is None:
1472ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        return
1473ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    elif isinstance(data, text_type): # pragma: no cover
1474ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        data = data.encode('ascii')
1475ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    if env['REQUEST_METHOD'] not in ('POST', 'PUT'):
1476ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        env['REQUEST_METHOD'] = 'POST'
1477ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    has_files = False
1478ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    if hasattr(data, 'items'):
1479ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        data = list(data.items())
1480ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        for k, v in data:
1481ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            if isinstance(v, (tuple, list)):
1482ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                has_files = True
1483ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                break
1484ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    if content_type is None:
1485ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if has_files:
1486ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            content_type = 'multipart/form-data'
1487ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        else:
1488ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            content_type = 'application/x-www-form-urlencoded'
1489ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    if content_type.startswith('multipart/form-data'):
1490ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if not isinstance(data, bytes):
1491ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            content_type, data = _encode_multipart(data, content_type)
1492ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    elif content_type.startswith('application/x-www-form-urlencoded'):
1493ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if has_files:
1494ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            raise ValueError('Submiting files is not allowed for'
1495ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                             ' content type `%s`' % content_type)
1496ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if not isinstance(data, bytes):
1497ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            data = url_encode(data)
1498ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    else:
1499ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if not isinstance(data, bytes):
1500ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            raise ValueError('Please provide `POST` data as string'
1501ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                             ' for content type `%s`' % content_type)
1502ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    data = bytes_(data, 'utf8')
1503ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    env['wsgi.input'] = io.BytesIO(data)
1504ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    env['webob.is_body_seekable'] = True
1505ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    env['CONTENT_LENGTH'] = str(len(data))
1506ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    env['CONTENT_TYPE'] = content_type
1507ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1508ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1509ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1510ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik#########################
1511ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik## Helper classes and monkeypatching
1512ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik#########################
1513ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1514ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craikclass DisconnectionError(IOError):
1515ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    pass
1516ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1517ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1518ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craikclass LimitedLengthFile(io.RawIOBase):
1519ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def __init__(self, file, maxlen):
1520ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        self.file = file
1521ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        self.maxlen = maxlen
1522ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        self.remaining = maxlen
1523ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1524ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def __repr__(self):
1525ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        return '<%s(%r, maxlen=%s)>' % (
1526ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            self.__class__.__name__,
1527ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            self.file,
1528ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            self.maxlen
1529ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        )
1530ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1531ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def fileno(self):
1532ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        return self.file.fileno()
1533ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1534ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    @staticmethod
1535ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def readable():
1536ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        return True
1537ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1538ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def readinto(self, buff):
1539ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if not self.remaining:
1540ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            return 0
1541ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        sz0 = min(len(buff), self.remaining)
1542ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        data = self.file.read(sz0)
1543ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        sz = len(data)
1544ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        self.remaining -= sz
1545ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        #if not data:
1546ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if sz < sz0 and self.remaining:
1547ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            raise DisconnectionError(
1548ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                "The client disconnected while sending the POST/PUT body "
1549ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                + "(%d more bytes were expected)" % self.remaining
1550ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            )
1551ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        buff[:sz] = data
1552ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        return sz
1553ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1554ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1555ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craikdef _cgi_FieldStorage__repr__patch(self):
1556ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    """ monkey patch for FieldStorage.__repr__
1557ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1558ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    Unbelievably, the default __repr__ on FieldStorage reads
1559ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    the entire file content instead of being sane about it.
1560ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    This is a simple replacement that doesn't do that
1561ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    """
1562ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    if self.file:
1563ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        return "FieldStorage(%r, %r)" % (self.name, self.filename)
1564ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    return "FieldStorage(%r, %r, %r)" % (self.name, self.filename, self.value)
1565ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1566ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craikcgi_FieldStorage.__repr__ = _cgi_FieldStorage__repr__patch
1567ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1568ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craikclass FakeCGIBody(io.RawIOBase):
1569ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def __init__(self, vars, content_type):
1570ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if content_type.startswith('multipart/form-data'):
1571ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            if not _get_multipart_boundary(content_type):
1572ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                raise ValueError('Content-type: %r does not contain boundary'
1573ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                            % content_type)
1574ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        self.vars = vars
1575ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        self.content_type = content_type
1576ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        self.file = None
1577ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1578ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def __repr__(self):
1579ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        inner = repr(self.vars)
1580ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if len(inner) > 20:
1581ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            inner = inner[:15] + '...' + inner[-5:]
1582ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        return '<%s at 0x%x viewing %s>' % (
1583ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            self.__class__.__name__,
1584ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            abs(id(self)), inner)
1585ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1586ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def fileno(self):
1587ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        return None
1588ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1589ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    @staticmethod
1590ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def readable():
1591ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        return True
1592ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1593ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def readinto(self, buff):
1594ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if self.file is None:
1595ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            if self.content_type.startswith(
1596ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                'application/x-www-form-urlencoded'):
1597ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                data = '&'.join(
1598ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                    '%s=%s' % (quote_plus(bytes_(k, 'utf8')), quote_plus(bytes_(v, 'utf8')))
1599ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                    for k,v in self.vars.items()
1600ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                )
1601ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                self.file = io.BytesIO(bytes_(data))
1602ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            elif self.content_type.startswith('multipart/form-data'):
1603ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                self.file = _encode_multipart(
1604ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                    self.vars.items(),
1605ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                    self.content_type,
1606ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                    fout=io.BytesIO()
1607ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                )[1]
1608ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                self.file.seek(0)
1609ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            else:
1610ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                assert 0, ('Bad content type: %r' % self.content_type)
1611ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        return self.file.readinto(buff)
1612ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1613ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1614ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craikdef _get_multipart_boundary(ctype):
1615ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    m = re.search(r'boundary=([^ ]+)', ctype, re.I)
1616ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    if m:
1617ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        return native_(m.group(1).strip('"'))
1618ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1619ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1620ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craikdef _encode_multipart(vars, content_type, fout=None):
1621ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    """Encode a multipart request body into a string"""
1622ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    f = fout or io.BytesIO()
1623ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    w = f.write
1624ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    wt = lambda t: f.write(t.encode('utf8'))
1625ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    CRLF = b'\r\n'
1626ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    boundary = _get_multipart_boundary(content_type)
1627ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    if not boundary:
1628ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        boundary = native_(binascii.hexlify(os.urandom(10)))
1629ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        content_type += ('; boundary=%s' % boundary)
1630ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    for name, value in vars:
1631ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        w(b'--')
1632ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        wt(boundary)
1633ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        w(CRLF)
1634ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        assert name is not None, 'Value associated with no name: %r' % value
1635ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        wt('Content-Disposition: form-data; name="%s"' % name)
1636ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        filename = None
1637ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if getattr(value, 'filename', None):
1638ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            filename = value.filename
1639ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        elif isinstance(value, (list, tuple)):
1640ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            filename, value = value
1641ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            if hasattr(value, 'read'):
1642ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                value = value.read()
1643ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1644ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if filename is not None:
1645ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            wt('; filename="%s"' % filename)
1646ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            mime_type = mimetypes.guess_type(filename)[0]
1647ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        else:
1648ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            mime_type = None
1649ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1650ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        w(CRLF)
1651ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1652ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        # TODO: should handle value.disposition_options
1653ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if getattr(value, 'type', None):
1654ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            wt('Content-type: %s' % value.type)
1655ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            if value.type_options:
1656ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                for ct_name, ct_value in sorted(value.type_options.items()):
1657ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                    wt('; %s="%s"' % (ct_name, ct_value))
1658ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            w(CRLF)
1659ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        elif mime_type:
1660ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            wt('Content-type: %s' % mime_type)
1661ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            w(CRLF)
1662ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        w(CRLF)
1663ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if hasattr(value, 'value'):
1664ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            value = value.value
1665ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if isinstance(value, bytes):
1666ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            w(value)
1667ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        else:
1668ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            wt(value)
1669ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        w(CRLF)
1670ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    wt('--%s--' % boundary)
1671ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    if fout:
1672ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        return content_type, fout
1673ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    else:
1674ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        return content_type, f.getvalue()
1675ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1676ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craikdef detect_charset(ctype):
1677ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    m = CHARSET_RE.search(ctype)
1678ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    if m:
1679ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        return m.group(1).strip('"').strip()
1680ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1681ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craikdef _is_utf8(charset):
1682ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    if not charset:
1683ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        return True
1684ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    else:
1685ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        return charset.lower().replace('-', '') == 'utf8'
1686ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1687ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1688ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craikclass Transcoder(object):
1689ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def __init__(self, charset, errors='strict'):
1690ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        self.charset = charset # source charset
1691ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        self.errors = errors # unicode errors
1692ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        self._trans = lambda b: b.decode(charset, errors).encode('utf8')
1693ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1694ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def transcode_query(self, q):
1695ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if PY3: # pragma: no cover
1696ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            q_orig = q
1697ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            if '=' not in q:
1698ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                # this doesn't look like a form submission
1699ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                return q_orig
1700ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            q = list(parse_qsl_text(q, self.charset))
1701ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            return url_encode(q)
1702ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        else:
1703ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            q_orig = q
1704ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            if '=' not in q:
1705ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                # this doesn't look like a form submission
1706ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                return q_orig
1707ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            q = urlparse.parse_qsl(q, self.charset)
1708ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            t = self._trans
1709ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            q = [(t(k), t(v)) for k,v in q]
1710ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            return url_encode(q)
1711ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1712ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    def transcode_fs(self, fs, content_type):
1713ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        # transcode FieldStorage
1714ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        if PY3: # pragma: no cover
1715ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            decode = lambda b: b
1716ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        else:
1717ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            decode = lambda b: b.decode(self.charset, self.errors)
1718ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        data = []
1719ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        for field in fs.list or ():
1720ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            field.name = decode(field.name)
1721ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            if field.filename:
1722ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                field.filename = decode(field.filename)
1723ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                data.append((field.name, field))
1724ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            else:
1725ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik                data.append((field.name, decode(field.value)))
1726ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1727ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        # TODO: transcode big requests to temp file
1728ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        content_type, fout = _encode_multipart(
1729ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            data,
1730ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            content_type,
1731ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik            fout=io.BytesIO()
1732ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        )
1733ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        return fout
1734ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik
1735ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik# TODO: remove in 1.4
1736ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craikfor _name in 'GET POST params cookies'.split():
1737ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    _str_name = 'str_'+_name
1738ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    _prop = deprecated_property(
1739ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        None, _str_name,
1740ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik        "disabled starting WebOb 1.2, use %s instead" % _name, '1.2')
1741ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik    setattr(BaseRequest, _str_name, _prop)
1742