xref: /aosp_15_r20/prebuilts/build-tools/common/py3-stdlib/http/cookiejar.py (revision cda5da8d549138a6648c5ee6d7a49cf8f4a657be)
1*cda5da8dSAndroid Build Coastguard Workerr"""HTTP cookie handling for web clients.
2*cda5da8dSAndroid Build Coastguard Worker
3*cda5da8dSAndroid Build Coastguard WorkerThis module has (now fairly distant) origins in Gisle Aas' Perl module
4*cda5da8dSAndroid Build Coastguard WorkerHTTP::Cookies, from the libwww-perl library.
5*cda5da8dSAndroid Build Coastguard Worker
6*cda5da8dSAndroid Build Coastguard WorkerDocstrings, comments and debug strings in this code refer to the
7*cda5da8dSAndroid Build Coastguard Workerattributes of the HTTP cookie system as cookie-attributes, to distinguish
8*cda5da8dSAndroid Build Coastguard Workerthem clearly from Python attributes.
9*cda5da8dSAndroid Build Coastguard Worker
10*cda5da8dSAndroid Build Coastguard WorkerClass diagram (note that BSDDBCookieJar and the MSIE* classes are not
11*cda5da8dSAndroid Build Coastguard Workerdistributed with the Python standard library, but are available from
12*cda5da8dSAndroid Build Coastguard Workerhttp://wwwsearch.sf.net/):
13*cda5da8dSAndroid Build Coastguard Worker
14*cda5da8dSAndroid Build Coastguard Worker                        CookieJar____
15*cda5da8dSAndroid Build Coastguard Worker                        /     \      \
16*cda5da8dSAndroid Build Coastguard Worker            FileCookieJar      \      \
17*cda5da8dSAndroid Build Coastguard Worker             /    |   \         \      \
18*cda5da8dSAndroid Build Coastguard Worker MozillaCookieJar | LWPCookieJar \      \
19*cda5da8dSAndroid Build Coastguard Worker                  |               |      \
20*cda5da8dSAndroid Build Coastguard Worker                  |   ---MSIEBase |       \
21*cda5da8dSAndroid Build Coastguard Worker                  |  /      |     |        \
22*cda5da8dSAndroid Build Coastguard Worker                  | /   MSIEDBCookieJar BSDDBCookieJar
23*cda5da8dSAndroid Build Coastguard Worker                  |/
24*cda5da8dSAndroid Build Coastguard Worker               MSIECookieJar
25*cda5da8dSAndroid Build Coastguard Worker
26*cda5da8dSAndroid Build Coastguard Worker"""
27*cda5da8dSAndroid Build Coastguard Worker
28*cda5da8dSAndroid Build Coastguard Worker__all__ = ['Cookie', 'CookieJar', 'CookiePolicy', 'DefaultCookiePolicy',
29*cda5da8dSAndroid Build Coastguard Worker           'FileCookieJar', 'LWPCookieJar', 'LoadError', 'MozillaCookieJar']
30*cda5da8dSAndroid Build Coastguard Worker
31*cda5da8dSAndroid Build Coastguard Workerimport os
32*cda5da8dSAndroid Build Coastguard Workerimport copy
33*cda5da8dSAndroid Build Coastguard Workerimport datetime
34*cda5da8dSAndroid Build Coastguard Workerimport re
35*cda5da8dSAndroid Build Coastguard Workerimport time
36*cda5da8dSAndroid Build Coastguard Workerimport urllib.parse, urllib.request
37*cda5da8dSAndroid Build Coastguard Workerimport threading as _threading
38*cda5da8dSAndroid Build Coastguard Workerimport http.client  # only for the default HTTP port
39*cda5da8dSAndroid Build Coastguard Workerfrom calendar import timegm
40*cda5da8dSAndroid Build Coastguard Worker
41*cda5da8dSAndroid Build Coastguard Workerdebug = False   # set to True to enable debugging via the logging module
42*cda5da8dSAndroid Build Coastguard Workerlogger = None
43*cda5da8dSAndroid Build Coastguard Worker
44*cda5da8dSAndroid Build Coastguard Workerdef _debug(*args):
45*cda5da8dSAndroid Build Coastguard Worker    if not debug:
46*cda5da8dSAndroid Build Coastguard Worker        return
47*cda5da8dSAndroid Build Coastguard Worker    global logger
48*cda5da8dSAndroid Build Coastguard Worker    if not logger:
49*cda5da8dSAndroid Build Coastguard Worker        import logging
50*cda5da8dSAndroid Build Coastguard Worker        logger = logging.getLogger("http.cookiejar")
51*cda5da8dSAndroid Build Coastguard Worker    return logger.debug(*args)
52*cda5da8dSAndroid Build Coastguard Worker
53*cda5da8dSAndroid Build Coastguard WorkerHTTPONLY_ATTR = "HTTPOnly"
54*cda5da8dSAndroid Build Coastguard WorkerHTTPONLY_PREFIX = "#HttpOnly_"
55*cda5da8dSAndroid Build Coastguard WorkerDEFAULT_HTTP_PORT = str(http.client.HTTP_PORT)
56*cda5da8dSAndroid Build Coastguard WorkerNETSCAPE_MAGIC_RGX = re.compile("#( Netscape)? HTTP Cookie File")
57*cda5da8dSAndroid Build Coastguard WorkerMISSING_FILENAME_TEXT = ("a filename was not supplied (nor was the CookieJar "
58*cda5da8dSAndroid Build Coastguard Worker                         "instance initialised with one)")
59*cda5da8dSAndroid Build Coastguard WorkerNETSCAPE_HEADER_TEXT =  """\
60*cda5da8dSAndroid Build Coastguard Worker# Netscape HTTP Cookie File
61*cda5da8dSAndroid Build Coastguard Worker# http://curl.haxx.se/rfc/cookie_spec.html
62*cda5da8dSAndroid Build Coastguard Worker# This is a generated file!  Do not edit.
63*cda5da8dSAndroid Build Coastguard Worker
64*cda5da8dSAndroid Build Coastguard Worker"""
65*cda5da8dSAndroid Build Coastguard Worker
66*cda5da8dSAndroid Build Coastguard Workerdef _warn_unhandled_exception():
67*cda5da8dSAndroid Build Coastguard Worker    # There are a few catch-all except: statements in this module, for
68*cda5da8dSAndroid Build Coastguard Worker    # catching input that's bad in unexpected ways.  Warn if any
69*cda5da8dSAndroid Build Coastguard Worker    # exceptions are caught there.
70*cda5da8dSAndroid Build Coastguard Worker    import io, warnings, traceback
71*cda5da8dSAndroid Build Coastguard Worker    f = io.StringIO()
72*cda5da8dSAndroid Build Coastguard Worker    traceback.print_exc(None, f)
73*cda5da8dSAndroid Build Coastguard Worker    msg = f.getvalue()
74*cda5da8dSAndroid Build Coastguard Worker    warnings.warn("http.cookiejar bug!\n%s" % msg, stacklevel=2)
75*cda5da8dSAndroid Build Coastguard Worker
76*cda5da8dSAndroid Build Coastguard Worker
77*cda5da8dSAndroid Build Coastguard Worker# Date/time conversion
78*cda5da8dSAndroid Build Coastguard Worker# -----------------------------------------------------------------------------
79*cda5da8dSAndroid Build Coastguard Worker
80*cda5da8dSAndroid Build Coastguard WorkerEPOCH_YEAR = 1970
81*cda5da8dSAndroid Build Coastguard Workerdef _timegm(tt):
82*cda5da8dSAndroid Build Coastguard Worker    year, month, mday, hour, min, sec = tt[:6]
83*cda5da8dSAndroid Build Coastguard Worker    if ((year >= EPOCH_YEAR) and (1 <= month <= 12) and (1 <= mday <= 31) and
84*cda5da8dSAndroid Build Coastguard Worker        (0 <= hour <= 24) and (0 <= min <= 59) and (0 <= sec <= 61)):
85*cda5da8dSAndroid Build Coastguard Worker        return timegm(tt)
86*cda5da8dSAndroid Build Coastguard Worker    else:
87*cda5da8dSAndroid Build Coastguard Worker        return None
88*cda5da8dSAndroid Build Coastguard Worker
89*cda5da8dSAndroid Build Coastguard WorkerDAYS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
90*cda5da8dSAndroid Build Coastguard WorkerMONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
91*cda5da8dSAndroid Build Coastguard Worker          "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
92*cda5da8dSAndroid Build Coastguard WorkerMONTHS_LOWER = [month.lower() for month in MONTHS]
93*cda5da8dSAndroid Build Coastguard Worker
94*cda5da8dSAndroid Build Coastguard Workerdef time2isoz(t=None):
95*cda5da8dSAndroid Build Coastguard Worker    """Return a string representing time in seconds since epoch, t.
96*cda5da8dSAndroid Build Coastguard Worker
97*cda5da8dSAndroid Build Coastguard Worker    If the function is called without an argument, it will use the current
98*cda5da8dSAndroid Build Coastguard Worker    time.
99*cda5da8dSAndroid Build Coastguard Worker
100*cda5da8dSAndroid Build Coastguard Worker    The format of the returned string is like "YYYY-MM-DD hh:mm:ssZ",
101*cda5da8dSAndroid Build Coastguard Worker    representing Universal Time (UTC, aka GMT).  An example of this format is:
102*cda5da8dSAndroid Build Coastguard Worker
103*cda5da8dSAndroid Build Coastguard Worker    1994-11-24 08:49:37Z
104*cda5da8dSAndroid Build Coastguard Worker
105*cda5da8dSAndroid Build Coastguard Worker    """
106*cda5da8dSAndroid Build Coastguard Worker    if t is None:
107*cda5da8dSAndroid Build Coastguard Worker        dt = datetime.datetime.utcnow()
108*cda5da8dSAndroid Build Coastguard Worker    else:
109*cda5da8dSAndroid Build Coastguard Worker        dt = datetime.datetime.utcfromtimestamp(t)
110*cda5da8dSAndroid Build Coastguard Worker    return "%04d-%02d-%02d %02d:%02d:%02dZ" % (
111*cda5da8dSAndroid Build Coastguard Worker        dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second)
112*cda5da8dSAndroid Build Coastguard Worker
113*cda5da8dSAndroid Build Coastguard Workerdef time2netscape(t=None):
114*cda5da8dSAndroid Build Coastguard Worker    """Return a string representing time in seconds since epoch, t.
115*cda5da8dSAndroid Build Coastguard Worker
116*cda5da8dSAndroid Build Coastguard Worker    If the function is called without an argument, it will use the current
117*cda5da8dSAndroid Build Coastguard Worker    time.
118*cda5da8dSAndroid Build Coastguard Worker
119*cda5da8dSAndroid Build Coastguard Worker    The format of the returned string is like this:
120*cda5da8dSAndroid Build Coastguard Worker
121*cda5da8dSAndroid Build Coastguard Worker    Wed, DD-Mon-YYYY HH:MM:SS GMT
122*cda5da8dSAndroid Build Coastguard Worker
123*cda5da8dSAndroid Build Coastguard Worker    """
124*cda5da8dSAndroid Build Coastguard Worker    if t is None:
125*cda5da8dSAndroid Build Coastguard Worker        dt = datetime.datetime.utcnow()
126*cda5da8dSAndroid Build Coastguard Worker    else:
127*cda5da8dSAndroid Build Coastguard Worker        dt = datetime.datetime.utcfromtimestamp(t)
128*cda5da8dSAndroid Build Coastguard Worker    return "%s, %02d-%s-%04d %02d:%02d:%02d GMT" % (
129*cda5da8dSAndroid Build Coastguard Worker        DAYS[dt.weekday()], dt.day, MONTHS[dt.month-1],
130*cda5da8dSAndroid Build Coastguard Worker        dt.year, dt.hour, dt.minute, dt.second)
131*cda5da8dSAndroid Build Coastguard Worker
132*cda5da8dSAndroid Build Coastguard Worker
133*cda5da8dSAndroid Build Coastguard WorkerUTC_ZONES = {"GMT": None, "UTC": None, "UT": None, "Z": None}
134*cda5da8dSAndroid Build Coastguard Worker
135*cda5da8dSAndroid Build Coastguard WorkerTIMEZONE_RE = re.compile(r"^([-+])?(\d\d?):?(\d\d)?$", re.ASCII)
136*cda5da8dSAndroid Build Coastguard Workerdef offset_from_tz_string(tz):
137*cda5da8dSAndroid Build Coastguard Worker    offset = None
138*cda5da8dSAndroid Build Coastguard Worker    if tz in UTC_ZONES:
139*cda5da8dSAndroid Build Coastguard Worker        offset = 0
140*cda5da8dSAndroid Build Coastguard Worker    else:
141*cda5da8dSAndroid Build Coastguard Worker        m = TIMEZONE_RE.search(tz)
142*cda5da8dSAndroid Build Coastguard Worker        if m:
143*cda5da8dSAndroid Build Coastguard Worker            offset = 3600 * int(m.group(2))
144*cda5da8dSAndroid Build Coastguard Worker            if m.group(3):
145*cda5da8dSAndroid Build Coastguard Worker                offset = offset + 60 * int(m.group(3))
146*cda5da8dSAndroid Build Coastguard Worker            if m.group(1) == '-':
147*cda5da8dSAndroid Build Coastguard Worker                offset = -offset
148*cda5da8dSAndroid Build Coastguard Worker    return offset
149*cda5da8dSAndroid Build Coastguard Worker
150*cda5da8dSAndroid Build Coastguard Workerdef _str2time(day, mon, yr, hr, min, sec, tz):
151*cda5da8dSAndroid Build Coastguard Worker    yr = int(yr)
152*cda5da8dSAndroid Build Coastguard Worker    if yr > datetime.MAXYEAR:
153*cda5da8dSAndroid Build Coastguard Worker        return None
154*cda5da8dSAndroid Build Coastguard Worker
155*cda5da8dSAndroid Build Coastguard Worker    # translate month name to number
156*cda5da8dSAndroid Build Coastguard Worker    # month numbers start with 1 (January)
157*cda5da8dSAndroid Build Coastguard Worker    try:
158*cda5da8dSAndroid Build Coastguard Worker        mon = MONTHS_LOWER.index(mon.lower())+1
159*cda5da8dSAndroid Build Coastguard Worker    except ValueError:
160*cda5da8dSAndroid Build Coastguard Worker        # maybe it's already a number
161*cda5da8dSAndroid Build Coastguard Worker        try:
162*cda5da8dSAndroid Build Coastguard Worker            imon = int(mon)
163*cda5da8dSAndroid Build Coastguard Worker        except ValueError:
164*cda5da8dSAndroid Build Coastguard Worker            return None
165*cda5da8dSAndroid Build Coastguard Worker        if 1 <= imon <= 12:
166*cda5da8dSAndroid Build Coastguard Worker            mon = imon
167*cda5da8dSAndroid Build Coastguard Worker        else:
168*cda5da8dSAndroid Build Coastguard Worker            return None
169*cda5da8dSAndroid Build Coastguard Worker
170*cda5da8dSAndroid Build Coastguard Worker    # make sure clock elements are defined
171*cda5da8dSAndroid Build Coastguard Worker    if hr is None: hr = 0
172*cda5da8dSAndroid Build Coastguard Worker    if min is None: min = 0
173*cda5da8dSAndroid Build Coastguard Worker    if sec is None: sec = 0
174*cda5da8dSAndroid Build Coastguard Worker
175*cda5da8dSAndroid Build Coastguard Worker    day = int(day)
176*cda5da8dSAndroid Build Coastguard Worker    hr = int(hr)
177*cda5da8dSAndroid Build Coastguard Worker    min = int(min)
178*cda5da8dSAndroid Build Coastguard Worker    sec = int(sec)
179*cda5da8dSAndroid Build Coastguard Worker
180*cda5da8dSAndroid Build Coastguard Worker    if yr < 1000:
181*cda5da8dSAndroid Build Coastguard Worker        # find "obvious" year
182*cda5da8dSAndroid Build Coastguard Worker        cur_yr = time.localtime(time.time())[0]
183*cda5da8dSAndroid Build Coastguard Worker        m = cur_yr % 100
184*cda5da8dSAndroid Build Coastguard Worker        tmp = yr
185*cda5da8dSAndroid Build Coastguard Worker        yr = yr + cur_yr - m
186*cda5da8dSAndroid Build Coastguard Worker        m = m - tmp
187*cda5da8dSAndroid Build Coastguard Worker        if abs(m) > 50:
188*cda5da8dSAndroid Build Coastguard Worker            if m > 0: yr = yr + 100
189*cda5da8dSAndroid Build Coastguard Worker            else: yr = yr - 100
190*cda5da8dSAndroid Build Coastguard Worker
191*cda5da8dSAndroid Build Coastguard Worker    # convert UTC time tuple to seconds since epoch (not timezone-adjusted)
192*cda5da8dSAndroid Build Coastguard Worker    t = _timegm((yr, mon, day, hr, min, sec, tz))
193*cda5da8dSAndroid Build Coastguard Worker
194*cda5da8dSAndroid Build Coastguard Worker    if t is not None:
195*cda5da8dSAndroid Build Coastguard Worker        # adjust time using timezone string, to get absolute time since epoch
196*cda5da8dSAndroid Build Coastguard Worker        if tz is None:
197*cda5da8dSAndroid Build Coastguard Worker            tz = "UTC"
198*cda5da8dSAndroid Build Coastguard Worker        tz = tz.upper()
199*cda5da8dSAndroid Build Coastguard Worker        offset = offset_from_tz_string(tz)
200*cda5da8dSAndroid Build Coastguard Worker        if offset is None:
201*cda5da8dSAndroid Build Coastguard Worker            return None
202*cda5da8dSAndroid Build Coastguard Worker        t = t - offset
203*cda5da8dSAndroid Build Coastguard Worker
204*cda5da8dSAndroid Build Coastguard Worker    return t
205*cda5da8dSAndroid Build Coastguard Worker
206*cda5da8dSAndroid Build Coastguard WorkerSTRICT_DATE_RE = re.compile(
207*cda5da8dSAndroid Build Coastguard Worker    r"^[SMTWF][a-z][a-z], (\d\d) ([JFMASOND][a-z][a-z]) "
208*cda5da8dSAndroid Build Coastguard Worker    r"(\d\d\d\d) (\d\d):(\d\d):(\d\d) GMT$", re.ASCII)
209*cda5da8dSAndroid Build Coastguard WorkerWEEKDAY_RE = re.compile(
210*cda5da8dSAndroid Build Coastguard Worker    r"^(?:Sun|Mon|Tue|Wed|Thu|Fri|Sat)[a-z]*,?\s*", re.I | re.ASCII)
211*cda5da8dSAndroid Build Coastguard WorkerLOOSE_HTTP_DATE_RE = re.compile(
212*cda5da8dSAndroid Build Coastguard Worker    r"""^
213*cda5da8dSAndroid Build Coastguard Worker    (\d\d?)            # day
214*cda5da8dSAndroid Build Coastguard Worker       (?:\s+|[-\/])
215*cda5da8dSAndroid Build Coastguard Worker    (\w+)              # month
216*cda5da8dSAndroid Build Coastguard Worker        (?:\s+|[-\/])
217*cda5da8dSAndroid Build Coastguard Worker    (\d+)              # year
218*cda5da8dSAndroid Build Coastguard Worker    (?:
219*cda5da8dSAndroid Build Coastguard Worker          (?:\s+|:)    # separator before clock
220*cda5da8dSAndroid Build Coastguard Worker       (\d\d?):(\d\d)  # hour:min
221*cda5da8dSAndroid Build Coastguard Worker       (?::(\d\d))?    # optional seconds
222*cda5da8dSAndroid Build Coastguard Worker    )?                 # optional clock
223*cda5da8dSAndroid Build Coastguard Worker       \s*
224*cda5da8dSAndroid Build Coastguard Worker    (?:
225*cda5da8dSAndroid Build Coastguard Worker       ([-+]?\d{2,4}|(?![APap][Mm]\b)[A-Za-z]+) # timezone
226*cda5da8dSAndroid Build Coastguard Worker       \s*
227*cda5da8dSAndroid Build Coastguard Worker    )?
228*cda5da8dSAndroid Build Coastguard Worker    (?:
229*cda5da8dSAndroid Build Coastguard Worker       \(\w+\)         # ASCII representation of timezone in parens.
230*cda5da8dSAndroid Build Coastguard Worker       \s*
231*cda5da8dSAndroid Build Coastguard Worker    )?$""", re.X | re.ASCII)
232*cda5da8dSAndroid Build Coastguard Workerdef http2time(text):
233*cda5da8dSAndroid Build Coastguard Worker    """Returns time in seconds since epoch of time represented by a string.
234*cda5da8dSAndroid Build Coastguard Worker
235*cda5da8dSAndroid Build Coastguard Worker    Return value is an integer.
236*cda5da8dSAndroid Build Coastguard Worker
237*cda5da8dSAndroid Build Coastguard Worker    None is returned if the format of str is unrecognized, the time is outside
238*cda5da8dSAndroid Build Coastguard Worker    the representable range, or the timezone string is not recognized.  If the
239*cda5da8dSAndroid Build Coastguard Worker    string contains no timezone, UTC is assumed.
240*cda5da8dSAndroid Build Coastguard Worker
241*cda5da8dSAndroid Build Coastguard Worker    The timezone in the string may be numerical (like "-0800" or "+0100") or a
242*cda5da8dSAndroid Build Coastguard Worker    string timezone (like "UTC", "GMT", "BST" or "EST").  Currently, only the
243*cda5da8dSAndroid Build Coastguard Worker    timezone strings equivalent to UTC (zero offset) are known to the function.
244*cda5da8dSAndroid Build Coastguard Worker
245*cda5da8dSAndroid Build Coastguard Worker    The function loosely parses the following formats:
246*cda5da8dSAndroid Build Coastguard Worker
247*cda5da8dSAndroid Build Coastguard Worker    Wed, 09 Feb 1994 22:23:32 GMT       -- HTTP format
248*cda5da8dSAndroid Build Coastguard Worker    Tuesday, 08-Feb-94 14:15:29 GMT     -- old rfc850 HTTP format
249*cda5da8dSAndroid Build Coastguard Worker    Tuesday, 08-Feb-1994 14:15:29 GMT   -- broken rfc850 HTTP format
250*cda5da8dSAndroid Build Coastguard Worker    09 Feb 1994 22:23:32 GMT            -- HTTP format (no weekday)
251*cda5da8dSAndroid Build Coastguard Worker    08-Feb-94 14:15:29 GMT              -- rfc850 format (no weekday)
252*cda5da8dSAndroid Build Coastguard Worker    08-Feb-1994 14:15:29 GMT            -- broken rfc850 format (no weekday)
253*cda5da8dSAndroid Build Coastguard Worker
254*cda5da8dSAndroid Build Coastguard Worker    The parser ignores leading and trailing whitespace.  The time may be
255*cda5da8dSAndroid Build Coastguard Worker    absent.
256*cda5da8dSAndroid Build Coastguard Worker
257*cda5da8dSAndroid Build Coastguard Worker    If the year is given with only 2 digits, the function will select the
258*cda5da8dSAndroid Build Coastguard Worker    century that makes the year closest to the current date.
259*cda5da8dSAndroid Build Coastguard Worker
260*cda5da8dSAndroid Build Coastguard Worker    """
261*cda5da8dSAndroid Build Coastguard Worker    # fast exit for strictly conforming string
262*cda5da8dSAndroid Build Coastguard Worker    m = STRICT_DATE_RE.search(text)
263*cda5da8dSAndroid Build Coastguard Worker    if m:
264*cda5da8dSAndroid Build Coastguard Worker        g = m.groups()
265*cda5da8dSAndroid Build Coastguard Worker        mon = MONTHS_LOWER.index(g[1].lower()) + 1
266*cda5da8dSAndroid Build Coastguard Worker        tt = (int(g[2]), mon, int(g[0]),
267*cda5da8dSAndroid Build Coastguard Worker              int(g[3]), int(g[4]), float(g[5]))
268*cda5da8dSAndroid Build Coastguard Worker        return _timegm(tt)
269*cda5da8dSAndroid Build Coastguard Worker
270*cda5da8dSAndroid Build Coastguard Worker    # No, we need some messy parsing...
271*cda5da8dSAndroid Build Coastguard Worker
272*cda5da8dSAndroid Build Coastguard Worker    # clean up
273*cda5da8dSAndroid Build Coastguard Worker    text = text.lstrip()
274*cda5da8dSAndroid Build Coastguard Worker    text = WEEKDAY_RE.sub("", text, 1)  # Useless weekday
275*cda5da8dSAndroid Build Coastguard Worker
276*cda5da8dSAndroid Build Coastguard Worker    # tz is time zone specifier string
277*cda5da8dSAndroid Build Coastguard Worker    day, mon, yr, hr, min, sec, tz = [None]*7
278*cda5da8dSAndroid Build Coastguard Worker
279*cda5da8dSAndroid Build Coastguard Worker    # loose regexp parse
280*cda5da8dSAndroid Build Coastguard Worker    m = LOOSE_HTTP_DATE_RE.search(text)
281*cda5da8dSAndroid Build Coastguard Worker    if m is not None:
282*cda5da8dSAndroid Build Coastguard Worker        day, mon, yr, hr, min, sec, tz = m.groups()
283*cda5da8dSAndroid Build Coastguard Worker    else:
284*cda5da8dSAndroid Build Coastguard Worker        return None  # bad format
285*cda5da8dSAndroid Build Coastguard Worker
286*cda5da8dSAndroid Build Coastguard Worker    return _str2time(day, mon, yr, hr, min, sec, tz)
287*cda5da8dSAndroid Build Coastguard Worker
288*cda5da8dSAndroid Build Coastguard WorkerISO_DATE_RE = re.compile(
289*cda5da8dSAndroid Build Coastguard Worker    r"""^
290*cda5da8dSAndroid Build Coastguard Worker    (\d{4})              # year
291*cda5da8dSAndroid Build Coastguard Worker       [-\/]?
292*cda5da8dSAndroid Build Coastguard Worker    (\d\d?)              # numerical month
293*cda5da8dSAndroid Build Coastguard Worker       [-\/]?
294*cda5da8dSAndroid Build Coastguard Worker    (\d\d?)              # day
295*cda5da8dSAndroid Build Coastguard Worker   (?:
296*cda5da8dSAndroid Build Coastguard Worker         (?:\s+|[-:Tt])  # separator before clock
297*cda5da8dSAndroid Build Coastguard Worker      (\d\d?):?(\d\d)    # hour:min
298*cda5da8dSAndroid Build Coastguard Worker      (?::?(\d\d(?:\.\d*)?))?  # optional seconds (and fractional)
299*cda5da8dSAndroid Build Coastguard Worker   )?                    # optional clock
300*cda5da8dSAndroid Build Coastguard Worker      \s*
301*cda5da8dSAndroid Build Coastguard Worker   (?:
302*cda5da8dSAndroid Build Coastguard Worker      ([-+]?\d\d?:?(:?\d\d)?
303*cda5da8dSAndroid Build Coastguard Worker       |Z|z)             # timezone  (Z is "zero meridian", i.e. GMT)
304*cda5da8dSAndroid Build Coastguard Worker      \s*
305*cda5da8dSAndroid Build Coastguard Worker   )?$""", re.X | re. ASCII)
306*cda5da8dSAndroid Build Coastguard Workerdef iso2time(text):
307*cda5da8dSAndroid Build Coastguard Worker    """
308*cda5da8dSAndroid Build Coastguard Worker    As for http2time, but parses the ISO 8601 formats:
309*cda5da8dSAndroid Build Coastguard Worker
310*cda5da8dSAndroid Build Coastguard Worker    1994-02-03 14:15:29 -0100    -- ISO 8601 format
311*cda5da8dSAndroid Build Coastguard Worker    1994-02-03 14:15:29          -- zone is optional
312*cda5da8dSAndroid Build Coastguard Worker    1994-02-03                   -- only date
313*cda5da8dSAndroid Build Coastguard Worker    1994-02-03T14:15:29          -- Use T as separator
314*cda5da8dSAndroid Build Coastguard Worker    19940203T141529Z             -- ISO 8601 compact format
315*cda5da8dSAndroid Build Coastguard Worker    19940203                     -- only date
316*cda5da8dSAndroid Build Coastguard Worker
317*cda5da8dSAndroid Build Coastguard Worker    """
318*cda5da8dSAndroid Build Coastguard Worker    # clean up
319*cda5da8dSAndroid Build Coastguard Worker    text = text.lstrip()
320*cda5da8dSAndroid Build Coastguard Worker
321*cda5da8dSAndroid Build Coastguard Worker    # tz is time zone specifier string
322*cda5da8dSAndroid Build Coastguard Worker    day, mon, yr, hr, min, sec, tz = [None]*7
323*cda5da8dSAndroid Build Coastguard Worker
324*cda5da8dSAndroid Build Coastguard Worker    # loose regexp parse
325*cda5da8dSAndroid Build Coastguard Worker    m = ISO_DATE_RE.search(text)
326*cda5da8dSAndroid Build Coastguard Worker    if m is not None:
327*cda5da8dSAndroid Build Coastguard Worker        # XXX there's an extra bit of the timezone I'm ignoring here: is
328*cda5da8dSAndroid Build Coastguard Worker        #   this the right thing to do?
329*cda5da8dSAndroid Build Coastguard Worker        yr, mon, day, hr, min, sec, tz, _ = m.groups()
330*cda5da8dSAndroid Build Coastguard Worker    else:
331*cda5da8dSAndroid Build Coastguard Worker        return None  # bad format
332*cda5da8dSAndroid Build Coastguard Worker
333*cda5da8dSAndroid Build Coastguard Worker    return _str2time(day, mon, yr, hr, min, sec, tz)
334*cda5da8dSAndroid Build Coastguard Worker
335*cda5da8dSAndroid Build Coastguard Worker
336*cda5da8dSAndroid Build Coastguard Worker# Header parsing
337*cda5da8dSAndroid Build Coastguard Worker# -----------------------------------------------------------------------------
338*cda5da8dSAndroid Build Coastguard Worker
339*cda5da8dSAndroid Build Coastguard Workerdef unmatched(match):
340*cda5da8dSAndroid Build Coastguard Worker    """Return unmatched part of re.Match object."""
341*cda5da8dSAndroid Build Coastguard Worker    start, end = match.span(0)
342*cda5da8dSAndroid Build Coastguard Worker    return match.string[:start]+match.string[end:]
343*cda5da8dSAndroid Build Coastguard Worker
344*cda5da8dSAndroid Build Coastguard WorkerHEADER_TOKEN_RE =        re.compile(r"^\s*([^=\s;,]+)")
345*cda5da8dSAndroid Build Coastguard WorkerHEADER_QUOTED_VALUE_RE = re.compile(r"^\s*=\s*\"([^\"\\]*(?:\\.[^\"\\]*)*)\"")
346*cda5da8dSAndroid Build Coastguard WorkerHEADER_VALUE_RE =        re.compile(r"^\s*=\s*([^\s;,]*)")
347*cda5da8dSAndroid Build Coastguard WorkerHEADER_ESCAPE_RE = re.compile(r"\\(.)")
348*cda5da8dSAndroid Build Coastguard Workerdef split_header_words(header_values):
349*cda5da8dSAndroid Build Coastguard Worker    r"""Parse header values into a list of lists containing key,value pairs.
350*cda5da8dSAndroid Build Coastguard Worker
351*cda5da8dSAndroid Build Coastguard Worker    The function knows how to deal with ",", ";" and "=" as well as quoted
352*cda5da8dSAndroid Build Coastguard Worker    values after "=".  A list of space separated tokens are parsed as if they
353*cda5da8dSAndroid Build Coastguard Worker    were separated by ";".
354*cda5da8dSAndroid Build Coastguard Worker
355*cda5da8dSAndroid Build Coastguard Worker    If the header_values passed as argument contains multiple values, then they
356*cda5da8dSAndroid Build Coastguard Worker    are treated as if they were a single value separated by comma ",".
357*cda5da8dSAndroid Build Coastguard Worker
358*cda5da8dSAndroid Build Coastguard Worker    This means that this function is useful for parsing header fields that
359*cda5da8dSAndroid Build Coastguard Worker    follow this syntax (BNF as from the HTTP/1.1 specification, but we relax
360*cda5da8dSAndroid Build Coastguard Worker    the requirement for tokens).
361*cda5da8dSAndroid Build Coastguard Worker
362*cda5da8dSAndroid Build Coastguard Worker      headers           = #header
363*cda5da8dSAndroid Build Coastguard Worker      header            = (token | parameter) *( [";"] (token | parameter))
364*cda5da8dSAndroid Build Coastguard Worker
365*cda5da8dSAndroid Build Coastguard Worker      token             = 1*<any CHAR except CTLs or separators>
366*cda5da8dSAndroid Build Coastguard Worker      separators        = "(" | ")" | "<" | ">" | "@"
367*cda5da8dSAndroid Build Coastguard Worker                        | "," | ";" | ":" | "\" | <">
368*cda5da8dSAndroid Build Coastguard Worker                        | "/" | "[" | "]" | "?" | "="
369*cda5da8dSAndroid Build Coastguard Worker                        | "{" | "}" | SP | HT
370*cda5da8dSAndroid Build Coastguard Worker
371*cda5da8dSAndroid Build Coastguard Worker      quoted-string     = ( <"> *(qdtext | quoted-pair ) <"> )
372*cda5da8dSAndroid Build Coastguard Worker      qdtext            = <any TEXT except <">>
373*cda5da8dSAndroid Build Coastguard Worker      quoted-pair       = "\" CHAR
374*cda5da8dSAndroid Build Coastguard Worker
375*cda5da8dSAndroid Build Coastguard Worker      parameter         = attribute "=" value
376*cda5da8dSAndroid Build Coastguard Worker      attribute         = token
377*cda5da8dSAndroid Build Coastguard Worker      value             = token | quoted-string
378*cda5da8dSAndroid Build Coastguard Worker
379*cda5da8dSAndroid Build Coastguard Worker    Each header is represented by a list of key/value pairs.  The value for a
380*cda5da8dSAndroid Build Coastguard Worker    simple token (not part of a parameter) is None.  Syntactically incorrect
381*cda5da8dSAndroid Build Coastguard Worker    headers will not necessarily be parsed as you would want.
382*cda5da8dSAndroid Build Coastguard Worker
383*cda5da8dSAndroid Build Coastguard Worker    This is easier to describe with some examples:
384*cda5da8dSAndroid Build Coastguard Worker
385*cda5da8dSAndroid Build Coastguard Worker    >>> split_header_words(['foo="bar"; port="80,81"; discard, bar=baz'])
386*cda5da8dSAndroid Build Coastguard Worker    [[('foo', 'bar'), ('port', '80,81'), ('discard', None)], [('bar', 'baz')]]
387*cda5da8dSAndroid Build Coastguard Worker    >>> split_header_words(['text/html; charset="iso-8859-1"'])
388*cda5da8dSAndroid Build Coastguard Worker    [[('text/html', None), ('charset', 'iso-8859-1')]]
389*cda5da8dSAndroid Build Coastguard Worker    >>> split_header_words([r'Basic realm="\"foo\bar\""'])
390*cda5da8dSAndroid Build Coastguard Worker    [[('Basic', None), ('realm', '"foobar"')]]
391*cda5da8dSAndroid Build Coastguard Worker
392*cda5da8dSAndroid Build Coastguard Worker    """
393*cda5da8dSAndroid Build Coastguard Worker    assert not isinstance(header_values, str)
394*cda5da8dSAndroid Build Coastguard Worker    result = []
395*cda5da8dSAndroid Build Coastguard Worker    for text in header_values:
396*cda5da8dSAndroid Build Coastguard Worker        orig_text = text
397*cda5da8dSAndroid Build Coastguard Worker        pairs = []
398*cda5da8dSAndroid Build Coastguard Worker        while text:
399*cda5da8dSAndroid Build Coastguard Worker            m = HEADER_TOKEN_RE.search(text)
400*cda5da8dSAndroid Build Coastguard Worker            if m:
401*cda5da8dSAndroid Build Coastguard Worker                text = unmatched(m)
402*cda5da8dSAndroid Build Coastguard Worker                name = m.group(1)
403*cda5da8dSAndroid Build Coastguard Worker                m = HEADER_QUOTED_VALUE_RE.search(text)
404*cda5da8dSAndroid Build Coastguard Worker                if m:  # quoted value
405*cda5da8dSAndroid Build Coastguard Worker                    text = unmatched(m)
406*cda5da8dSAndroid Build Coastguard Worker                    value = m.group(1)
407*cda5da8dSAndroid Build Coastguard Worker                    value = HEADER_ESCAPE_RE.sub(r"\1", value)
408*cda5da8dSAndroid Build Coastguard Worker                else:
409*cda5da8dSAndroid Build Coastguard Worker                    m = HEADER_VALUE_RE.search(text)
410*cda5da8dSAndroid Build Coastguard Worker                    if m:  # unquoted value
411*cda5da8dSAndroid Build Coastguard Worker                        text = unmatched(m)
412*cda5da8dSAndroid Build Coastguard Worker                        value = m.group(1)
413*cda5da8dSAndroid Build Coastguard Worker                        value = value.rstrip()
414*cda5da8dSAndroid Build Coastguard Worker                    else:
415*cda5da8dSAndroid Build Coastguard Worker                        # no value, a lone token
416*cda5da8dSAndroid Build Coastguard Worker                        value = None
417*cda5da8dSAndroid Build Coastguard Worker                pairs.append((name, value))
418*cda5da8dSAndroid Build Coastguard Worker            elif text.lstrip().startswith(","):
419*cda5da8dSAndroid Build Coastguard Worker                # concatenated headers, as per RFC 2616 section 4.2
420*cda5da8dSAndroid Build Coastguard Worker                text = text.lstrip()[1:]
421*cda5da8dSAndroid Build Coastguard Worker                if pairs: result.append(pairs)
422*cda5da8dSAndroid Build Coastguard Worker                pairs = []
423*cda5da8dSAndroid Build Coastguard Worker            else:
424*cda5da8dSAndroid Build Coastguard Worker                # skip junk
425*cda5da8dSAndroid Build Coastguard Worker                non_junk, nr_junk_chars = re.subn(r"^[=\s;]*", "", text)
426*cda5da8dSAndroid Build Coastguard Worker                assert nr_junk_chars > 0, (
427*cda5da8dSAndroid Build Coastguard Worker                    "split_header_words bug: '%s', '%s', %s" %
428*cda5da8dSAndroid Build Coastguard Worker                    (orig_text, text, pairs))
429*cda5da8dSAndroid Build Coastguard Worker                text = non_junk
430*cda5da8dSAndroid Build Coastguard Worker        if pairs: result.append(pairs)
431*cda5da8dSAndroid Build Coastguard Worker    return result
432*cda5da8dSAndroid Build Coastguard Worker
433*cda5da8dSAndroid Build Coastguard WorkerHEADER_JOIN_ESCAPE_RE = re.compile(r"([\"\\])")
434*cda5da8dSAndroid Build Coastguard Workerdef join_header_words(lists):
435*cda5da8dSAndroid Build Coastguard Worker    """Do the inverse (almost) of the conversion done by split_header_words.
436*cda5da8dSAndroid Build Coastguard Worker
437*cda5da8dSAndroid Build Coastguard Worker    Takes a list of lists of (key, value) pairs and produces a single header
438*cda5da8dSAndroid Build Coastguard Worker    value.  Attribute values are quoted if needed.
439*cda5da8dSAndroid Build Coastguard Worker
440*cda5da8dSAndroid Build Coastguard Worker    >>> join_header_words([[("text/plain", None), ("charset", "iso-8859-1")]])
441*cda5da8dSAndroid Build Coastguard Worker    'text/plain; charset="iso-8859-1"'
442*cda5da8dSAndroid Build Coastguard Worker    >>> join_header_words([[("text/plain", None)], [("charset", "iso-8859-1")]])
443*cda5da8dSAndroid Build Coastguard Worker    'text/plain, charset="iso-8859-1"'
444*cda5da8dSAndroid Build Coastguard Worker
445*cda5da8dSAndroid Build Coastguard Worker    """
446*cda5da8dSAndroid Build Coastguard Worker    headers = []
447*cda5da8dSAndroid Build Coastguard Worker    for pairs in lists:
448*cda5da8dSAndroid Build Coastguard Worker        attr = []
449*cda5da8dSAndroid Build Coastguard Worker        for k, v in pairs:
450*cda5da8dSAndroid Build Coastguard Worker            if v is not None:
451*cda5da8dSAndroid Build Coastguard Worker                if not re.search(r"^\w+$", v):
452*cda5da8dSAndroid Build Coastguard Worker                    v = HEADER_JOIN_ESCAPE_RE.sub(r"\\\1", v)  # escape " and \
453*cda5da8dSAndroid Build Coastguard Worker                    v = '"%s"' % v
454*cda5da8dSAndroid Build Coastguard Worker                k = "%s=%s" % (k, v)
455*cda5da8dSAndroid Build Coastguard Worker            attr.append(k)
456*cda5da8dSAndroid Build Coastguard Worker        if attr: headers.append("; ".join(attr))
457*cda5da8dSAndroid Build Coastguard Worker    return ", ".join(headers)
458*cda5da8dSAndroid Build Coastguard Worker
459*cda5da8dSAndroid Build Coastguard Workerdef strip_quotes(text):
460*cda5da8dSAndroid Build Coastguard Worker    if text.startswith('"'):
461*cda5da8dSAndroid Build Coastguard Worker        text = text[1:]
462*cda5da8dSAndroid Build Coastguard Worker    if text.endswith('"'):
463*cda5da8dSAndroid Build Coastguard Worker        text = text[:-1]
464*cda5da8dSAndroid Build Coastguard Worker    return text
465*cda5da8dSAndroid Build Coastguard Worker
466*cda5da8dSAndroid Build Coastguard Workerdef parse_ns_headers(ns_headers):
467*cda5da8dSAndroid Build Coastguard Worker    """Ad-hoc parser for Netscape protocol cookie-attributes.
468*cda5da8dSAndroid Build Coastguard Worker
469*cda5da8dSAndroid Build Coastguard Worker    The old Netscape cookie format for Set-Cookie can for instance contain
470*cda5da8dSAndroid Build Coastguard Worker    an unquoted "," in the expires field, so we have to use this ad-hoc
471*cda5da8dSAndroid Build Coastguard Worker    parser instead of split_header_words.
472*cda5da8dSAndroid Build Coastguard Worker
473*cda5da8dSAndroid Build Coastguard Worker    XXX This may not make the best possible effort to parse all the crap
474*cda5da8dSAndroid Build Coastguard Worker    that Netscape Cookie headers contain.  Ronald Tschalar's HTTPClient
475*cda5da8dSAndroid Build Coastguard Worker    parser is probably better, so could do worse than following that if
476*cda5da8dSAndroid Build Coastguard Worker    this ever gives any trouble.
477*cda5da8dSAndroid Build Coastguard Worker
478*cda5da8dSAndroid Build Coastguard Worker    Currently, this is also used for parsing RFC 2109 cookies.
479*cda5da8dSAndroid Build Coastguard Worker
480*cda5da8dSAndroid Build Coastguard Worker    """
481*cda5da8dSAndroid Build Coastguard Worker    known_attrs = ("expires", "domain", "path", "secure",
482*cda5da8dSAndroid Build Coastguard Worker                   # RFC 2109 attrs (may turn up in Netscape cookies, too)
483*cda5da8dSAndroid Build Coastguard Worker                   "version", "port", "max-age")
484*cda5da8dSAndroid Build Coastguard Worker
485*cda5da8dSAndroid Build Coastguard Worker    result = []
486*cda5da8dSAndroid Build Coastguard Worker    for ns_header in ns_headers:
487*cda5da8dSAndroid Build Coastguard Worker        pairs = []
488*cda5da8dSAndroid Build Coastguard Worker        version_set = False
489*cda5da8dSAndroid Build Coastguard Worker
490*cda5da8dSAndroid Build Coastguard Worker        # XXX: The following does not strictly adhere to RFCs in that empty
491*cda5da8dSAndroid Build Coastguard Worker        # names and values are legal (the former will only appear once and will
492*cda5da8dSAndroid Build Coastguard Worker        # be overwritten if multiple occurrences are present). This is
493*cda5da8dSAndroid Build Coastguard Worker        # mostly to deal with backwards compatibility.
494*cda5da8dSAndroid Build Coastguard Worker        for ii, param in enumerate(ns_header.split(';')):
495*cda5da8dSAndroid Build Coastguard Worker            param = param.strip()
496*cda5da8dSAndroid Build Coastguard Worker
497*cda5da8dSAndroid Build Coastguard Worker            key, sep, val = param.partition('=')
498*cda5da8dSAndroid Build Coastguard Worker            key = key.strip()
499*cda5da8dSAndroid Build Coastguard Worker
500*cda5da8dSAndroid Build Coastguard Worker            if not key:
501*cda5da8dSAndroid Build Coastguard Worker                if ii == 0:
502*cda5da8dSAndroid Build Coastguard Worker                    break
503*cda5da8dSAndroid Build Coastguard Worker                else:
504*cda5da8dSAndroid Build Coastguard Worker                    continue
505*cda5da8dSAndroid Build Coastguard Worker
506*cda5da8dSAndroid Build Coastguard Worker            # allow for a distinction between present and empty and missing
507*cda5da8dSAndroid Build Coastguard Worker            # altogether
508*cda5da8dSAndroid Build Coastguard Worker            val = val.strip() if sep else None
509*cda5da8dSAndroid Build Coastguard Worker
510*cda5da8dSAndroid Build Coastguard Worker            if ii != 0:
511*cda5da8dSAndroid Build Coastguard Worker                lc = key.lower()
512*cda5da8dSAndroid Build Coastguard Worker                if lc in known_attrs:
513*cda5da8dSAndroid Build Coastguard Worker                    key = lc
514*cda5da8dSAndroid Build Coastguard Worker
515*cda5da8dSAndroid Build Coastguard Worker                if key == "version":
516*cda5da8dSAndroid Build Coastguard Worker                    # This is an RFC 2109 cookie.
517*cda5da8dSAndroid Build Coastguard Worker                    if val is not None:
518*cda5da8dSAndroid Build Coastguard Worker                        val = strip_quotes(val)
519*cda5da8dSAndroid Build Coastguard Worker                    version_set = True
520*cda5da8dSAndroid Build Coastguard Worker                elif key == "expires":
521*cda5da8dSAndroid Build Coastguard Worker                    # convert expires date to seconds since epoch
522*cda5da8dSAndroid Build Coastguard Worker                    if val is not None:
523*cda5da8dSAndroid Build Coastguard Worker                        val = http2time(strip_quotes(val))  # None if invalid
524*cda5da8dSAndroid Build Coastguard Worker            pairs.append((key, val))
525*cda5da8dSAndroid Build Coastguard Worker
526*cda5da8dSAndroid Build Coastguard Worker        if pairs:
527*cda5da8dSAndroid Build Coastguard Worker            if not version_set:
528*cda5da8dSAndroid Build Coastguard Worker                pairs.append(("version", "0"))
529*cda5da8dSAndroid Build Coastguard Worker            result.append(pairs)
530*cda5da8dSAndroid Build Coastguard Worker
531*cda5da8dSAndroid Build Coastguard Worker    return result
532*cda5da8dSAndroid Build Coastguard Worker
533*cda5da8dSAndroid Build Coastguard Worker
534*cda5da8dSAndroid Build Coastguard WorkerIPV4_RE = re.compile(r"\.\d+$", re.ASCII)
535*cda5da8dSAndroid Build Coastguard Workerdef is_HDN(text):
536*cda5da8dSAndroid Build Coastguard Worker    """Return True if text is a host domain name."""
537*cda5da8dSAndroid Build Coastguard Worker    # XXX
538*cda5da8dSAndroid Build Coastguard Worker    # This may well be wrong.  Which RFC is HDN defined in, if any (for
539*cda5da8dSAndroid Build Coastguard Worker    #  the purposes of RFC 2965)?
540*cda5da8dSAndroid Build Coastguard Worker    # For the current implementation, what about IPv6?  Remember to look
541*cda5da8dSAndroid Build Coastguard Worker    #  at other uses of IPV4_RE also, if change this.
542*cda5da8dSAndroid Build Coastguard Worker    if IPV4_RE.search(text):
543*cda5da8dSAndroid Build Coastguard Worker        return False
544*cda5da8dSAndroid Build Coastguard Worker    if text == "":
545*cda5da8dSAndroid Build Coastguard Worker        return False
546*cda5da8dSAndroid Build Coastguard Worker    if text[0] == "." or text[-1] == ".":
547*cda5da8dSAndroid Build Coastguard Worker        return False
548*cda5da8dSAndroid Build Coastguard Worker    return True
549*cda5da8dSAndroid Build Coastguard Worker
550*cda5da8dSAndroid Build Coastguard Workerdef domain_match(A, B):
551*cda5da8dSAndroid Build Coastguard Worker    """Return True if domain A domain-matches domain B, according to RFC 2965.
552*cda5da8dSAndroid Build Coastguard Worker
553*cda5da8dSAndroid Build Coastguard Worker    A and B may be host domain names or IP addresses.
554*cda5da8dSAndroid Build Coastguard Worker
555*cda5da8dSAndroid Build Coastguard Worker    RFC 2965, section 1:
556*cda5da8dSAndroid Build Coastguard Worker
557*cda5da8dSAndroid Build Coastguard Worker    Host names can be specified either as an IP address or a HDN string.
558*cda5da8dSAndroid Build Coastguard Worker    Sometimes we compare one host name with another.  (Such comparisons SHALL
559*cda5da8dSAndroid Build Coastguard Worker    be case-insensitive.)  Host A's name domain-matches host B's if
560*cda5da8dSAndroid Build Coastguard Worker
561*cda5da8dSAndroid Build Coastguard Worker         *  their host name strings string-compare equal; or
562*cda5da8dSAndroid Build Coastguard Worker
563*cda5da8dSAndroid Build Coastguard Worker         * A is a HDN string and has the form NB, where N is a non-empty
564*cda5da8dSAndroid Build Coastguard Worker            name string, B has the form .B', and B' is a HDN string.  (So,
565*cda5da8dSAndroid Build Coastguard Worker            x.y.com domain-matches .Y.com but not Y.com.)
566*cda5da8dSAndroid Build Coastguard Worker
567*cda5da8dSAndroid Build Coastguard Worker    Note that domain-match is not a commutative operation: a.b.c.com
568*cda5da8dSAndroid Build Coastguard Worker    domain-matches .c.com, but not the reverse.
569*cda5da8dSAndroid Build Coastguard Worker
570*cda5da8dSAndroid Build Coastguard Worker    """
571*cda5da8dSAndroid Build Coastguard Worker    # Note that, if A or B are IP addresses, the only relevant part of the
572*cda5da8dSAndroid Build Coastguard Worker    # definition of the domain-match algorithm is the direct string-compare.
573*cda5da8dSAndroid Build Coastguard Worker    A = A.lower()
574*cda5da8dSAndroid Build Coastguard Worker    B = B.lower()
575*cda5da8dSAndroid Build Coastguard Worker    if A == B:
576*cda5da8dSAndroid Build Coastguard Worker        return True
577*cda5da8dSAndroid Build Coastguard Worker    if not is_HDN(A):
578*cda5da8dSAndroid Build Coastguard Worker        return False
579*cda5da8dSAndroid Build Coastguard Worker    i = A.rfind(B)
580*cda5da8dSAndroid Build Coastguard Worker    if i == -1 or i == 0:
581*cda5da8dSAndroid Build Coastguard Worker        # A does not have form NB, or N is the empty string
582*cda5da8dSAndroid Build Coastguard Worker        return False
583*cda5da8dSAndroid Build Coastguard Worker    if not B.startswith("."):
584*cda5da8dSAndroid Build Coastguard Worker        return False
585*cda5da8dSAndroid Build Coastguard Worker    if not is_HDN(B[1:]):
586*cda5da8dSAndroid Build Coastguard Worker        return False
587*cda5da8dSAndroid Build Coastguard Worker    return True
588*cda5da8dSAndroid Build Coastguard Worker
589*cda5da8dSAndroid Build Coastguard Workerdef liberal_is_HDN(text):
590*cda5da8dSAndroid Build Coastguard Worker    """Return True if text is a sort-of-like a host domain name.
591*cda5da8dSAndroid Build Coastguard Worker
592*cda5da8dSAndroid Build Coastguard Worker    For accepting/blocking domains.
593*cda5da8dSAndroid Build Coastguard Worker
594*cda5da8dSAndroid Build Coastguard Worker    """
595*cda5da8dSAndroid Build Coastguard Worker    if IPV4_RE.search(text):
596*cda5da8dSAndroid Build Coastguard Worker        return False
597*cda5da8dSAndroid Build Coastguard Worker    return True
598*cda5da8dSAndroid Build Coastguard Worker
599*cda5da8dSAndroid Build Coastguard Workerdef user_domain_match(A, B):
600*cda5da8dSAndroid Build Coastguard Worker    """For blocking/accepting domains.
601*cda5da8dSAndroid Build Coastguard Worker
602*cda5da8dSAndroid Build Coastguard Worker    A and B may be host domain names or IP addresses.
603*cda5da8dSAndroid Build Coastguard Worker
604*cda5da8dSAndroid Build Coastguard Worker    """
605*cda5da8dSAndroid Build Coastguard Worker    A = A.lower()
606*cda5da8dSAndroid Build Coastguard Worker    B = B.lower()
607*cda5da8dSAndroid Build Coastguard Worker    if not (liberal_is_HDN(A) and liberal_is_HDN(B)):
608*cda5da8dSAndroid Build Coastguard Worker        if A == B:
609*cda5da8dSAndroid Build Coastguard Worker            # equal IP addresses
610*cda5da8dSAndroid Build Coastguard Worker            return True
611*cda5da8dSAndroid Build Coastguard Worker        return False
612*cda5da8dSAndroid Build Coastguard Worker    initial_dot = B.startswith(".")
613*cda5da8dSAndroid Build Coastguard Worker    if initial_dot and A.endswith(B):
614*cda5da8dSAndroid Build Coastguard Worker        return True
615*cda5da8dSAndroid Build Coastguard Worker    if not initial_dot and A == B:
616*cda5da8dSAndroid Build Coastguard Worker        return True
617*cda5da8dSAndroid Build Coastguard Worker    return False
618*cda5da8dSAndroid Build Coastguard Worker
619*cda5da8dSAndroid Build Coastguard Workercut_port_re = re.compile(r":\d+$", re.ASCII)
620*cda5da8dSAndroid Build Coastguard Workerdef request_host(request):
621*cda5da8dSAndroid Build Coastguard Worker    """Return request-host, as defined by RFC 2965.
622*cda5da8dSAndroid Build Coastguard Worker
623*cda5da8dSAndroid Build Coastguard Worker    Variation from RFC: returned value is lowercased, for convenient
624*cda5da8dSAndroid Build Coastguard Worker    comparison.
625*cda5da8dSAndroid Build Coastguard Worker
626*cda5da8dSAndroid Build Coastguard Worker    """
627*cda5da8dSAndroid Build Coastguard Worker    url = request.get_full_url()
628*cda5da8dSAndroid Build Coastguard Worker    host = urllib.parse.urlparse(url)[1]
629*cda5da8dSAndroid Build Coastguard Worker    if host == "":
630*cda5da8dSAndroid Build Coastguard Worker        host = request.get_header("Host", "")
631*cda5da8dSAndroid Build Coastguard Worker
632*cda5da8dSAndroid Build Coastguard Worker    # remove port, if present
633*cda5da8dSAndroid Build Coastguard Worker    host = cut_port_re.sub("", host, 1)
634*cda5da8dSAndroid Build Coastguard Worker    return host.lower()
635*cda5da8dSAndroid Build Coastguard Worker
636*cda5da8dSAndroid Build Coastguard Workerdef eff_request_host(request):
637*cda5da8dSAndroid Build Coastguard Worker    """Return a tuple (request-host, effective request-host name).
638*cda5da8dSAndroid Build Coastguard Worker
639*cda5da8dSAndroid Build Coastguard Worker    As defined by RFC 2965, except both are lowercased.
640*cda5da8dSAndroid Build Coastguard Worker
641*cda5da8dSAndroid Build Coastguard Worker    """
642*cda5da8dSAndroid Build Coastguard Worker    erhn = req_host = request_host(request)
643*cda5da8dSAndroid Build Coastguard Worker    if req_host.find(".") == -1 and not IPV4_RE.search(req_host):
644*cda5da8dSAndroid Build Coastguard Worker        erhn = req_host + ".local"
645*cda5da8dSAndroid Build Coastguard Worker    return req_host, erhn
646*cda5da8dSAndroid Build Coastguard Worker
647*cda5da8dSAndroid Build Coastguard Workerdef request_path(request):
648*cda5da8dSAndroid Build Coastguard Worker    """Path component of request-URI, as defined by RFC 2965."""
649*cda5da8dSAndroid Build Coastguard Worker    url = request.get_full_url()
650*cda5da8dSAndroid Build Coastguard Worker    parts = urllib.parse.urlsplit(url)
651*cda5da8dSAndroid Build Coastguard Worker    path = escape_path(parts.path)
652*cda5da8dSAndroid Build Coastguard Worker    if not path.startswith("/"):
653*cda5da8dSAndroid Build Coastguard Worker        # fix bad RFC 2396 absoluteURI
654*cda5da8dSAndroid Build Coastguard Worker        path = "/" + path
655*cda5da8dSAndroid Build Coastguard Worker    return path
656*cda5da8dSAndroid Build Coastguard Worker
657*cda5da8dSAndroid Build Coastguard Workerdef request_port(request):
658*cda5da8dSAndroid Build Coastguard Worker    host = request.host
659*cda5da8dSAndroid Build Coastguard Worker    i = host.find(':')
660*cda5da8dSAndroid Build Coastguard Worker    if i >= 0:
661*cda5da8dSAndroid Build Coastguard Worker        port = host[i+1:]
662*cda5da8dSAndroid Build Coastguard Worker        try:
663*cda5da8dSAndroid Build Coastguard Worker            int(port)
664*cda5da8dSAndroid Build Coastguard Worker        except ValueError:
665*cda5da8dSAndroid Build Coastguard Worker            _debug("nonnumeric port: '%s'", port)
666*cda5da8dSAndroid Build Coastguard Worker            return None
667*cda5da8dSAndroid Build Coastguard Worker    else:
668*cda5da8dSAndroid Build Coastguard Worker        port = DEFAULT_HTTP_PORT
669*cda5da8dSAndroid Build Coastguard Worker    return port
670*cda5da8dSAndroid Build Coastguard Worker
671*cda5da8dSAndroid Build Coastguard Worker# Characters in addition to A-Z, a-z, 0-9, '_', '.', and '-' that don't
672*cda5da8dSAndroid Build Coastguard Worker# need to be escaped to form a valid HTTP URL (RFCs 2396 and 1738).
673*cda5da8dSAndroid Build Coastguard WorkerHTTP_PATH_SAFE = "%/;:@&=+$,!~*'()"
674*cda5da8dSAndroid Build Coastguard WorkerESCAPED_CHAR_RE = re.compile(r"%([0-9a-fA-F][0-9a-fA-F])")
675*cda5da8dSAndroid Build Coastguard Workerdef uppercase_escaped_char(match):
676*cda5da8dSAndroid Build Coastguard Worker    return "%%%s" % match.group(1).upper()
677*cda5da8dSAndroid Build Coastguard Workerdef escape_path(path):
678*cda5da8dSAndroid Build Coastguard Worker    """Escape any invalid characters in HTTP URL, and uppercase all escapes."""
679*cda5da8dSAndroid Build Coastguard Worker    # There's no knowing what character encoding was used to create URLs
680*cda5da8dSAndroid Build Coastguard Worker    # containing %-escapes, but since we have to pick one to escape invalid
681*cda5da8dSAndroid Build Coastguard Worker    # path characters, we pick UTF-8, as recommended in the HTML 4.0
682*cda5da8dSAndroid Build Coastguard Worker    # specification:
683*cda5da8dSAndroid Build Coastguard Worker    # http://www.w3.org/TR/REC-html40/appendix/notes.html#h-B.2.1
684*cda5da8dSAndroid Build Coastguard Worker    # And here, kind of: draft-fielding-uri-rfc2396bis-03
685*cda5da8dSAndroid Build Coastguard Worker    # (And in draft IRI specification: draft-duerst-iri-05)
686*cda5da8dSAndroid Build Coastguard Worker    # (And here, for new URI schemes: RFC 2718)
687*cda5da8dSAndroid Build Coastguard Worker    path = urllib.parse.quote(path, HTTP_PATH_SAFE)
688*cda5da8dSAndroid Build Coastguard Worker    path = ESCAPED_CHAR_RE.sub(uppercase_escaped_char, path)
689*cda5da8dSAndroid Build Coastguard Worker    return path
690*cda5da8dSAndroid Build Coastguard Worker
691*cda5da8dSAndroid Build Coastguard Workerdef reach(h):
692*cda5da8dSAndroid Build Coastguard Worker    """Return reach of host h, as defined by RFC 2965, section 1.
693*cda5da8dSAndroid Build Coastguard Worker
694*cda5da8dSAndroid Build Coastguard Worker    The reach R of a host name H is defined as follows:
695*cda5da8dSAndroid Build Coastguard Worker
696*cda5da8dSAndroid Build Coastguard Worker       *  If
697*cda5da8dSAndroid Build Coastguard Worker
698*cda5da8dSAndroid Build Coastguard Worker          -  H is the host domain name of a host; and,
699*cda5da8dSAndroid Build Coastguard Worker
700*cda5da8dSAndroid Build Coastguard Worker          -  H has the form A.B; and
701*cda5da8dSAndroid Build Coastguard Worker
702*cda5da8dSAndroid Build Coastguard Worker          -  A has no embedded (that is, interior) dots; and
703*cda5da8dSAndroid Build Coastguard Worker
704*cda5da8dSAndroid Build Coastguard Worker          -  B has at least one embedded dot, or B is the string "local".
705*cda5da8dSAndroid Build Coastguard Worker             then the reach of H is .B.
706*cda5da8dSAndroid Build Coastguard Worker
707*cda5da8dSAndroid Build Coastguard Worker       *  Otherwise, the reach of H is H.
708*cda5da8dSAndroid Build Coastguard Worker
709*cda5da8dSAndroid Build Coastguard Worker    >>> reach("www.acme.com")
710*cda5da8dSAndroid Build Coastguard Worker    '.acme.com'
711*cda5da8dSAndroid Build Coastguard Worker    >>> reach("acme.com")
712*cda5da8dSAndroid Build Coastguard Worker    'acme.com'
713*cda5da8dSAndroid Build Coastguard Worker    >>> reach("acme.local")
714*cda5da8dSAndroid Build Coastguard Worker    '.local'
715*cda5da8dSAndroid Build Coastguard Worker
716*cda5da8dSAndroid Build Coastguard Worker    """
717*cda5da8dSAndroid Build Coastguard Worker    i = h.find(".")
718*cda5da8dSAndroid Build Coastguard Worker    if i >= 0:
719*cda5da8dSAndroid Build Coastguard Worker        #a = h[:i]  # this line is only here to show what a is
720*cda5da8dSAndroid Build Coastguard Worker        b = h[i+1:]
721*cda5da8dSAndroid Build Coastguard Worker        i = b.find(".")
722*cda5da8dSAndroid Build Coastguard Worker        if is_HDN(h) and (i >= 0 or b == "local"):
723*cda5da8dSAndroid Build Coastguard Worker            return "."+b
724*cda5da8dSAndroid Build Coastguard Worker    return h
725*cda5da8dSAndroid Build Coastguard Worker
726*cda5da8dSAndroid Build Coastguard Workerdef is_third_party(request):
727*cda5da8dSAndroid Build Coastguard Worker    """
728*cda5da8dSAndroid Build Coastguard Worker
729*cda5da8dSAndroid Build Coastguard Worker    RFC 2965, section 3.3.6:
730*cda5da8dSAndroid Build Coastguard Worker
731*cda5da8dSAndroid Build Coastguard Worker        An unverifiable transaction is to a third-party host if its request-
732*cda5da8dSAndroid Build Coastguard Worker        host U does not domain-match the reach R of the request-host O in the
733*cda5da8dSAndroid Build Coastguard Worker        origin transaction.
734*cda5da8dSAndroid Build Coastguard Worker
735*cda5da8dSAndroid Build Coastguard Worker    """
736*cda5da8dSAndroid Build Coastguard Worker    req_host = request_host(request)
737*cda5da8dSAndroid Build Coastguard Worker    if not domain_match(req_host, reach(request.origin_req_host)):
738*cda5da8dSAndroid Build Coastguard Worker        return True
739*cda5da8dSAndroid Build Coastguard Worker    else:
740*cda5da8dSAndroid Build Coastguard Worker        return False
741*cda5da8dSAndroid Build Coastguard Worker
742*cda5da8dSAndroid Build Coastguard Worker
743*cda5da8dSAndroid Build Coastguard Workerclass Cookie:
744*cda5da8dSAndroid Build Coastguard Worker    """HTTP Cookie.
745*cda5da8dSAndroid Build Coastguard Worker
746*cda5da8dSAndroid Build Coastguard Worker    This class represents both Netscape and RFC 2965 cookies.
747*cda5da8dSAndroid Build Coastguard Worker
748*cda5da8dSAndroid Build Coastguard Worker    This is deliberately a very simple class.  It just holds attributes.  It's
749*cda5da8dSAndroid Build Coastguard Worker    possible to construct Cookie instances that don't comply with the cookie
750*cda5da8dSAndroid Build Coastguard Worker    standards.  CookieJar.make_cookies is the factory function for Cookie
751*cda5da8dSAndroid Build Coastguard Worker    objects -- it deals with cookie parsing, supplying defaults, and
752*cda5da8dSAndroid Build Coastguard Worker    normalising to the representation used in this class.  CookiePolicy is
753*cda5da8dSAndroid Build Coastguard Worker    responsible for checking them to see whether they should be accepted from
754*cda5da8dSAndroid Build Coastguard Worker    and returned to the server.
755*cda5da8dSAndroid Build Coastguard Worker
756*cda5da8dSAndroid Build Coastguard Worker    Note that the port may be present in the headers, but unspecified ("Port"
757*cda5da8dSAndroid Build Coastguard Worker    rather than"Port=80", for example); if this is the case, port is None.
758*cda5da8dSAndroid Build Coastguard Worker
759*cda5da8dSAndroid Build Coastguard Worker    """
760*cda5da8dSAndroid Build Coastguard Worker
761*cda5da8dSAndroid Build Coastguard Worker    def __init__(self, version, name, value,
762*cda5da8dSAndroid Build Coastguard Worker                 port, port_specified,
763*cda5da8dSAndroid Build Coastguard Worker                 domain, domain_specified, domain_initial_dot,
764*cda5da8dSAndroid Build Coastguard Worker                 path, path_specified,
765*cda5da8dSAndroid Build Coastguard Worker                 secure,
766*cda5da8dSAndroid Build Coastguard Worker                 expires,
767*cda5da8dSAndroid Build Coastguard Worker                 discard,
768*cda5da8dSAndroid Build Coastguard Worker                 comment,
769*cda5da8dSAndroid Build Coastguard Worker                 comment_url,
770*cda5da8dSAndroid Build Coastguard Worker                 rest,
771*cda5da8dSAndroid Build Coastguard Worker                 rfc2109=False,
772*cda5da8dSAndroid Build Coastguard Worker                 ):
773*cda5da8dSAndroid Build Coastguard Worker
774*cda5da8dSAndroid Build Coastguard Worker        if version is not None: version = int(version)
775*cda5da8dSAndroid Build Coastguard Worker        if expires is not None: expires = int(float(expires))
776*cda5da8dSAndroid Build Coastguard Worker        if port is None and port_specified is True:
777*cda5da8dSAndroid Build Coastguard Worker            raise ValueError("if port is None, port_specified must be false")
778*cda5da8dSAndroid Build Coastguard Worker
779*cda5da8dSAndroid Build Coastguard Worker        self.version = version
780*cda5da8dSAndroid Build Coastguard Worker        self.name = name
781*cda5da8dSAndroid Build Coastguard Worker        self.value = value
782*cda5da8dSAndroid Build Coastguard Worker        self.port = port
783*cda5da8dSAndroid Build Coastguard Worker        self.port_specified = port_specified
784*cda5da8dSAndroid Build Coastguard Worker        # normalise case, as per RFC 2965 section 3.3.3
785*cda5da8dSAndroid Build Coastguard Worker        self.domain = domain.lower()
786*cda5da8dSAndroid Build Coastguard Worker        self.domain_specified = domain_specified
787*cda5da8dSAndroid Build Coastguard Worker        # Sigh.  We need to know whether the domain given in the
788*cda5da8dSAndroid Build Coastguard Worker        # cookie-attribute had an initial dot, in order to follow RFC 2965
789*cda5da8dSAndroid Build Coastguard Worker        # (as clarified in draft errata).  Needed for the returned $Domain
790*cda5da8dSAndroid Build Coastguard Worker        # value.
791*cda5da8dSAndroid Build Coastguard Worker        self.domain_initial_dot = domain_initial_dot
792*cda5da8dSAndroid Build Coastguard Worker        self.path = path
793*cda5da8dSAndroid Build Coastguard Worker        self.path_specified = path_specified
794*cda5da8dSAndroid Build Coastguard Worker        self.secure = secure
795*cda5da8dSAndroid Build Coastguard Worker        self.expires = expires
796*cda5da8dSAndroid Build Coastguard Worker        self.discard = discard
797*cda5da8dSAndroid Build Coastguard Worker        self.comment = comment
798*cda5da8dSAndroid Build Coastguard Worker        self.comment_url = comment_url
799*cda5da8dSAndroid Build Coastguard Worker        self.rfc2109 = rfc2109
800*cda5da8dSAndroid Build Coastguard Worker
801*cda5da8dSAndroid Build Coastguard Worker        self._rest = copy.copy(rest)
802*cda5da8dSAndroid Build Coastguard Worker
803*cda5da8dSAndroid Build Coastguard Worker    def has_nonstandard_attr(self, name):
804*cda5da8dSAndroid Build Coastguard Worker        return name in self._rest
805*cda5da8dSAndroid Build Coastguard Worker    def get_nonstandard_attr(self, name, default=None):
806*cda5da8dSAndroid Build Coastguard Worker        return self._rest.get(name, default)
807*cda5da8dSAndroid Build Coastguard Worker    def set_nonstandard_attr(self, name, value):
808*cda5da8dSAndroid Build Coastguard Worker        self._rest[name] = value
809*cda5da8dSAndroid Build Coastguard Worker
810*cda5da8dSAndroid Build Coastguard Worker    def is_expired(self, now=None):
811*cda5da8dSAndroid Build Coastguard Worker        if now is None: now = time.time()
812*cda5da8dSAndroid Build Coastguard Worker        if (self.expires is not None) and (self.expires <= now):
813*cda5da8dSAndroid Build Coastguard Worker            return True
814*cda5da8dSAndroid Build Coastguard Worker        return False
815*cda5da8dSAndroid Build Coastguard Worker
816*cda5da8dSAndroid Build Coastguard Worker    def __str__(self):
817*cda5da8dSAndroid Build Coastguard Worker        if self.port is None: p = ""
818*cda5da8dSAndroid Build Coastguard Worker        else: p = ":"+self.port
819*cda5da8dSAndroid Build Coastguard Worker        limit = self.domain + p + self.path
820*cda5da8dSAndroid Build Coastguard Worker        if self.value is not None:
821*cda5da8dSAndroid Build Coastguard Worker            namevalue = "%s=%s" % (self.name, self.value)
822*cda5da8dSAndroid Build Coastguard Worker        else:
823*cda5da8dSAndroid Build Coastguard Worker            namevalue = self.name
824*cda5da8dSAndroid Build Coastguard Worker        return "<Cookie %s for %s>" % (namevalue, limit)
825*cda5da8dSAndroid Build Coastguard Worker
826*cda5da8dSAndroid Build Coastguard Worker    def __repr__(self):
827*cda5da8dSAndroid Build Coastguard Worker        args = []
828*cda5da8dSAndroid Build Coastguard Worker        for name in ("version", "name", "value",
829*cda5da8dSAndroid Build Coastguard Worker                     "port", "port_specified",
830*cda5da8dSAndroid Build Coastguard Worker                     "domain", "domain_specified", "domain_initial_dot",
831*cda5da8dSAndroid Build Coastguard Worker                     "path", "path_specified",
832*cda5da8dSAndroid Build Coastguard Worker                     "secure", "expires", "discard", "comment", "comment_url",
833*cda5da8dSAndroid Build Coastguard Worker                     ):
834*cda5da8dSAndroid Build Coastguard Worker            attr = getattr(self, name)
835*cda5da8dSAndroid Build Coastguard Worker            args.append("%s=%s" % (name, repr(attr)))
836*cda5da8dSAndroid Build Coastguard Worker        args.append("rest=%s" % repr(self._rest))
837*cda5da8dSAndroid Build Coastguard Worker        args.append("rfc2109=%s" % repr(self.rfc2109))
838*cda5da8dSAndroid Build Coastguard Worker        return "%s(%s)" % (self.__class__.__name__, ", ".join(args))
839*cda5da8dSAndroid Build Coastguard Worker
840*cda5da8dSAndroid Build Coastguard Worker
841*cda5da8dSAndroid Build Coastguard Workerclass CookiePolicy:
842*cda5da8dSAndroid Build Coastguard Worker    """Defines which cookies get accepted from and returned to server.
843*cda5da8dSAndroid Build Coastguard Worker
844*cda5da8dSAndroid Build Coastguard Worker    May also modify cookies, though this is probably a bad idea.
845*cda5da8dSAndroid Build Coastguard Worker
846*cda5da8dSAndroid Build Coastguard Worker    The subclass DefaultCookiePolicy defines the standard rules for Netscape
847*cda5da8dSAndroid Build Coastguard Worker    and RFC 2965 cookies -- override that if you want a customized policy.
848*cda5da8dSAndroid Build Coastguard Worker
849*cda5da8dSAndroid Build Coastguard Worker    """
850*cda5da8dSAndroid Build Coastguard Worker    def set_ok(self, cookie, request):
851*cda5da8dSAndroid Build Coastguard Worker        """Return true if (and only if) cookie should be accepted from server.
852*cda5da8dSAndroid Build Coastguard Worker
853*cda5da8dSAndroid Build Coastguard Worker        Currently, pre-expired cookies never get this far -- the CookieJar
854*cda5da8dSAndroid Build Coastguard Worker        class deletes such cookies itself.
855*cda5da8dSAndroid Build Coastguard Worker
856*cda5da8dSAndroid Build Coastguard Worker        """
857*cda5da8dSAndroid Build Coastguard Worker        raise NotImplementedError()
858*cda5da8dSAndroid Build Coastguard Worker
859*cda5da8dSAndroid Build Coastguard Worker    def return_ok(self, cookie, request):
860*cda5da8dSAndroid Build Coastguard Worker        """Return true if (and only if) cookie should be returned to server."""
861*cda5da8dSAndroid Build Coastguard Worker        raise NotImplementedError()
862*cda5da8dSAndroid Build Coastguard Worker
863*cda5da8dSAndroid Build Coastguard Worker    def domain_return_ok(self, domain, request):
864*cda5da8dSAndroid Build Coastguard Worker        """Return false if cookies should not be returned, given cookie domain.
865*cda5da8dSAndroid Build Coastguard Worker        """
866*cda5da8dSAndroid Build Coastguard Worker        return True
867*cda5da8dSAndroid Build Coastguard Worker
868*cda5da8dSAndroid Build Coastguard Worker    def path_return_ok(self, path, request):
869*cda5da8dSAndroid Build Coastguard Worker        """Return false if cookies should not be returned, given cookie path.
870*cda5da8dSAndroid Build Coastguard Worker        """
871*cda5da8dSAndroid Build Coastguard Worker        return True
872*cda5da8dSAndroid Build Coastguard Worker
873*cda5da8dSAndroid Build Coastguard Worker
874*cda5da8dSAndroid Build Coastguard Workerclass DefaultCookiePolicy(CookiePolicy):
875*cda5da8dSAndroid Build Coastguard Worker    """Implements the standard rules for accepting and returning cookies."""
876*cda5da8dSAndroid Build Coastguard Worker
877*cda5da8dSAndroid Build Coastguard Worker    DomainStrictNoDots = 1
878*cda5da8dSAndroid Build Coastguard Worker    DomainStrictNonDomain = 2
879*cda5da8dSAndroid Build Coastguard Worker    DomainRFC2965Match = 4
880*cda5da8dSAndroid Build Coastguard Worker
881*cda5da8dSAndroid Build Coastguard Worker    DomainLiberal = 0
882*cda5da8dSAndroid Build Coastguard Worker    DomainStrict = DomainStrictNoDots|DomainStrictNonDomain
883*cda5da8dSAndroid Build Coastguard Worker
884*cda5da8dSAndroid Build Coastguard Worker    def __init__(self,
885*cda5da8dSAndroid Build Coastguard Worker                 blocked_domains=None, allowed_domains=None,
886*cda5da8dSAndroid Build Coastguard Worker                 netscape=True, rfc2965=False,
887*cda5da8dSAndroid Build Coastguard Worker                 rfc2109_as_netscape=None,
888*cda5da8dSAndroid Build Coastguard Worker                 hide_cookie2=False,
889*cda5da8dSAndroid Build Coastguard Worker                 strict_domain=False,
890*cda5da8dSAndroid Build Coastguard Worker                 strict_rfc2965_unverifiable=True,
891*cda5da8dSAndroid Build Coastguard Worker                 strict_ns_unverifiable=False,
892*cda5da8dSAndroid Build Coastguard Worker                 strict_ns_domain=DomainLiberal,
893*cda5da8dSAndroid Build Coastguard Worker                 strict_ns_set_initial_dollar=False,
894*cda5da8dSAndroid Build Coastguard Worker                 strict_ns_set_path=False,
895*cda5da8dSAndroid Build Coastguard Worker                 secure_protocols=("https", "wss")
896*cda5da8dSAndroid Build Coastguard Worker                 ):
897*cda5da8dSAndroid Build Coastguard Worker        """Constructor arguments should be passed as keyword arguments only."""
898*cda5da8dSAndroid Build Coastguard Worker        self.netscape = netscape
899*cda5da8dSAndroid Build Coastguard Worker        self.rfc2965 = rfc2965
900*cda5da8dSAndroid Build Coastguard Worker        self.rfc2109_as_netscape = rfc2109_as_netscape
901*cda5da8dSAndroid Build Coastguard Worker        self.hide_cookie2 = hide_cookie2
902*cda5da8dSAndroid Build Coastguard Worker        self.strict_domain = strict_domain
903*cda5da8dSAndroid Build Coastguard Worker        self.strict_rfc2965_unverifiable = strict_rfc2965_unverifiable
904*cda5da8dSAndroid Build Coastguard Worker        self.strict_ns_unverifiable = strict_ns_unverifiable
905*cda5da8dSAndroid Build Coastguard Worker        self.strict_ns_domain = strict_ns_domain
906*cda5da8dSAndroid Build Coastguard Worker        self.strict_ns_set_initial_dollar = strict_ns_set_initial_dollar
907*cda5da8dSAndroid Build Coastguard Worker        self.strict_ns_set_path = strict_ns_set_path
908*cda5da8dSAndroid Build Coastguard Worker        self.secure_protocols = secure_protocols
909*cda5da8dSAndroid Build Coastguard Worker
910*cda5da8dSAndroid Build Coastguard Worker        if blocked_domains is not None:
911*cda5da8dSAndroid Build Coastguard Worker            self._blocked_domains = tuple(blocked_domains)
912*cda5da8dSAndroid Build Coastguard Worker        else:
913*cda5da8dSAndroid Build Coastguard Worker            self._blocked_domains = ()
914*cda5da8dSAndroid Build Coastguard Worker
915*cda5da8dSAndroid Build Coastguard Worker        if allowed_domains is not None:
916*cda5da8dSAndroid Build Coastguard Worker            allowed_domains = tuple(allowed_domains)
917*cda5da8dSAndroid Build Coastguard Worker        self._allowed_domains = allowed_domains
918*cda5da8dSAndroid Build Coastguard Worker
919*cda5da8dSAndroid Build Coastguard Worker    def blocked_domains(self):
920*cda5da8dSAndroid Build Coastguard Worker        """Return the sequence of blocked domains (as a tuple)."""
921*cda5da8dSAndroid Build Coastguard Worker        return self._blocked_domains
922*cda5da8dSAndroid Build Coastguard Worker    def set_blocked_domains(self, blocked_domains):
923*cda5da8dSAndroid Build Coastguard Worker        """Set the sequence of blocked domains."""
924*cda5da8dSAndroid Build Coastguard Worker        self._blocked_domains = tuple(blocked_domains)
925*cda5da8dSAndroid Build Coastguard Worker
926*cda5da8dSAndroid Build Coastguard Worker    def is_blocked(self, domain):
927*cda5da8dSAndroid Build Coastguard Worker        for blocked_domain in self._blocked_domains:
928*cda5da8dSAndroid Build Coastguard Worker            if user_domain_match(domain, blocked_domain):
929*cda5da8dSAndroid Build Coastguard Worker                return True
930*cda5da8dSAndroid Build Coastguard Worker        return False
931*cda5da8dSAndroid Build Coastguard Worker
932*cda5da8dSAndroid Build Coastguard Worker    def allowed_domains(self):
933*cda5da8dSAndroid Build Coastguard Worker        """Return None, or the sequence of allowed domains (as a tuple)."""
934*cda5da8dSAndroid Build Coastguard Worker        return self._allowed_domains
935*cda5da8dSAndroid Build Coastguard Worker    def set_allowed_domains(self, allowed_domains):
936*cda5da8dSAndroid Build Coastguard Worker        """Set the sequence of allowed domains, or None."""
937*cda5da8dSAndroid Build Coastguard Worker        if allowed_domains is not None:
938*cda5da8dSAndroid Build Coastguard Worker            allowed_domains = tuple(allowed_domains)
939*cda5da8dSAndroid Build Coastguard Worker        self._allowed_domains = allowed_domains
940*cda5da8dSAndroid Build Coastguard Worker
941*cda5da8dSAndroid Build Coastguard Worker    def is_not_allowed(self, domain):
942*cda5da8dSAndroid Build Coastguard Worker        if self._allowed_domains is None:
943*cda5da8dSAndroid Build Coastguard Worker            return False
944*cda5da8dSAndroid Build Coastguard Worker        for allowed_domain in self._allowed_domains:
945*cda5da8dSAndroid Build Coastguard Worker            if user_domain_match(domain, allowed_domain):
946*cda5da8dSAndroid Build Coastguard Worker                return False
947*cda5da8dSAndroid Build Coastguard Worker        return True
948*cda5da8dSAndroid Build Coastguard Worker
949*cda5da8dSAndroid Build Coastguard Worker    def set_ok(self, cookie, request):
950*cda5da8dSAndroid Build Coastguard Worker        """
951*cda5da8dSAndroid Build Coastguard Worker        If you override .set_ok(), be sure to call this method.  If it returns
952*cda5da8dSAndroid Build Coastguard Worker        false, so should your subclass (assuming your subclass wants to be more
953*cda5da8dSAndroid Build Coastguard Worker        strict about which cookies to accept).
954*cda5da8dSAndroid Build Coastguard Worker
955*cda5da8dSAndroid Build Coastguard Worker        """
956*cda5da8dSAndroid Build Coastguard Worker        _debug(" - checking cookie %s=%s", cookie.name, cookie.value)
957*cda5da8dSAndroid Build Coastguard Worker
958*cda5da8dSAndroid Build Coastguard Worker        assert cookie.name is not None
959*cda5da8dSAndroid Build Coastguard Worker
960*cda5da8dSAndroid Build Coastguard Worker        for n in "version", "verifiability", "name", "path", "domain", "port":
961*cda5da8dSAndroid Build Coastguard Worker            fn_name = "set_ok_"+n
962*cda5da8dSAndroid Build Coastguard Worker            fn = getattr(self, fn_name)
963*cda5da8dSAndroid Build Coastguard Worker            if not fn(cookie, request):
964*cda5da8dSAndroid Build Coastguard Worker                return False
965*cda5da8dSAndroid Build Coastguard Worker
966*cda5da8dSAndroid Build Coastguard Worker        return True
967*cda5da8dSAndroid Build Coastguard Worker
968*cda5da8dSAndroid Build Coastguard Worker    def set_ok_version(self, cookie, request):
969*cda5da8dSAndroid Build Coastguard Worker        if cookie.version is None:
970*cda5da8dSAndroid Build Coastguard Worker            # Version is always set to 0 by parse_ns_headers if it's a Netscape
971*cda5da8dSAndroid Build Coastguard Worker            # cookie, so this must be an invalid RFC 2965 cookie.
972*cda5da8dSAndroid Build Coastguard Worker            _debug("   Set-Cookie2 without version attribute (%s=%s)",
973*cda5da8dSAndroid Build Coastguard Worker                   cookie.name, cookie.value)
974*cda5da8dSAndroid Build Coastguard Worker            return False
975*cda5da8dSAndroid Build Coastguard Worker        if cookie.version > 0 and not self.rfc2965:
976*cda5da8dSAndroid Build Coastguard Worker            _debug("   RFC 2965 cookies are switched off")
977*cda5da8dSAndroid Build Coastguard Worker            return False
978*cda5da8dSAndroid Build Coastguard Worker        elif cookie.version == 0 and not self.netscape:
979*cda5da8dSAndroid Build Coastguard Worker            _debug("   Netscape cookies are switched off")
980*cda5da8dSAndroid Build Coastguard Worker            return False
981*cda5da8dSAndroid Build Coastguard Worker        return True
982*cda5da8dSAndroid Build Coastguard Worker
983*cda5da8dSAndroid Build Coastguard Worker    def set_ok_verifiability(self, cookie, request):
984*cda5da8dSAndroid Build Coastguard Worker        if request.unverifiable and is_third_party(request):
985*cda5da8dSAndroid Build Coastguard Worker            if cookie.version > 0 and self.strict_rfc2965_unverifiable:
986*cda5da8dSAndroid Build Coastguard Worker                _debug("   third-party RFC 2965 cookie during "
987*cda5da8dSAndroid Build Coastguard Worker                             "unverifiable transaction")
988*cda5da8dSAndroid Build Coastguard Worker                return False
989*cda5da8dSAndroid Build Coastguard Worker            elif cookie.version == 0 and self.strict_ns_unverifiable:
990*cda5da8dSAndroid Build Coastguard Worker                _debug("   third-party Netscape cookie during "
991*cda5da8dSAndroid Build Coastguard Worker                             "unverifiable transaction")
992*cda5da8dSAndroid Build Coastguard Worker                return False
993*cda5da8dSAndroid Build Coastguard Worker        return True
994*cda5da8dSAndroid Build Coastguard Worker
995*cda5da8dSAndroid Build Coastguard Worker    def set_ok_name(self, cookie, request):
996*cda5da8dSAndroid Build Coastguard Worker        # Try and stop servers setting V0 cookies designed to hack other
997*cda5da8dSAndroid Build Coastguard Worker        # servers that know both V0 and V1 protocols.
998*cda5da8dSAndroid Build Coastguard Worker        if (cookie.version == 0 and self.strict_ns_set_initial_dollar and
999*cda5da8dSAndroid Build Coastguard Worker            cookie.name.startswith("$")):
1000*cda5da8dSAndroid Build Coastguard Worker            _debug("   illegal name (starts with '$'): '%s'", cookie.name)
1001*cda5da8dSAndroid Build Coastguard Worker            return False
1002*cda5da8dSAndroid Build Coastguard Worker        return True
1003*cda5da8dSAndroid Build Coastguard Worker
1004*cda5da8dSAndroid Build Coastguard Worker    def set_ok_path(self, cookie, request):
1005*cda5da8dSAndroid Build Coastguard Worker        if cookie.path_specified:
1006*cda5da8dSAndroid Build Coastguard Worker            req_path = request_path(request)
1007*cda5da8dSAndroid Build Coastguard Worker            if ((cookie.version > 0 or
1008*cda5da8dSAndroid Build Coastguard Worker                 (cookie.version == 0 and self.strict_ns_set_path)) and
1009*cda5da8dSAndroid Build Coastguard Worker                not self.path_return_ok(cookie.path, request)):
1010*cda5da8dSAndroid Build Coastguard Worker                _debug("   path attribute %s is not a prefix of request "
1011*cda5da8dSAndroid Build Coastguard Worker                       "path %s", cookie.path, req_path)
1012*cda5da8dSAndroid Build Coastguard Worker                return False
1013*cda5da8dSAndroid Build Coastguard Worker        return True
1014*cda5da8dSAndroid Build Coastguard Worker
1015*cda5da8dSAndroid Build Coastguard Worker    def set_ok_domain(self, cookie, request):
1016*cda5da8dSAndroid Build Coastguard Worker        if self.is_blocked(cookie.domain):
1017*cda5da8dSAndroid Build Coastguard Worker            _debug("   domain %s is in user block-list", cookie.domain)
1018*cda5da8dSAndroid Build Coastguard Worker            return False
1019*cda5da8dSAndroid Build Coastguard Worker        if self.is_not_allowed(cookie.domain):
1020*cda5da8dSAndroid Build Coastguard Worker            _debug("   domain %s is not in user allow-list", cookie.domain)
1021*cda5da8dSAndroid Build Coastguard Worker            return False
1022*cda5da8dSAndroid Build Coastguard Worker        if cookie.domain_specified:
1023*cda5da8dSAndroid Build Coastguard Worker            req_host, erhn = eff_request_host(request)
1024*cda5da8dSAndroid Build Coastguard Worker            domain = cookie.domain
1025*cda5da8dSAndroid Build Coastguard Worker            if self.strict_domain and (domain.count(".") >= 2):
1026*cda5da8dSAndroid Build Coastguard Worker                # XXX This should probably be compared with the Konqueror
1027*cda5da8dSAndroid Build Coastguard Worker                # (kcookiejar.cpp) and Mozilla implementations, but it's a
1028*cda5da8dSAndroid Build Coastguard Worker                # losing battle.
1029*cda5da8dSAndroid Build Coastguard Worker                i = domain.rfind(".")
1030*cda5da8dSAndroid Build Coastguard Worker                j = domain.rfind(".", 0, i)
1031*cda5da8dSAndroid Build Coastguard Worker                if j == 0:  # domain like .foo.bar
1032*cda5da8dSAndroid Build Coastguard Worker                    tld = domain[i+1:]
1033*cda5da8dSAndroid Build Coastguard Worker                    sld = domain[j+1:i]
1034*cda5da8dSAndroid Build Coastguard Worker                    if sld.lower() in ("co", "ac", "com", "edu", "org", "net",
1035*cda5da8dSAndroid Build Coastguard Worker                       "gov", "mil", "int", "aero", "biz", "cat", "coop",
1036*cda5da8dSAndroid Build Coastguard Worker                       "info", "jobs", "mobi", "museum", "name", "pro",
1037*cda5da8dSAndroid Build Coastguard Worker                       "travel", "eu") and len(tld) == 2:
1038*cda5da8dSAndroid Build Coastguard Worker                        # domain like .co.uk
1039*cda5da8dSAndroid Build Coastguard Worker                        _debug("   country-code second level domain %s", domain)
1040*cda5da8dSAndroid Build Coastguard Worker                        return False
1041*cda5da8dSAndroid Build Coastguard Worker            if domain.startswith("."):
1042*cda5da8dSAndroid Build Coastguard Worker                undotted_domain = domain[1:]
1043*cda5da8dSAndroid Build Coastguard Worker            else:
1044*cda5da8dSAndroid Build Coastguard Worker                undotted_domain = domain
1045*cda5da8dSAndroid Build Coastguard Worker            embedded_dots = (undotted_domain.find(".") >= 0)
1046*cda5da8dSAndroid Build Coastguard Worker            if not embedded_dots and not erhn.endswith(".local"):
1047*cda5da8dSAndroid Build Coastguard Worker                _debug("   non-local domain %s contains no embedded dot",
1048*cda5da8dSAndroid Build Coastguard Worker                       domain)
1049*cda5da8dSAndroid Build Coastguard Worker                return False
1050*cda5da8dSAndroid Build Coastguard Worker            if cookie.version == 0:
1051*cda5da8dSAndroid Build Coastguard Worker                if (not (erhn.endswith(domain) or
1052*cda5da8dSAndroid Build Coastguard Worker                         erhn.endswith(f"{undotted_domain}.local")) and
1053*cda5da8dSAndroid Build Coastguard Worker                    (not erhn.startswith(".") and
1054*cda5da8dSAndroid Build Coastguard Worker                     not ("."+erhn).endswith(domain))):
1055*cda5da8dSAndroid Build Coastguard Worker                    _debug("   effective request-host %s (even with added "
1056*cda5da8dSAndroid Build Coastguard Worker                           "initial dot) does not end with %s",
1057*cda5da8dSAndroid Build Coastguard Worker                           erhn, domain)
1058*cda5da8dSAndroid Build Coastguard Worker                    return False
1059*cda5da8dSAndroid Build Coastguard Worker            if (cookie.version > 0 or
1060*cda5da8dSAndroid Build Coastguard Worker                (self.strict_ns_domain & self.DomainRFC2965Match)):
1061*cda5da8dSAndroid Build Coastguard Worker                if not domain_match(erhn, domain):
1062*cda5da8dSAndroid Build Coastguard Worker                    _debug("   effective request-host %s does not domain-match "
1063*cda5da8dSAndroid Build Coastguard Worker                           "%s", erhn, domain)
1064*cda5da8dSAndroid Build Coastguard Worker                    return False
1065*cda5da8dSAndroid Build Coastguard Worker            if (cookie.version > 0 or
1066*cda5da8dSAndroid Build Coastguard Worker                (self.strict_ns_domain & self.DomainStrictNoDots)):
1067*cda5da8dSAndroid Build Coastguard Worker                host_prefix = req_host[:-len(domain)]
1068*cda5da8dSAndroid Build Coastguard Worker                if (host_prefix.find(".") >= 0 and
1069*cda5da8dSAndroid Build Coastguard Worker                    not IPV4_RE.search(req_host)):
1070*cda5da8dSAndroid Build Coastguard Worker                    _debug("   host prefix %s for domain %s contains a dot",
1071*cda5da8dSAndroid Build Coastguard Worker                           host_prefix, domain)
1072*cda5da8dSAndroid Build Coastguard Worker                    return False
1073*cda5da8dSAndroid Build Coastguard Worker        return True
1074*cda5da8dSAndroid Build Coastguard Worker
1075*cda5da8dSAndroid Build Coastguard Worker    def set_ok_port(self, cookie, request):
1076*cda5da8dSAndroid Build Coastguard Worker        if cookie.port_specified:
1077*cda5da8dSAndroid Build Coastguard Worker            req_port = request_port(request)
1078*cda5da8dSAndroid Build Coastguard Worker            if req_port is None:
1079*cda5da8dSAndroid Build Coastguard Worker                req_port = "80"
1080*cda5da8dSAndroid Build Coastguard Worker            else:
1081*cda5da8dSAndroid Build Coastguard Worker                req_port = str(req_port)
1082*cda5da8dSAndroid Build Coastguard Worker            for p in cookie.port.split(","):
1083*cda5da8dSAndroid Build Coastguard Worker                try:
1084*cda5da8dSAndroid Build Coastguard Worker                    int(p)
1085*cda5da8dSAndroid Build Coastguard Worker                except ValueError:
1086*cda5da8dSAndroid Build Coastguard Worker                    _debug("   bad port %s (not numeric)", p)
1087*cda5da8dSAndroid Build Coastguard Worker                    return False
1088*cda5da8dSAndroid Build Coastguard Worker                if p == req_port:
1089*cda5da8dSAndroid Build Coastguard Worker                    break
1090*cda5da8dSAndroid Build Coastguard Worker            else:
1091*cda5da8dSAndroid Build Coastguard Worker                _debug("   request port (%s) not found in %s",
1092*cda5da8dSAndroid Build Coastguard Worker                       req_port, cookie.port)
1093*cda5da8dSAndroid Build Coastguard Worker                return False
1094*cda5da8dSAndroid Build Coastguard Worker        return True
1095*cda5da8dSAndroid Build Coastguard Worker
1096*cda5da8dSAndroid Build Coastguard Worker    def return_ok(self, cookie, request):
1097*cda5da8dSAndroid Build Coastguard Worker        """
1098*cda5da8dSAndroid Build Coastguard Worker        If you override .return_ok(), be sure to call this method.  If it
1099*cda5da8dSAndroid Build Coastguard Worker        returns false, so should your subclass (assuming your subclass wants to
1100*cda5da8dSAndroid Build Coastguard Worker        be more strict about which cookies to return).
1101*cda5da8dSAndroid Build Coastguard Worker
1102*cda5da8dSAndroid Build Coastguard Worker        """
1103*cda5da8dSAndroid Build Coastguard Worker        # Path has already been checked by .path_return_ok(), and domain
1104*cda5da8dSAndroid Build Coastguard Worker        # blocking done by .domain_return_ok().
1105*cda5da8dSAndroid Build Coastguard Worker        _debug(" - checking cookie %s=%s", cookie.name, cookie.value)
1106*cda5da8dSAndroid Build Coastguard Worker
1107*cda5da8dSAndroid Build Coastguard Worker        for n in "version", "verifiability", "secure", "expires", "port", "domain":
1108*cda5da8dSAndroid Build Coastguard Worker            fn_name = "return_ok_"+n
1109*cda5da8dSAndroid Build Coastguard Worker            fn = getattr(self, fn_name)
1110*cda5da8dSAndroid Build Coastguard Worker            if not fn(cookie, request):
1111*cda5da8dSAndroid Build Coastguard Worker                return False
1112*cda5da8dSAndroid Build Coastguard Worker        return True
1113*cda5da8dSAndroid Build Coastguard Worker
1114*cda5da8dSAndroid Build Coastguard Worker    def return_ok_version(self, cookie, request):
1115*cda5da8dSAndroid Build Coastguard Worker        if cookie.version > 0 and not self.rfc2965:
1116*cda5da8dSAndroid Build Coastguard Worker            _debug("   RFC 2965 cookies are switched off")
1117*cda5da8dSAndroid Build Coastguard Worker            return False
1118*cda5da8dSAndroid Build Coastguard Worker        elif cookie.version == 0 and not self.netscape:
1119*cda5da8dSAndroid Build Coastguard Worker            _debug("   Netscape cookies are switched off")
1120*cda5da8dSAndroid Build Coastguard Worker            return False
1121*cda5da8dSAndroid Build Coastguard Worker        return True
1122*cda5da8dSAndroid Build Coastguard Worker
1123*cda5da8dSAndroid Build Coastguard Worker    def return_ok_verifiability(self, cookie, request):
1124*cda5da8dSAndroid Build Coastguard Worker        if request.unverifiable and is_third_party(request):
1125*cda5da8dSAndroid Build Coastguard Worker            if cookie.version > 0 and self.strict_rfc2965_unverifiable:
1126*cda5da8dSAndroid Build Coastguard Worker                _debug("   third-party RFC 2965 cookie during unverifiable "
1127*cda5da8dSAndroid Build Coastguard Worker                       "transaction")
1128*cda5da8dSAndroid Build Coastguard Worker                return False
1129*cda5da8dSAndroid Build Coastguard Worker            elif cookie.version == 0 and self.strict_ns_unverifiable:
1130*cda5da8dSAndroid Build Coastguard Worker                _debug("   third-party Netscape cookie during unverifiable "
1131*cda5da8dSAndroid Build Coastguard Worker                       "transaction")
1132*cda5da8dSAndroid Build Coastguard Worker                return False
1133*cda5da8dSAndroid Build Coastguard Worker        return True
1134*cda5da8dSAndroid Build Coastguard Worker
1135*cda5da8dSAndroid Build Coastguard Worker    def return_ok_secure(self, cookie, request):
1136*cda5da8dSAndroid Build Coastguard Worker        if cookie.secure and request.type not in self.secure_protocols:
1137*cda5da8dSAndroid Build Coastguard Worker            _debug("   secure cookie with non-secure request")
1138*cda5da8dSAndroid Build Coastguard Worker            return False
1139*cda5da8dSAndroid Build Coastguard Worker        return True
1140*cda5da8dSAndroid Build Coastguard Worker
1141*cda5da8dSAndroid Build Coastguard Worker    def return_ok_expires(self, cookie, request):
1142*cda5da8dSAndroid Build Coastguard Worker        if cookie.is_expired(self._now):
1143*cda5da8dSAndroid Build Coastguard Worker            _debug("   cookie expired")
1144*cda5da8dSAndroid Build Coastguard Worker            return False
1145*cda5da8dSAndroid Build Coastguard Worker        return True
1146*cda5da8dSAndroid Build Coastguard Worker
1147*cda5da8dSAndroid Build Coastguard Worker    def return_ok_port(self, cookie, request):
1148*cda5da8dSAndroid Build Coastguard Worker        if cookie.port:
1149*cda5da8dSAndroid Build Coastguard Worker            req_port = request_port(request)
1150*cda5da8dSAndroid Build Coastguard Worker            if req_port is None:
1151*cda5da8dSAndroid Build Coastguard Worker                req_port = "80"
1152*cda5da8dSAndroid Build Coastguard Worker            for p in cookie.port.split(","):
1153*cda5da8dSAndroid Build Coastguard Worker                if p == req_port:
1154*cda5da8dSAndroid Build Coastguard Worker                    break
1155*cda5da8dSAndroid Build Coastguard Worker            else:
1156*cda5da8dSAndroid Build Coastguard Worker                _debug("   request port %s does not match cookie port %s",
1157*cda5da8dSAndroid Build Coastguard Worker                       req_port, cookie.port)
1158*cda5da8dSAndroid Build Coastguard Worker                return False
1159*cda5da8dSAndroid Build Coastguard Worker        return True
1160*cda5da8dSAndroid Build Coastguard Worker
1161*cda5da8dSAndroid Build Coastguard Worker    def return_ok_domain(self, cookie, request):
1162*cda5da8dSAndroid Build Coastguard Worker        req_host, erhn = eff_request_host(request)
1163*cda5da8dSAndroid Build Coastguard Worker        domain = cookie.domain
1164*cda5da8dSAndroid Build Coastguard Worker
1165*cda5da8dSAndroid Build Coastguard Worker        if domain and not domain.startswith("."):
1166*cda5da8dSAndroid Build Coastguard Worker            dotdomain = "." + domain
1167*cda5da8dSAndroid Build Coastguard Worker        else:
1168*cda5da8dSAndroid Build Coastguard Worker            dotdomain = domain
1169*cda5da8dSAndroid Build Coastguard Worker
1170*cda5da8dSAndroid Build Coastguard Worker        # strict check of non-domain cookies: Mozilla does this, MSIE5 doesn't
1171*cda5da8dSAndroid Build Coastguard Worker        if (cookie.version == 0 and
1172*cda5da8dSAndroid Build Coastguard Worker            (self.strict_ns_domain & self.DomainStrictNonDomain) and
1173*cda5da8dSAndroid Build Coastguard Worker            not cookie.domain_specified and domain != erhn):
1174*cda5da8dSAndroid Build Coastguard Worker            _debug("   cookie with unspecified domain does not string-compare "
1175*cda5da8dSAndroid Build Coastguard Worker                   "equal to request domain")
1176*cda5da8dSAndroid Build Coastguard Worker            return False
1177*cda5da8dSAndroid Build Coastguard Worker
1178*cda5da8dSAndroid Build Coastguard Worker        if cookie.version > 0 and not domain_match(erhn, domain):
1179*cda5da8dSAndroid Build Coastguard Worker            _debug("   effective request-host name %s does not domain-match "
1180*cda5da8dSAndroid Build Coastguard Worker                   "RFC 2965 cookie domain %s", erhn, domain)
1181*cda5da8dSAndroid Build Coastguard Worker            return False
1182*cda5da8dSAndroid Build Coastguard Worker        if cookie.version == 0 and not ("."+erhn).endswith(dotdomain):
1183*cda5da8dSAndroid Build Coastguard Worker            _debug("   request-host %s does not match Netscape cookie domain "
1184*cda5da8dSAndroid Build Coastguard Worker                   "%s", req_host, domain)
1185*cda5da8dSAndroid Build Coastguard Worker            return False
1186*cda5da8dSAndroid Build Coastguard Worker        return True
1187*cda5da8dSAndroid Build Coastguard Worker
1188*cda5da8dSAndroid Build Coastguard Worker    def domain_return_ok(self, domain, request):
1189*cda5da8dSAndroid Build Coastguard Worker        # Liberal check of.  This is here as an optimization to avoid
1190*cda5da8dSAndroid Build Coastguard Worker        # having to load lots of MSIE cookie files unless necessary.
1191*cda5da8dSAndroid Build Coastguard Worker        req_host, erhn = eff_request_host(request)
1192*cda5da8dSAndroid Build Coastguard Worker        if not req_host.startswith("."):
1193*cda5da8dSAndroid Build Coastguard Worker            req_host = "."+req_host
1194*cda5da8dSAndroid Build Coastguard Worker        if not erhn.startswith("."):
1195*cda5da8dSAndroid Build Coastguard Worker            erhn = "."+erhn
1196*cda5da8dSAndroid Build Coastguard Worker        if domain and not domain.startswith("."):
1197*cda5da8dSAndroid Build Coastguard Worker            dotdomain = "." + domain
1198*cda5da8dSAndroid Build Coastguard Worker        else:
1199*cda5da8dSAndroid Build Coastguard Worker            dotdomain = domain
1200*cda5da8dSAndroid Build Coastguard Worker        if not (req_host.endswith(dotdomain) or erhn.endswith(dotdomain)):
1201*cda5da8dSAndroid Build Coastguard Worker            #_debug("   request domain %s does not match cookie domain %s",
1202*cda5da8dSAndroid Build Coastguard Worker            #       req_host, domain)
1203*cda5da8dSAndroid Build Coastguard Worker            return False
1204*cda5da8dSAndroid Build Coastguard Worker
1205*cda5da8dSAndroid Build Coastguard Worker        if self.is_blocked(domain):
1206*cda5da8dSAndroid Build Coastguard Worker            _debug("   domain %s is in user block-list", domain)
1207*cda5da8dSAndroid Build Coastguard Worker            return False
1208*cda5da8dSAndroid Build Coastguard Worker        if self.is_not_allowed(domain):
1209*cda5da8dSAndroid Build Coastguard Worker            _debug("   domain %s is not in user allow-list", domain)
1210*cda5da8dSAndroid Build Coastguard Worker            return False
1211*cda5da8dSAndroid Build Coastguard Worker
1212*cda5da8dSAndroid Build Coastguard Worker        return True
1213*cda5da8dSAndroid Build Coastguard Worker
1214*cda5da8dSAndroid Build Coastguard Worker    def path_return_ok(self, path, request):
1215*cda5da8dSAndroid Build Coastguard Worker        _debug("- checking cookie path=%s", path)
1216*cda5da8dSAndroid Build Coastguard Worker        req_path = request_path(request)
1217*cda5da8dSAndroid Build Coastguard Worker        pathlen = len(path)
1218*cda5da8dSAndroid Build Coastguard Worker        if req_path == path:
1219*cda5da8dSAndroid Build Coastguard Worker            return True
1220*cda5da8dSAndroid Build Coastguard Worker        elif (req_path.startswith(path) and
1221*cda5da8dSAndroid Build Coastguard Worker              (path.endswith("/") or req_path[pathlen:pathlen+1] == "/")):
1222*cda5da8dSAndroid Build Coastguard Worker            return True
1223*cda5da8dSAndroid Build Coastguard Worker
1224*cda5da8dSAndroid Build Coastguard Worker        _debug("  %s does not path-match %s", req_path, path)
1225*cda5da8dSAndroid Build Coastguard Worker        return False
1226*cda5da8dSAndroid Build Coastguard Worker
1227*cda5da8dSAndroid Build Coastguard Workerdef deepvalues(mapping):
1228*cda5da8dSAndroid Build Coastguard Worker    """Iterates over nested mapping, depth-first"""
1229*cda5da8dSAndroid Build Coastguard Worker    for obj in list(mapping.values()):
1230*cda5da8dSAndroid Build Coastguard Worker        mapping = False
1231*cda5da8dSAndroid Build Coastguard Worker        try:
1232*cda5da8dSAndroid Build Coastguard Worker            obj.items
1233*cda5da8dSAndroid Build Coastguard Worker        except AttributeError:
1234*cda5da8dSAndroid Build Coastguard Worker            pass
1235*cda5da8dSAndroid Build Coastguard Worker        else:
1236*cda5da8dSAndroid Build Coastguard Worker            mapping = True
1237*cda5da8dSAndroid Build Coastguard Worker            yield from deepvalues(obj)
1238*cda5da8dSAndroid Build Coastguard Worker        if not mapping:
1239*cda5da8dSAndroid Build Coastguard Worker            yield obj
1240*cda5da8dSAndroid Build Coastguard Worker
1241*cda5da8dSAndroid Build Coastguard Worker
1242*cda5da8dSAndroid Build Coastguard Worker# Used as second parameter to dict.get() method, to distinguish absent
1243*cda5da8dSAndroid Build Coastguard Worker# dict key from one with a None value.
1244*cda5da8dSAndroid Build Coastguard Workerclass Absent: pass
1245*cda5da8dSAndroid Build Coastguard Worker
1246*cda5da8dSAndroid Build Coastguard Workerclass CookieJar:
1247*cda5da8dSAndroid Build Coastguard Worker    """Collection of HTTP cookies.
1248*cda5da8dSAndroid Build Coastguard Worker
1249*cda5da8dSAndroid Build Coastguard Worker    You may not need to know about this class: try
1250*cda5da8dSAndroid Build Coastguard Worker    urllib.request.build_opener(HTTPCookieProcessor).open(url).
1251*cda5da8dSAndroid Build Coastguard Worker    """
1252*cda5da8dSAndroid Build Coastguard Worker
1253*cda5da8dSAndroid Build Coastguard Worker    non_word_re = re.compile(r"\W")
1254*cda5da8dSAndroid Build Coastguard Worker    quote_re = re.compile(r"([\"\\])")
1255*cda5da8dSAndroid Build Coastguard Worker    strict_domain_re = re.compile(r"\.?[^.]*")
1256*cda5da8dSAndroid Build Coastguard Worker    domain_re = re.compile(r"[^.]*")
1257*cda5da8dSAndroid Build Coastguard Worker    dots_re = re.compile(r"^\.+")
1258*cda5da8dSAndroid Build Coastguard Worker
1259*cda5da8dSAndroid Build Coastguard Worker    magic_re = re.compile(r"^\#LWP-Cookies-(\d+\.\d+)", re.ASCII)
1260*cda5da8dSAndroid Build Coastguard Worker
1261*cda5da8dSAndroid Build Coastguard Worker    def __init__(self, policy=None):
1262*cda5da8dSAndroid Build Coastguard Worker        if policy is None:
1263*cda5da8dSAndroid Build Coastguard Worker            policy = DefaultCookiePolicy()
1264*cda5da8dSAndroid Build Coastguard Worker        self._policy = policy
1265*cda5da8dSAndroid Build Coastguard Worker
1266*cda5da8dSAndroid Build Coastguard Worker        self._cookies_lock = _threading.RLock()
1267*cda5da8dSAndroid Build Coastguard Worker        self._cookies = {}
1268*cda5da8dSAndroid Build Coastguard Worker
1269*cda5da8dSAndroid Build Coastguard Worker    def set_policy(self, policy):
1270*cda5da8dSAndroid Build Coastguard Worker        self._policy = policy
1271*cda5da8dSAndroid Build Coastguard Worker
1272*cda5da8dSAndroid Build Coastguard Worker    def _cookies_for_domain(self, domain, request):
1273*cda5da8dSAndroid Build Coastguard Worker        cookies = []
1274*cda5da8dSAndroid Build Coastguard Worker        if not self._policy.domain_return_ok(domain, request):
1275*cda5da8dSAndroid Build Coastguard Worker            return []
1276*cda5da8dSAndroid Build Coastguard Worker        _debug("Checking %s for cookies to return", domain)
1277*cda5da8dSAndroid Build Coastguard Worker        cookies_by_path = self._cookies[domain]
1278*cda5da8dSAndroid Build Coastguard Worker        for path in cookies_by_path.keys():
1279*cda5da8dSAndroid Build Coastguard Worker            if not self._policy.path_return_ok(path, request):
1280*cda5da8dSAndroid Build Coastguard Worker                continue
1281*cda5da8dSAndroid Build Coastguard Worker            cookies_by_name = cookies_by_path[path]
1282*cda5da8dSAndroid Build Coastguard Worker            for cookie in cookies_by_name.values():
1283*cda5da8dSAndroid Build Coastguard Worker                if not self._policy.return_ok(cookie, request):
1284*cda5da8dSAndroid Build Coastguard Worker                    _debug("   not returning cookie")
1285*cda5da8dSAndroid Build Coastguard Worker                    continue
1286*cda5da8dSAndroid Build Coastguard Worker                _debug("   it's a match")
1287*cda5da8dSAndroid Build Coastguard Worker                cookies.append(cookie)
1288*cda5da8dSAndroid Build Coastguard Worker        return cookies
1289*cda5da8dSAndroid Build Coastguard Worker
1290*cda5da8dSAndroid Build Coastguard Worker    def _cookies_for_request(self, request):
1291*cda5da8dSAndroid Build Coastguard Worker        """Return a list of cookies to be returned to server."""
1292*cda5da8dSAndroid Build Coastguard Worker        cookies = []
1293*cda5da8dSAndroid Build Coastguard Worker        for domain in self._cookies.keys():
1294*cda5da8dSAndroid Build Coastguard Worker            cookies.extend(self._cookies_for_domain(domain, request))
1295*cda5da8dSAndroid Build Coastguard Worker        return cookies
1296*cda5da8dSAndroid Build Coastguard Worker
1297*cda5da8dSAndroid Build Coastguard Worker    def _cookie_attrs(self, cookies):
1298*cda5da8dSAndroid Build Coastguard Worker        """Return a list of cookie-attributes to be returned to server.
1299*cda5da8dSAndroid Build Coastguard Worker
1300*cda5da8dSAndroid Build Coastguard Worker        like ['foo="bar"; $Path="/"', ...]
1301*cda5da8dSAndroid Build Coastguard Worker
1302*cda5da8dSAndroid Build Coastguard Worker        The $Version attribute is also added when appropriate (currently only
1303*cda5da8dSAndroid Build Coastguard Worker        once per request).
1304*cda5da8dSAndroid Build Coastguard Worker
1305*cda5da8dSAndroid Build Coastguard Worker        """
1306*cda5da8dSAndroid Build Coastguard Worker        # add cookies in order of most specific (ie. longest) path first
1307*cda5da8dSAndroid Build Coastguard Worker        cookies.sort(key=lambda a: len(a.path), reverse=True)
1308*cda5da8dSAndroid Build Coastguard Worker
1309*cda5da8dSAndroid Build Coastguard Worker        version_set = False
1310*cda5da8dSAndroid Build Coastguard Worker
1311*cda5da8dSAndroid Build Coastguard Worker        attrs = []
1312*cda5da8dSAndroid Build Coastguard Worker        for cookie in cookies:
1313*cda5da8dSAndroid Build Coastguard Worker            # set version of Cookie header
1314*cda5da8dSAndroid Build Coastguard Worker            # XXX
1315*cda5da8dSAndroid Build Coastguard Worker            # What should it be if multiple matching Set-Cookie headers have
1316*cda5da8dSAndroid Build Coastguard Worker            #  different versions themselves?
1317*cda5da8dSAndroid Build Coastguard Worker            # Answer: there is no answer; was supposed to be settled by
1318*cda5da8dSAndroid Build Coastguard Worker            #  RFC 2965 errata, but that may never appear...
1319*cda5da8dSAndroid Build Coastguard Worker            version = cookie.version
1320*cda5da8dSAndroid Build Coastguard Worker            if not version_set:
1321*cda5da8dSAndroid Build Coastguard Worker                version_set = True
1322*cda5da8dSAndroid Build Coastguard Worker                if version > 0:
1323*cda5da8dSAndroid Build Coastguard Worker                    attrs.append("$Version=%s" % version)
1324*cda5da8dSAndroid Build Coastguard Worker
1325*cda5da8dSAndroid Build Coastguard Worker            # quote cookie value if necessary
1326*cda5da8dSAndroid Build Coastguard Worker            # (not for Netscape protocol, which already has any quotes
1327*cda5da8dSAndroid Build Coastguard Worker            #  intact, due to the poorly-specified Netscape Cookie: syntax)
1328*cda5da8dSAndroid Build Coastguard Worker            if ((cookie.value is not None) and
1329*cda5da8dSAndroid Build Coastguard Worker                self.non_word_re.search(cookie.value) and version > 0):
1330*cda5da8dSAndroid Build Coastguard Worker                value = self.quote_re.sub(r"\\\1", cookie.value)
1331*cda5da8dSAndroid Build Coastguard Worker            else:
1332*cda5da8dSAndroid Build Coastguard Worker                value = cookie.value
1333*cda5da8dSAndroid Build Coastguard Worker
1334*cda5da8dSAndroid Build Coastguard Worker            # add cookie-attributes to be returned in Cookie header
1335*cda5da8dSAndroid Build Coastguard Worker            if cookie.value is None:
1336*cda5da8dSAndroid Build Coastguard Worker                attrs.append(cookie.name)
1337*cda5da8dSAndroid Build Coastguard Worker            else:
1338*cda5da8dSAndroid Build Coastguard Worker                attrs.append("%s=%s" % (cookie.name, value))
1339*cda5da8dSAndroid Build Coastguard Worker            if version > 0:
1340*cda5da8dSAndroid Build Coastguard Worker                if cookie.path_specified:
1341*cda5da8dSAndroid Build Coastguard Worker                    attrs.append('$Path="%s"' % cookie.path)
1342*cda5da8dSAndroid Build Coastguard Worker                if cookie.domain.startswith("."):
1343*cda5da8dSAndroid Build Coastguard Worker                    domain = cookie.domain
1344*cda5da8dSAndroid Build Coastguard Worker                    if (not cookie.domain_initial_dot and
1345*cda5da8dSAndroid Build Coastguard Worker                        domain.startswith(".")):
1346*cda5da8dSAndroid Build Coastguard Worker                        domain = domain[1:]
1347*cda5da8dSAndroid Build Coastguard Worker                    attrs.append('$Domain="%s"' % domain)
1348*cda5da8dSAndroid Build Coastguard Worker                if cookie.port is not None:
1349*cda5da8dSAndroid Build Coastguard Worker                    p = "$Port"
1350*cda5da8dSAndroid Build Coastguard Worker                    if cookie.port_specified:
1351*cda5da8dSAndroid Build Coastguard Worker                        p = p + ('="%s"' % cookie.port)
1352*cda5da8dSAndroid Build Coastguard Worker                    attrs.append(p)
1353*cda5da8dSAndroid Build Coastguard Worker
1354*cda5da8dSAndroid Build Coastguard Worker        return attrs
1355*cda5da8dSAndroid Build Coastguard Worker
1356*cda5da8dSAndroid Build Coastguard Worker    def add_cookie_header(self, request):
1357*cda5da8dSAndroid Build Coastguard Worker        """Add correct Cookie: header to request (urllib.request.Request object).
1358*cda5da8dSAndroid Build Coastguard Worker
1359*cda5da8dSAndroid Build Coastguard Worker        The Cookie2 header is also added unless policy.hide_cookie2 is true.
1360*cda5da8dSAndroid Build Coastguard Worker
1361*cda5da8dSAndroid Build Coastguard Worker        """
1362*cda5da8dSAndroid Build Coastguard Worker        _debug("add_cookie_header")
1363*cda5da8dSAndroid Build Coastguard Worker        self._cookies_lock.acquire()
1364*cda5da8dSAndroid Build Coastguard Worker        try:
1365*cda5da8dSAndroid Build Coastguard Worker
1366*cda5da8dSAndroid Build Coastguard Worker            self._policy._now = self._now = int(time.time())
1367*cda5da8dSAndroid Build Coastguard Worker
1368*cda5da8dSAndroid Build Coastguard Worker            cookies = self._cookies_for_request(request)
1369*cda5da8dSAndroid Build Coastguard Worker
1370*cda5da8dSAndroid Build Coastguard Worker            attrs = self._cookie_attrs(cookies)
1371*cda5da8dSAndroid Build Coastguard Worker            if attrs:
1372*cda5da8dSAndroid Build Coastguard Worker                if not request.has_header("Cookie"):
1373*cda5da8dSAndroid Build Coastguard Worker                    request.add_unredirected_header(
1374*cda5da8dSAndroid Build Coastguard Worker                        "Cookie", "; ".join(attrs))
1375*cda5da8dSAndroid Build Coastguard Worker
1376*cda5da8dSAndroid Build Coastguard Worker            # if necessary, advertise that we know RFC 2965
1377*cda5da8dSAndroid Build Coastguard Worker            if (self._policy.rfc2965 and not self._policy.hide_cookie2 and
1378*cda5da8dSAndroid Build Coastguard Worker                not request.has_header("Cookie2")):
1379*cda5da8dSAndroid Build Coastguard Worker                for cookie in cookies:
1380*cda5da8dSAndroid Build Coastguard Worker                    if cookie.version != 1:
1381*cda5da8dSAndroid Build Coastguard Worker                        request.add_unredirected_header("Cookie2", '$Version="1"')
1382*cda5da8dSAndroid Build Coastguard Worker                        break
1383*cda5da8dSAndroid Build Coastguard Worker
1384*cda5da8dSAndroid Build Coastguard Worker        finally:
1385*cda5da8dSAndroid Build Coastguard Worker            self._cookies_lock.release()
1386*cda5da8dSAndroid Build Coastguard Worker
1387*cda5da8dSAndroid Build Coastguard Worker        self.clear_expired_cookies()
1388*cda5da8dSAndroid Build Coastguard Worker
1389*cda5da8dSAndroid Build Coastguard Worker    def _normalized_cookie_tuples(self, attrs_set):
1390*cda5da8dSAndroid Build Coastguard Worker        """Return list of tuples containing normalised cookie information.
1391*cda5da8dSAndroid Build Coastguard Worker
1392*cda5da8dSAndroid Build Coastguard Worker        attrs_set is the list of lists of key,value pairs extracted from
1393*cda5da8dSAndroid Build Coastguard Worker        the Set-Cookie or Set-Cookie2 headers.
1394*cda5da8dSAndroid Build Coastguard Worker
1395*cda5da8dSAndroid Build Coastguard Worker        Tuples are name, value, standard, rest, where name and value are the
1396*cda5da8dSAndroid Build Coastguard Worker        cookie name and value, standard is a dictionary containing the standard
1397*cda5da8dSAndroid Build Coastguard Worker        cookie-attributes (discard, secure, version, expires or max-age,
1398*cda5da8dSAndroid Build Coastguard Worker        domain, path and port) and rest is a dictionary containing the rest of
1399*cda5da8dSAndroid Build Coastguard Worker        the cookie-attributes.
1400*cda5da8dSAndroid Build Coastguard Worker
1401*cda5da8dSAndroid Build Coastguard Worker        """
1402*cda5da8dSAndroid Build Coastguard Worker        cookie_tuples = []
1403*cda5da8dSAndroid Build Coastguard Worker
1404*cda5da8dSAndroid Build Coastguard Worker        boolean_attrs = "discard", "secure"
1405*cda5da8dSAndroid Build Coastguard Worker        value_attrs = ("version",
1406*cda5da8dSAndroid Build Coastguard Worker                       "expires", "max-age",
1407*cda5da8dSAndroid Build Coastguard Worker                       "domain", "path", "port",
1408*cda5da8dSAndroid Build Coastguard Worker                       "comment", "commenturl")
1409*cda5da8dSAndroid Build Coastguard Worker
1410*cda5da8dSAndroid Build Coastguard Worker        for cookie_attrs in attrs_set:
1411*cda5da8dSAndroid Build Coastguard Worker            name, value = cookie_attrs[0]
1412*cda5da8dSAndroid Build Coastguard Worker
1413*cda5da8dSAndroid Build Coastguard Worker            # Build dictionary of standard cookie-attributes (standard) and
1414*cda5da8dSAndroid Build Coastguard Worker            # dictionary of other cookie-attributes (rest).
1415*cda5da8dSAndroid Build Coastguard Worker
1416*cda5da8dSAndroid Build Coastguard Worker            # Note: expiry time is normalised to seconds since epoch.  V0
1417*cda5da8dSAndroid Build Coastguard Worker            # cookies should have the Expires cookie-attribute, and V1 cookies
1418*cda5da8dSAndroid Build Coastguard Worker            # should have Max-Age, but since V1 includes RFC 2109 cookies (and
1419*cda5da8dSAndroid Build Coastguard Worker            # since V0 cookies may be a mish-mash of Netscape and RFC 2109), we
1420*cda5da8dSAndroid Build Coastguard Worker            # accept either (but prefer Max-Age).
1421*cda5da8dSAndroid Build Coastguard Worker            max_age_set = False
1422*cda5da8dSAndroid Build Coastguard Worker
1423*cda5da8dSAndroid Build Coastguard Worker            bad_cookie = False
1424*cda5da8dSAndroid Build Coastguard Worker
1425*cda5da8dSAndroid Build Coastguard Worker            standard = {}
1426*cda5da8dSAndroid Build Coastguard Worker            rest = {}
1427*cda5da8dSAndroid Build Coastguard Worker            for k, v in cookie_attrs[1:]:
1428*cda5da8dSAndroid Build Coastguard Worker                lc = k.lower()
1429*cda5da8dSAndroid Build Coastguard Worker                # don't lose case distinction for unknown fields
1430*cda5da8dSAndroid Build Coastguard Worker                if lc in value_attrs or lc in boolean_attrs:
1431*cda5da8dSAndroid Build Coastguard Worker                    k = lc
1432*cda5da8dSAndroid Build Coastguard Worker                if k in boolean_attrs and v is None:
1433*cda5da8dSAndroid Build Coastguard Worker                    # boolean cookie-attribute is present, but has no value
1434*cda5da8dSAndroid Build Coastguard Worker                    # (like "discard", rather than "port=80")
1435*cda5da8dSAndroid Build Coastguard Worker                    v = True
1436*cda5da8dSAndroid Build Coastguard Worker                if k in standard:
1437*cda5da8dSAndroid Build Coastguard Worker                    # only first value is significant
1438*cda5da8dSAndroid Build Coastguard Worker                    continue
1439*cda5da8dSAndroid Build Coastguard Worker                if k == "domain":
1440*cda5da8dSAndroid Build Coastguard Worker                    if v is None:
1441*cda5da8dSAndroid Build Coastguard Worker                        _debug("   missing value for domain attribute")
1442*cda5da8dSAndroid Build Coastguard Worker                        bad_cookie = True
1443*cda5da8dSAndroid Build Coastguard Worker                        break
1444*cda5da8dSAndroid Build Coastguard Worker                    # RFC 2965 section 3.3.3
1445*cda5da8dSAndroid Build Coastguard Worker                    v = v.lower()
1446*cda5da8dSAndroid Build Coastguard Worker                if k == "expires":
1447*cda5da8dSAndroid Build Coastguard Worker                    if max_age_set:
1448*cda5da8dSAndroid Build Coastguard Worker                        # Prefer max-age to expires (like Mozilla)
1449*cda5da8dSAndroid Build Coastguard Worker                        continue
1450*cda5da8dSAndroid Build Coastguard Worker                    if v is None:
1451*cda5da8dSAndroid Build Coastguard Worker                        _debug("   missing or invalid value for expires "
1452*cda5da8dSAndroid Build Coastguard Worker                              "attribute: treating as session cookie")
1453*cda5da8dSAndroid Build Coastguard Worker                        continue
1454*cda5da8dSAndroid Build Coastguard Worker                if k == "max-age":
1455*cda5da8dSAndroid Build Coastguard Worker                    max_age_set = True
1456*cda5da8dSAndroid Build Coastguard Worker                    try:
1457*cda5da8dSAndroid Build Coastguard Worker                        v = int(v)
1458*cda5da8dSAndroid Build Coastguard Worker                    except ValueError:
1459*cda5da8dSAndroid Build Coastguard Worker                        _debug("   missing or invalid (non-numeric) value for "
1460*cda5da8dSAndroid Build Coastguard Worker                              "max-age attribute")
1461*cda5da8dSAndroid Build Coastguard Worker                        bad_cookie = True
1462*cda5da8dSAndroid Build Coastguard Worker                        break
1463*cda5da8dSAndroid Build Coastguard Worker                    # convert RFC 2965 Max-Age to seconds since epoch
1464*cda5da8dSAndroid Build Coastguard Worker                    # XXX Strictly you're supposed to follow RFC 2616
1465*cda5da8dSAndroid Build Coastguard Worker                    #   age-calculation rules.  Remember that zero Max-Age
1466*cda5da8dSAndroid Build Coastguard Worker                    #   is a request to discard (old and new) cookie, though.
1467*cda5da8dSAndroid Build Coastguard Worker                    k = "expires"
1468*cda5da8dSAndroid Build Coastguard Worker                    v = self._now + v
1469*cda5da8dSAndroid Build Coastguard Worker                if (k in value_attrs) or (k in boolean_attrs):
1470*cda5da8dSAndroid Build Coastguard Worker                    if (v is None and
1471*cda5da8dSAndroid Build Coastguard Worker                        k not in ("port", "comment", "commenturl")):
1472*cda5da8dSAndroid Build Coastguard Worker                        _debug("   missing value for %s attribute" % k)
1473*cda5da8dSAndroid Build Coastguard Worker                        bad_cookie = True
1474*cda5da8dSAndroid Build Coastguard Worker                        break
1475*cda5da8dSAndroid Build Coastguard Worker                    standard[k] = v
1476*cda5da8dSAndroid Build Coastguard Worker                else:
1477*cda5da8dSAndroid Build Coastguard Worker                    rest[k] = v
1478*cda5da8dSAndroid Build Coastguard Worker
1479*cda5da8dSAndroid Build Coastguard Worker            if bad_cookie:
1480*cda5da8dSAndroid Build Coastguard Worker                continue
1481*cda5da8dSAndroid Build Coastguard Worker
1482*cda5da8dSAndroid Build Coastguard Worker            cookie_tuples.append((name, value, standard, rest))
1483*cda5da8dSAndroid Build Coastguard Worker
1484*cda5da8dSAndroid Build Coastguard Worker        return cookie_tuples
1485*cda5da8dSAndroid Build Coastguard Worker
1486*cda5da8dSAndroid Build Coastguard Worker    def _cookie_from_cookie_tuple(self, tup, request):
1487*cda5da8dSAndroid Build Coastguard Worker        # standard is dict of standard cookie-attributes, rest is dict of the
1488*cda5da8dSAndroid Build Coastguard Worker        # rest of them
1489*cda5da8dSAndroid Build Coastguard Worker        name, value, standard, rest = tup
1490*cda5da8dSAndroid Build Coastguard Worker
1491*cda5da8dSAndroid Build Coastguard Worker        domain = standard.get("domain", Absent)
1492*cda5da8dSAndroid Build Coastguard Worker        path = standard.get("path", Absent)
1493*cda5da8dSAndroid Build Coastguard Worker        port = standard.get("port", Absent)
1494*cda5da8dSAndroid Build Coastguard Worker        expires = standard.get("expires", Absent)
1495*cda5da8dSAndroid Build Coastguard Worker
1496*cda5da8dSAndroid Build Coastguard Worker        # set the easy defaults
1497*cda5da8dSAndroid Build Coastguard Worker        version = standard.get("version", None)
1498*cda5da8dSAndroid Build Coastguard Worker        if version is not None:
1499*cda5da8dSAndroid Build Coastguard Worker            try:
1500*cda5da8dSAndroid Build Coastguard Worker                version = int(version)
1501*cda5da8dSAndroid Build Coastguard Worker            except ValueError:
1502*cda5da8dSAndroid Build Coastguard Worker                return None  # invalid version, ignore cookie
1503*cda5da8dSAndroid Build Coastguard Worker        secure = standard.get("secure", False)
1504*cda5da8dSAndroid Build Coastguard Worker        # (discard is also set if expires is Absent)
1505*cda5da8dSAndroid Build Coastguard Worker        discard = standard.get("discard", False)
1506*cda5da8dSAndroid Build Coastguard Worker        comment = standard.get("comment", None)
1507*cda5da8dSAndroid Build Coastguard Worker        comment_url = standard.get("commenturl", None)
1508*cda5da8dSAndroid Build Coastguard Worker
1509*cda5da8dSAndroid Build Coastguard Worker        # set default path
1510*cda5da8dSAndroid Build Coastguard Worker        if path is not Absent and path != "":
1511*cda5da8dSAndroid Build Coastguard Worker            path_specified = True
1512*cda5da8dSAndroid Build Coastguard Worker            path = escape_path(path)
1513*cda5da8dSAndroid Build Coastguard Worker        else:
1514*cda5da8dSAndroid Build Coastguard Worker            path_specified = False
1515*cda5da8dSAndroid Build Coastguard Worker            path = request_path(request)
1516*cda5da8dSAndroid Build Coastguard Worker            i = path.rfind("/")
1517*cda5da8dSAndroid Build Coastguard Worker            if i != -1:
1518*cda5da8dSAndroid Build Coastguard Worker                if version == 0:
1519*cda5da8dSAndroid Build Coastguard Worker                    # Netscape spec parts company from reality here
1520*cda5da8dSAndroid Build Coastguard Worker                    path = path[:i]
1521*cda5da8dSAndroid Build Coastguard Worker                else:
1522*cda5da8dSAndroid Build Coastguard Worker                    path = path[:i+1]
1523*cda5da8dSAndroid Build Coastguard Worker            if len(path) == 0: path = "/"
1524*cda5da8dSAndroid Build Coastguard Worker
1525*cda5da8dSAndroid Build Coastguard Worker        # set default domain
1526*cda5da8dSAndroid Build Coastguard Worker        domain_specified = domain is not Absent
1527*cda5da8dSAndroid Build Coastguard Worker        # but first we have to remember whether it starts with a dot
1528*cda5da8dSAndroid Build Coastguard Worker        domain_initial_dot = False
1529*cda5da8dSAndroid Build Coastguard Worker        if domain_specified:
1530*cda5da8dSAndroid Build Coastguard Worker            domain_initial_dot = bool(domain.startswith("."))
1531*cda5da8dSAndroid Build Coastguard Worker        if domain is Absent:
1532*cda5da8dSAndroid Build Coastguard Worker            req_host, erhn = eff_request_host(request)
1533*cda5da8dSAndroid Build Coastguard Worker            domain = erhn
1534*cda5da8dSAndroid Build Coastguard Worker        elif not domain.startswith("."):
1535*cda5da8dSAndroid Build Coastguard Worker            domain = "."+domain
1536*cda5da8dSAndroid Build Coastguard Worker
1537*cda5da8dSAndroid Build Coastguard Worker        # set default port
1538*cda5da8dSAndroid Build Coastguard Worker        port_specified = False
1539*cda5da8dSAndroid Build Coastguard Worker        if port is not Absent:
1540*cda5da8dSAndroid Build Coastguard Worker            if port is None:
1541*cda5da8dSAndroid Build Coastguard Worker                # Port attr present, but has no value: default to request port.
1542*cda5da8dSAndroid Build Coastguard Worker                # Cookie should then only be sent back on that port.
1543*cda5da8dSAndroid Build Coastguard Worker                port = request_port(request)
1544*cda5da8dSAndroid Build Coastguard Worker            else:
1545*cda5da8dSAndroid Build Coastguard Worker                port_specified = True
1546*cda5da8dSAndroid Build Coastguard Worker                port = re.sub(r"\s+", "", port)
1547*cda5da8dSAndroid Build Coastguard Worker        else:
1548*cda5da8dSAndroid Build Coastguard Worker            # No port attr present.  Cookie can be sent back on any port.
1549*cda5da8dSAndroid Build Coastguard Worker            port = None
1550*cda5da8dSAndroid Build Coastguard Worker
1551*cda5da8dSAndroid Build Coastguard Worker        # set default expires and discard
1552*cda5da8dSAndroid Build Coastguard Worker        if expires is Absent:
1553*cda5da8dSAndroid Build Coastguard Worker            expires = None
1554*cda5da8dSAndroid Build Coastguard Worker            discard = True
1555*cda5da8dSAndroid Build Coastguard Worker        elif expires <= self._now:
1556*cda5da8dSAndroid Build Coastguard Worker            # Expiry date in past is request to delete cookie.  This can't be
1557*cda5da8dSAndroid Build Coastguard Worker            # in DefaultCookiePolicy, because can't delete cookies there.
1558*cda5da8dSAndroid Build Coastguard Worker            try:
1559*cda5da8dSAndroid Build Coastguard Worker                self.clear(domain, path, name)
1560*cda5da8dSAndroid Build Coastguard Worker            except KeyError:
1561*cda5da8dSAndroid Build Coastguard Worker                pass
1562*cda5da8dSAndroid Build Coastguard Worker            _debug("Expiring cookie, domain='%s', path='%s', name='%s'",
1563*cda5da8dSAndroid Build Coastguard Worker                   domain, path, name)
1564*cda5da8dSAndroid Build Coastguard Worker            return None
1565*cda5da8dSAndroid Build Coastguard Worker
1566*cda5da8dSAndroid Build Coastguard Worker        return Cookie(version,
1567*cda5da8dSAndroid Build Coastguard Worker                      name, value,
1568*cda5da8dSAndroid Build Coastguard Worker                      port, port_specified,
1569*cda5da8dSAndroid Build Coastguard Worker                      domain, domain_specified, domain_initial_dot,
1570*cda5da8dSAndroid Build Coastguard Worker                      path, path_specified,
1571*cda5da8dSAndroid Build Coastguard Worker                      secure,
1572*cda5da8dSAndroid Build Coastguard Worker                      expires,
1573*cda5da8dSAndroid Build Coastguard Worker                      discard,
1574*cda5da8dSAndroid Build Coastguard Worker                      comment,
1575*cda5da8dSAndroid Build Coastguard Worker                      comment_url,
1576*cda5da8dSAndroid Build Coastguard Worker                      rest)
1577*cda5da8dSAndroid Build Coastguard Worker
1578*cda5da8dSAndroid Build Coastguard Worker    def _cookies_from_attrs_set(self, attrs_set, request):
1579*cda5da8dSAndroid Build Coastguard Worker        cookie_tuples = self._normalized_cookie_tuples(attrs_set)
1580*cda5da8dSAndroid Build Coastguard Worker
1581*cda5da8dSAndroid Build Coastguard Worker        cookies = []
1582*cda5da8dSAndroid Build Coastguard Worker        for tup in cookie_tuples:
1583*cda5da8dSAndroid Build Coastguard Worker            cookie = self._cookie_from_cookie_tuple(tup, request)
1584*cda5da8dSAndroid Build Coastguard Worker            if cookie: cookies.append(cookie)
1585*cda5da8dSAndroid Build Coastguard Worker        return cookies
1586*cda5da8dSAndroid Build Coastguard Worker
1587*cda5da8dSAndroid Build Coastguard Worker    def _process_rfc2109_cookies(self, cookies):
1588*cda5da8dSAndroid Build Coastguard Worker        rfc2109_as_ns = getattr(self._policy, 'rfc2109_as_netscape', None)
1589*cda5da8dSAndroid Build Coastguard Worker        if rfc2109_as_ns is None:
1590*cda5da8dSAndroid Build Coastguard Worker            rfc2109_as_ns = not self._policy.rfc2965
1591*cda5da8dSAndroid Build Coastguard Worker        for cookie in cookies:
1592*cda5da8dSAndroid Build Coastguard Worker            if cookie.version == 1:
1593*cda5da8dSAndroid Build Coastguard Worker                cookie.rfc2109 = True
1594*cda5da8dSAndroid Build Coastguard Worker                if rfc2109_as_ns:
1595*cda5da8dSAndroid Build Coastguard Worker                    # treat 2109 cookies as Netscape cookies rather than
1596*cda5da8dSAndroid Build Coastguard Worker                    # as RFC2965 cookies
1597*cda5da8dSAndroid Build Coastguard Worker                    cookie.version = 0
1598*cda5da8dSAndroid Build Coastguard Worker
1599*cda5da8dSAndroid Build Coastguard Worker    def make_cookies(self, response, request):
1600*cda5da8dSAndroid Build Coastguard Worker        """Return sequence of Cookie objects extracted from response object."""
1601*cda5da8dSAndroid Build Coastguard Worker        # get cookie-attributes for RFC 2965 and Netscape protocols
1602*cda5da8dSAndroid Build Coastguard Worker        headers = response.info()
1603*cda5da8dSAndroid Build Coastguard Worker        rfc2965_hdrs = headers.get_all("Set-Cookie2", [])
1604*cda5da8dSAndroid Build Coastguard Worker        ns_hdrs = headers.get_all("Set-Cookie", [])
1605*cda5da8dSAndroid Build Coastguard Worker        self._policy._now = self._now = int(time.time())
1606*cda5da8dSAndroid Build Coastguard Worker
1607*cda5da8dSAndroid Build Coastguard Worker        rfc2965 = self._policy.rfc2965
1608*cda5da8dSAndroid Build Coastguard Worker        netscape = self._policy.netscape
1609*cda5da8dSAndroid Build Coastguard Worker
1610*cda5da8dSAndroid Build Coastguard Worker        if ((not rfc2965_hdrs and not ns_hdrs) or
1611*cda5da8dSAndroid Build Coastguard Worker            (not ns_hdrs and not rfc2965) or
1612*cda5da8dSAndroid Build Coastguard Worker            (not rfc2965_hdrs and not netscape) or
1613*cda5da8dSAndroid Build Coastguard Worker            (not netscape and not rfc2965)):
1614*cda5da8dSAndroid Build Coastguard Worker            return []  # no relevant cookie headers: quick exit
1615*cda5da8dSAndroid Build Coastguard Worker
1616*cda5da8dSAndroid Build Coastguard Worker        try:
1617*cda5da8dSAndroid Build Coastguard Worker            cookies = self._cookies_from_attrs_set(
1618*cda5da8dSAndroid Build Coastguard Worker                split_header_words(rfc2965_hdrs), request)
1619*cda5da8dSAndroid Build Coastguard Worker        except Exception:
1620*cda5da8dSAndroid Build Coastguard Worker            _warn_unhandled_exception()
1621*cda5da8dSAndroid Build Coastguard Worker            cookies = []
1622*cda5da8dSAndroid Build Coastguard Worker
1623*cda5da8dSAndroid Build Coastguard Worker        if ns_hdrs and netscape:
1624*cda5da8dSAndroid Build Coastguard Worker            try:
1625*cda5da8dSAndroid Build Coastguard Worker                # RFC 2109 and Netscape cookies
1626*cda5da8dSAndroid Build Coastguard Worker                ns_cookies = self._cookies_from_attrs_set(
1627*cda5da8dSAndroid Build Coastguard Worker                    parse_ns_headers(ns_hdrs), request)
1628*cda5da8dSAndroid Build Coastguard Worker            except Exception:
1629*cda5da8dSAndroid Build Coastguard Worker                _warn_unhandled_exception()
1630*cda5da8dSAndroid Build Coastguard Worker                ns_cookies = []
1631*cda5da8dSAndroid Build Coastguard Worker            self._process_rfc2109_cookies(ns_cookies)
1632*cda5da8dSAndroid Build Coastguard Worker
1633*cda5da8dSAndroid Build Coastguard Worker            # Look for Netscape cookies (from Set-Cookie headers) that match
1634*cda5da8dSAndroid Build Coastguard Worker            # corresponding RFC 2965 cookies (from Set-Cookie2 headers).
1635*cda5da8dSAndroid Build Coastguard Worker            # For each match, keep the RFC 2965 cookie and ignore the Netscape
1636*cda5da8dSAndroid Build Coastguard Worker            # cookie (RFC 2965 section 9.1).  Actually, RFC 2109 cookies are
1637*cda5da8dSAndroid Build Coastguard Worker            # bundled in with the Netscape cookies for this purpose, which is
1638*cda5da8dSAndroid Build Coastguard Worker            # reasonable behaviour.
1639*cda5da8dSAndroid Build Coastguard Worker            if rfc2965:
1640*cda5da8dSAndroid Build Coastguard Worker                lookup = {}
1641*cda5da8dSAndroid Build Coastguard Worker                for cookie in cookies:
1642*cda5da8dSAndroid Build Coastguard Worker                    lookup[(cookie.domain, cookie.path, cookie.name)] = None
1643*cda5da8dSAndroid Build Coastguard Worker
1644*cda5da8dSAndroid Build Coastguard Worker                def no_matching_rfc2965(ns_cookie, lookup=lookup):
1645*cda5da8dSAndroid Build Coastguard Worker                    key = ns_cookie.domain, ns_cookie.path, ns_cookie.name
1646*cda5da8dSAndroid Build Coastguard Worker                    return key not in lookup
1647*cda5da8dSAndroid Build Coastguard Worker                ns_cookies = filter(no_matching_rfc2965, ns_cookies)
1648*cda5da8dSAndroid Build Coastguard Worker
1649*cda5da8dSAndroid Build Coastguard Worker            if ns_cookies:
1650*cda5da8dSAndroid Build Coastguard Worker                cookies.extend(ns_cookies)
1651*cda5da8dSAndroid Build Coastguard Worker
1652*cda5da8dSAndroid Build Coastguard Worker        return cookies
1653*cda5da8dSAndroid Build Coastguard Worker
1654*cda5da8dSAndroid Build Coastguard Worker    def set_cookie_if_ok(self, cookie, request):
1655*cda5da8dSAndroid Build Coastguard Worker        """Set a cookie if policy says it's OK to do so."""
1656*cda5da8dSAndroid Build Coastguard Worker        self._cookies_lock.acquire()
1657*cda5da8dSAndroid Build Coastguard Worker        try:
1658*cda5da8dSAndroid Build Coastguard Worker            self._policy._now = self._now = int(time.time())
1659*cda5da8dSAndroid Build Coastguard Worker
1660*cda5da8dSAndroid Build Coastguard Worker            if self._policy.set_ok(cookie, request):
1661*cda5da8dSAndroid Build Coastguard Worker                self.set_cookie(cookie)
1662*cda5da8dSAndroid Build Coastguard Worker
1663*cda5da8dSAndroid Build Coastguard Worker
1664*cda5da8dSAndroid Build Coastguard Worker        finally:
1665*cda5da8dSAndroid Build Coastguard Worker            self._cookies_lock.release()
1666*cda5da8dSAndroid Build Coastguard Worker
1667*cda5da8dSAndroid Build Coastguard Worker    def set_cookie(self, cookie):
1668*cda5da8dSAndroid Build Coastguard Worker        """Set a cookie, without checking whether or not it should be set."""
1669*cda5da8dSAndroid Build Coastguard Worker        c = self._cookies
1670*cda5da8dSAndroid Build Coastguard Worker        self._cookies_lock.acquire()
1671*cda5da8dSAndroid Build Coastguard Worker        try:
1672*cda5da8dSAndroid Build Coastguard Worker            if cookie.domain not in c: c[cookie.domain] = {}
1673*cda5da8dSAndroid Build Coastguard Worker            c2 = c[cookie.domain]
1674*cda5da8dSAndroid Build Coastguard Worker            if cookie.path not in c2: c2[cookie.path] = {}
1675*cda5da8dSAndroid Build Coastguard Worker            c3 = c2[cookie.path]
1676*cda5da8dSAndroid Build Coastguard Worker            c3[cookie.name] = cookie
1677*cda5da8dSAndroid Build Coastguard Worker        finally:
1678*cda5da8dSAndroid Build Coastguard Worker            self._cookies_lock.release()
1679*cda5da8dSAndroid Build Coastguard Worker
1680*cda5da8dSAndroid Build Coastguard Worker    def extract_cookies(self, response, request):
1681*cda5da8dSAndroid Build Coastguard Worker        """Extract cookies from response, where allowable given the request."""
1682*cda5da8dSAndroid Build Coastguard Worker        _debug("extract_cookies: %s", response.info())
1683*cda5da8dSAndroid Build Coastguard Worker        self._cookies_lock.acquire()
1684*cda5da8dSAndroid Build Coastguard Worker        try:
1685*cda5da8dSAndroid Build Coastguard Worker            for cookie in self.make_cookies(response, request):
1686*cda5da8dSAndroid Build Coastguard Worker                if self._policy.set_ok(cookie, request):
1687*cda5da8dSAndroid Build Coastguard Worker                    _debug(" setting cookie: %s", cookie)
1688*cda5da8dSAndroid Build Coastguard Worker                    self.set_cookie(cookie)
1689*cda5da8dSAndroid Build Coastguard Worker        finally:
1690*cda5da8dSAndroid Build Coastguard Worker            self._cookies_lock.release()
1691*cda5da8dSAndroid Build Coastguard Worker
1692*cda5da8dSAndroid Build Coastguard Worker    def clear(self, domain=None, path=None, name=None):
1693*cda5da8dSAndroid Build Coastguard Worker        """Clear some cookies.
1694*cda5da8dSAndroid Build Coastguard Worker
1695*cda5da8dSAndroid Build Coastguard Worker        Invoking this method without arguments will clear all cookies.  If
1696*cda5da8dSAndroid Build Coastguard Worker        given a single argument, only cookies belonging to that domain will be
1697*cda5da8dSAndroid Build Coastguard Worker        removed.  If given two arguments, cookies belonging to the specified
1698*cda5da8dSAndroid Build Coastguard Worker        path within that domain are removed.  If given three arguments, then
1699*cda5da8dSAndroid Build Coastguard Worker        the cookie with the specified name, path and domain is removed.
1700*cda5da8dSAndroid Build Coastguard Worker
1701*cda5da8dSAndroid Build Coastguard Worker        Raises KeyError if no matching cookie exists.
1702*cda5da8dSAndroid Build Coastguard Worker
1703*cda5da8dSAndroid Build Coastguard Worker        """
1704*cda5da8dSAndroid Build Coastguard Worker        if name is not None:
1705*cda5da8dSAndroid Build Coastguard Worker            if (domain is None) or (path is None):
1706*cda5da8dSAndroid Build Coastguard Worker                raise ValueError(
1707*cda5da8dSAndroid Build Coastguard Worker                    "domain and path must be given to remove a cookie by name")
1708*cda5da8dSAndroid Build Coastguard Worker            del self._cookies[domain][path][name]
1709*cda5da8dSAndroid Build Coastguard Worker        elif path is not None:
1710*cda5da8dSAndroid Build Coastguard Worker            if domain is None:
1711*cda5da8dSAndroid Build Coastguard Worker                raise ValueError(
1712*cda5da8dSAndroid Build Coastguard Worker                    "domain must be given to remove cookies by path")
1713*cda5da8dSAndroid Build Coastguard Worker            del self._cookies[domain][path]
1714*cda5da8dSAndroid Build Coastguard Worker        elif domain is not None:
1715*cda5da8dSAndroid Build Coastguard Worker            del self._cookies[domain]
1716*cda5da8dSAndroid Build Coastguard Worker        else:
1717*cda5da8dSAndroid Build Coastguard Worker            self._cookies = {}
1718*cda5da8dSAndroid Build Coastguard Worker
1719*cda5da8dSAndroid Build Coastguard Worker    def clear_session_cookies(self):
1720*cda5da8dSAndroid Build Coastguard Worker        """Discard all session cookies.
1721*cda5da8dSAndroid Build Coastguard Worker
1722*cda5da8dSAndroid Build Coastguard Worker        Note that the .save() method won't save session cookies anyway, unless
1723*cda5da8dSAndroid Build Coastguard Worker        you ask otherwise by passing a true ignore_discard argument.
1724*cda5da8dSAndroid Build Coastguard Worker
1725*cda5da8dSAndroid Build Coastguard Worker        """
1726*cda5da8dSAndroid Build Coastguard Worker        self._cookies_lock.acquire()
1727*cda5da8dSAndroid Build Coastguard Worker        try:
1728*cda5da8dSAndroid Build Coastguard Worker            for cookie in self:
1729*cda5da8dSAndroid Build Coastguard Worker                if cookie.discard:
1730*cda5da8dSAndroid Build Coastguard Worker                    self.clear(cookie.domain, cookie.path, cookie.name)
1731*cda5da8dSAndroid Build Coastguard Worker        finally:
1732*cda5da8dSAndroid Build Coastguard Worker            self._cookies_lock.release()
1733*cda5da8dSAndroid Build Coastguard Worker
1734*cda5da8dSAndroid Build Coastguard Worker    def clear_expired_cookies(self):
1735*cda5da8dSAndroid Build Coastguard Worker        """Discard all expired cookies.
1736*cda5da8dSAndroid Build Coastguard Worker
1737*cda5da8dSAndroid Build Coastguard Worker        You probably don't need to call this method: expired cookies are never
1738*cda5da8dSAndroid Build Coastguard Worker        sent back to the server (provided you're using DefaultCookiePolicy),
1739*cda5da8dSAndroid Build Coastguard Worker        this method is called by CookieJar itself every so often, and the
1740*cda5da8dSAndroid Build Coastguard Worker        .save() method won't save expired cookies anyway (unless you ask
1741*cda5da8dSAndroid Build Coastguard Worker        otherwise by passing a true ignore_expires argument).
1742*cda5da8dSAndroid Build Coastguard Worker
1743*cda5da8dSAndroid Build Coastguard Worker        """
1744*cda5da8dSAndroid Build Coastguard Worker        self._cookies_lock.acquire()
1745*cda5da8dSAndroid Build Coastguard Worker        try:
1746*cda5da8dSAndroid Build Coastguard Worker            now = time.time()
1747*cda5da8dSAndroid Build Coastguard Worker            for cookie in self:
1748*cda5da8dSAndroid Build Coastguard Worker                if cookie.is_expired(now):
1749*cda5da8dSAndroid Build Coastguard Worker                    self.clear(cookie.domain, cookie.path, cookie.name)
1750*cda5da8dSAndroid Build Coastguard Worker        finally:
1751*cda5da8dSAndroid Build Coastguard Worker            self._cookies_lock.release()
1752*cda5da8dSAndroid Build Coastguard Worker
1753*cda5da8dSAndroid Build Coastguard Worker    def __iter__(self):
1754*cda5da8dSAndroid Build Coastguard Worker        return deepvalues(self._cookies)
1755*cda5da8dSAndroid Build Coastguard Worker
1756*cda5da8dSAndroid Build Coastguard Worker    def __len__(self):
1757*cda5da8dSAndroid Build Coastguard Worker        """Return number of contained cookies."""
1758*cda5da8dSAndroid Build Coastguard Worker        i = 0
1759*cda5da8dSAndroid Build Coastguard Worker        for cookie in self: i = i + 1
1760*cda5da8dSAndroid Build Coastguard Worker        return i
1761*cda5da8dSAndroid Build Coastguard Worker
1762*cda5da8dSAndroid Build Coastguard Worker    def __repr__(self):
1763*cda5da8dSAndroid Build Coastguard Worker        r = []
1764*cda5da8dSAndroid Build Coastguard Worker        for cookie in self: r.append(repr(cookie))
1765*cda5da8dSAndroid Build Coastguard Worker        return "<%s[%s]>" % (self.__class__.__name__, ", ".join(r))
1766*cda5da8dSAndroid Build Coastguard Worker
1767*cda5da8dSAndroid Build Coastguard Worker    def __str__(self):
1768*cda5da8dSAndroid Build Coastguard Worker        r = []
1769*cda5da8dSAndroid Build Coastguard Worker        for cookie in self: r.append(str(cookie))
1770*cda5da8dSAndroid Build Coastguard Worker        return "<%s[%s]>" % (self.__class__.__name__, ", ".join(r))
1771*cda5da8dSAndroid Build Coastguard Worker
1772*cda5da8dSAndroid Build Coastguard Worker
1773*cda5da8dSAndroid Build Coastguard Worker# derives from OSError for backwards-compatibility with Python 2.4.0
1774*cda5da8dSAndroid Build Coastguard Workerclass LoadError(OSError): pass
1775*cda5da8dSAndroid Build Coastguard Worker
1776*cda5da8dSAndroid Build Coastguard Workerclass FileCookieJar(CookieJar):
1777*cda5da8dSAndroid Build Coastguard Worker    """CookieJar that can be loaded from and saved to a file."""
1778*cda5da8dSAndroid Build Coastguard Worker
1779*cda5da8dSAndroid Build Coastguard Worker    def __init__(self, filename=None, delayload=False, policy=None):
1780*cda5da8dSAndroid Build Coastguard Worker        """
1781*cda5da8dSAndroid Build Coastguard Worker        Cookies are NOT loaded from the named file until either the .load() or
1782*cda5da8dSAndroid Build Coastguard Worker        .revert() method is called.
1783*cda5da8dSAndroid Build Coastguard Worker
1784*cda5da8dSAndroid Build Coastguard Worker        """
1785*cda5da8dSAndroid Build Coastguard Worker        CookieJar.__init__(self, policy)
1786*cda5da8dSAndroid Build Coastguard Worker        if filename is not None:
1787*cda5da8dSAndroid Build Coastguard Worker            filename = os.fspath(filename)
1788*cda5da8dSAndroid Build Coastguard Worker        self.filename = filename
1789*cda5da8dSAndroid Build Coastguard Worker        self.delayload = bool(delayload)
1790*cda5da8dSAndroid Build Coastguard Worker
1791*cda5da8dSAndroid Build Coastguard Worker    def save(self, filename=None, ignore_discard=False, ignore_expires=False):
1792*cda5da8dSAndroid Build Coastguard Worker        """Save cookies to a file."""
1793*cda5da8dSAndroid Build Coastguard Worker        raise NotImplementedError()
1794*cda5da8dSAndroid Build Coastguard Worker
1795*cda5da8dSAndroid Build Coastguard Worker    def load(self, filename=None, ignore_discard=False, ignore_expires=False):
1796*cda5da8dSAndroid Build Coastguard Worker        """Load cookies from a file."""
1797*cda5da8dSAndroid Build Coastguard Worker        if filename is None:
1798*cda5da8dSAndroid Build Coastguard Worker            if self.filename is not None: filename = self.filename
1799*cda5da8dSAndroid Build Coastguard Worker            else: raise ValueError(MISSING_FILENAME_TEXT)
1800*cda5da8dSAndroid Build Coastguard Worker
1801*cda5da8dSAndroid Build Coastguard Worker        with open(filename) as f:
1802*cda5da8dSAndroid Build Coastguard Worker            self._really_load(f, filename, ignore_discard, ignore_expires)
1803*cda5da8dSAndroid Build Coastguard Worker
1804*cda5da8dSAndroid Build Coastguard Worker    def revert(self, filename=None,
1805*cda5da8dSAndroid Build Coastguard Worker               ignore_discard=False, ignore_expires=False):
1806*cda5da8dSAndroid Build Coastguard Worker        """Clear all cookies and reload cookies from a saved file.
1807*cda5da8dSAndroid Build Coastguard Worker
1808*cda5da8dSAndroid Build Coastguard Worker        Raises LoadError (or OSError) if reversion is not successful; the
1809*cda5da8dSAndroid Build Coastguard Worker        object's state will not be altered if this happens.
1810*cda5da8dSAndroid Build Coastguard Worker
1811*cda5da8dSAndroid Build Coastguard Worker        """
1812*cda5da8dSAndroid Build Coastguard Worker        if filename is None:
1813*cda5da8dSAndroid Build Coastguard Worker            if self.filename is not None: filename = self.filename
1814*cda5da8dSAndroid Build Coastguard Worker            else: raise ValueError(MISSING_FILENAME_TEXT)
1815*cda5da8dSAndroid Build Coastguard Worker
1816*cda5da8dSAndroid Build Coastguard Worker        self._cookies_lock.acquire()
1817*cda5da8dSAndroid Build Coastguard Worker        try:
1818*cda5da8dSAndroid Build Coastguard Worker
1819*cda5da8dSAndroid Build Coastguard Worker            old_state = copy.deepcopy(self._cookies)
1820*cda5da8dSAndroid Build Coastguard Worker            self._cookies = {}
1821*cda5da8dSAndroid Build Coastguard Worker            try:
1822*cda5da8dSAndroid Build Coastguard Worker                self.load(filename, ignore_discard, ignore_expires)
1823*cda5da8dSAndroid Build Coastguard Worker            except OSError:
1824*cda5da8dSAndroid Build Coastguard Worker                self._cookies = old_state
1825*cda5da8dSAndroid Build Coastguard Worker                raise
1826*cda5da8dSAndroid Build Coastguard Worker
1827*cda5da8dSAndroid Build Coastguard Worker        finally:
1828*cda5da8dSAndroid Build Coastguard Worker            self._cookies_lock.release()
1829*cda5da8dSAndroid Build Coastguard Worker
1830*cda5da8dSAndroid Build Coastguard Worker
1831*cda5da8dSAndroid Build Coastguard Workerdef lwp_cookie_str(cookie):
1832*cda5da8dSAndroid Build Coastguard Worker    """Return string representation of Cookie in the LWP cookie file format.
1833*cda5da8dSAndroid Build Coastguard Worker
1834*cda5da8dSAndroid Build Coastguard Worker    Actually, the format is extended a bit -- see module docstring.
1835*cda5da8dSAndroid Build Coastguard Worker
1836*cda5da8dSAndroid Build Coastguard Worker    """
1837*cda5da8dSAndroid Build Coastguard Worker    h = [(cookie.name, cookie.value),
1838*cda5da8dSAndroid Build Coastguard Worker         ("path", cookie.path),
1839*cda5da8dSAndroid Build Coastguard Worker         ("domain", cookie.domain)]
1840*cda5da8dSAndroid Build Coastguard Worker    if cookie.port is not None: h.append(("port", cookie.port))
1841*cda5da8dSAndroid Build Coastguard Worker    if cookie.path_specified: h.append(("path_spec", None))
1842*cda5da8dSAndroid Build Coastguard Worker    if cookie.port_specified: h.append(("port_spec", None))
1843*cda5da8dSAndroid Build Coastguard Worker    if cookie.domain_initial_dot: h.append(("domain_dot", None))
1844*cda5da8dSAndroid Build Coastguard Worker    if cookie.secure: h.append(("secure", None))
1845*cda5da8dSAndroid Build Coastguard Worker    if cookie.expires: h.append(("expires",
1846*cda5da8dSAndroid Build Coastguard Worker                               time2isoz(float(cookie.expires))))
1847*cda5da8dSAndroid Build Coastguard Worker    if cookie.discard: h.append(("discard", None))
1848*cda5da8dSAndroid Build Coastguard Worker    if cookie.comment: h.append(("comment", cookie.comment))
1849*cda5da8dSAndroid Build Coastguard Worker    if cookie.comment_url: h.append(("commenturl", cookie.comment_url))
1850*cda5da8dSAndroid Build Coastguard Worker
1851*cda5da8dSAndroid Build Coastguard Worker    keys = sorted(cookie._rest.keys())
1852*cda5da8dSAndroid Build Coastguard Worker    for k in keys:
1853*cda5da8dSAndroid Build Coastguard Worker        h.append((k, str(cookie._rest[k])))
1854*cda5da8dSAndroid Build Coastguard Worker
1855*cda5da8dSAndroid Build Coastguard Worker    h.append(("version", str(cookie.version)))
1856*cda5da8dSAndroid Build Coastguard Worker
1857*cda5da8dSAndroid Build Coastguard Worker    return join_header_words([h])
1858*cda5da8dSAndroid Build Coastguard Worker
1859*cda5da8dSAndroid Build Coastguard Workerclass LWPCookieJar(FileCookieJar):
1860*cda5da8dSAndroid Build Coastguard Worker    """
1861*cda5da8dSAndroid Build Coastguard Worker    The LWPCookieJar saves a sequence of "Set-Cookie3" lines.
1862*cda5da8dSAndroid Build Coastguard Worker    "Set-Cookie3" is the format used by the libwww-perl library, not known
1863*cda5da8dSAndroid Build Coastguard Worker    to be compatible with any browser, but which is easy to read and
1864*cda5da8dSAndroid Build Coastguard Worker    doesn't lose information about RFC 2965 cookies.
1865*cda5da8dSAndroid Build Coastguard Worker
1866*cda5da8dSAndroid Build Coastguard Worker    Additional methods
1867*cda5da8dSAndroid Build Coastguard Worker
1868*cda5da8dSAndroid Build Coastguard Worker    as_lwp_str(ignore_discard=True, ignore_expired=True)
1869*cda5da8dSAndroid Build Coastguard Worker
1870*cda5da8dSAndroid Build Coastguard Worker    """
1871*cda5da8dSAndroid Build Coastguard Worker
1872*cda5da8dSAndroid Build Coastguard Worker    def as_lwp_str(self, ignore_discard=True, ignore_expires=True):
1873*cda5da8dSAndroid Build Coastguard Worker        """Return cookies as a string of "\\n"-separated "Set-Cookie3" headers.
1874*cda5da8dSAndroid Build Coastguard Worker
1875*cda5da8dSAndroid Build Coastguard Worker        ignore_discard and ignore_expires: see docstring for FileCookieJar.save
1876*cda5da8dSAndroid Build Coastguard Worker
1877*cda5da8dSAndroid Build Coastguard Worker        """
1878*cda5da8dSAndroid Build Coastguard Worker        now = time.time()
1879*cda5da8dSAndroid Build Coastguard Worker        r = []
1880*cda5da8dSAndroid Build Coastguard Worker        for cookie in self:
1881*cda5da8dSAndroid Build Coastguard Worker            if not ignore_discard and cookie.discard:
1882*cda5da8dSAndroid Build Coastguard Worker                continue
1883*cda5da8dSAndroid Build Coastguard Worker            if not ignore_expires and cookie.is_expired(now):
1884*cda5da8dSAndroid Build Coastguard Worker                continue
1885*cda5da8dSAndroid Build Coastguard Worker            r.append("Set-Cookie3: %s" % lwp_cookie_str(cookie))
1886*cda5da8dSAndroid Build Coastguard Worker        return "\n".join(r+[""])
1887*cda5da8dSAndroid Build Coastguard Worker
1888*cda5da8dSAndroid Build Coastguard Worker    def save(self, filename=None, ignore_discard=False, ignore_expires=False):
1889*cda5da8dSAndroid Build Coastguard Worker        if filename is None:
1890*cda5da8dSAndroid Build Coastguard Worker            if self.filename is not None: filename = self.filename
1891*cda5da8dSAndroid Build Coastguard Worker            else: raise ValueError(MISSING_FILENAME_TEXT)
1892*cda5da8dSAndroid Build Coastguard Worker
1893*cda5da8dSAndroid Build Coastguard Worker        with os.fdopen(
1894*cda5da8dSAndroid Build Coastguard Worker            os.open(filename, os.O_CREAT | os.O_WRONLY | os.O_TRUNC, 0o600),
1895*cda5da8dSAndroid Build Coastguard Worker            'w',
1896*cda5da8dSAndroid Build Coastguard Worker        ) as f:
1897*cda5da8dSAndroid Build Coastguard Worker            # There really isn't an LWP Cookies 2.0 format, but this indicates
1898*cda5da8dSAndroid Build Coastguard Worker            # that there is extra information in here (domain_dot and
1899*cda5da8dSAndroid Build Coastguard Worker            # port_spec) while still being compatible with libwww-perl, I hope.
1900*cda5da8dSAndroid Build Coastguard Worker            f.write("#LWP-Cookies-2.0\n")
1901*cda5da8dSAndroid Build Coastguard Worker            f.write(self.as_lwp_str(ignore_discard, ignore_expires))
1902*cda5da8dSAndroid Build Coastguard Worker
1903*cda5da8dSAndroid Build Coastguard Worker    def _really_load(self, f, filename, ignore_discard, ignore_expires):
1904*cda5da8dSAndroid Build Coastguard Worker        magic = f.readline()
1905*cda5da8dSAndroid Build Coastguard Worker        if not self.magic_re.search(magic):
1906*cda5da8dSAndroid Build Coastguard Worker            msg = ("%r does not look like a Set-Cookie3 (LWP) format "
1907*cda5da8dSAndroid Build Coastguard Worker                   "file" % filename)
1908*cda5da8dSAndroid Build Coastguard Worker            raise LoadError(msg)
1909*cda5da8dSAndroid Build Coastguard Worker
1910*cda5da8dSAndroid Build Coastguard Worker        now = time.time()
1911*cda5da8dSAndroid Build Coastguard Worker
1912*cda5da8dSAndroid Build Coastguard Worker        header = "Set-Cookie3:"
1913*cda5da8dSAndroid Build Coastguard Worker        boolean_attrs = ("port_spec", "path_spec", "domain_dot",
1914*cda5da8dSAndroid Build Coastguard Worker                         "secure", "discard")
1915*cda5da8dSAndroid Build Coastguard Worker        value_attrs = ("version",
1916*cda5da8dSAndroid Build Coastguard Worker                       "port", "path", "domain",
1917*cda5da8dSAndroid Build Coastguard Worker                       "expires",
1918*cda5da8dSAndroid Build Coastguard Worker                       "comment", "commenturl")
1919*cda5da8dSAndroid Build Coastguard Worker
1920*cda5da8dSAndroid Build Coastguard Worker        try:
1921*cda5da8dSAndroid Build Coastguard Worker            while 1:
1922*cda5da8dSAndroid Build Coastguard Worker                line = f.readline()
1923*cda5da8dSAndroid Build Coastguard Worker                if line == "": break
1924*cda5da8dSAndroid Build Coastguard Worker                if not line.startswith(header):
1925*cda5da8dSAndroid Build Coastguard Worker                    continue
1926*cda5da8dSAndroid Build Coastguard Worker                line = line[len(header):].strip()
1927*cda5da8dSAndroid Build Coastguard Worker
1928*cda5da8dSAndroid Build Coastguard Worker                for data in split_header_words([line]):
1929*cda5da8dSAndroid Build Coastguard Worker                    name, value = data[0]
1930*cda5da8dSAndroid Build Coastguard Worker                    standard = {}
1931*cda5da8dSAndroid Build Coastguard Worker                    rest = {}
1932*cda5da8dSAndroid Build Coastguard Worker                    for k in boolean_attrs:
1933*cda5da8dSAndroid Build Coastguard Worker                        standard[k] = False
1934*cda5da8dSAndroid Build Coastguard Worker                    for k, v in data[1:]:
1935*cda5da8dSAndroid Build Coastguard Worker                        if k is not None:
1936*cda5da8dSAndroid Build Coastguard Worker                            lc = k.lower()
1937*cda5da8dSAndroid Build Coastguard Worker                        else:
1938*cda5da8dSAndroid Build Coastguard Worker                            lc = None
1939*cda5da8dSAndroid Build Coastguard Worker                        # don't lose case distinction for unknown fields
1940*cda5da8dSAndroid Build Coastguard Worker                        if (lc in value_attrs) or (lc in boolean_attrs):
1941*cda5da8dSAndroid Build Coastguard Worker                            k = lc
1942*cda5da8dSAndroid Build Coastguard Worker                        if k in boolean_attrs:
1943*cda5da8dSAndroid Build Coastguard Worker                            if v is None: v = True
1944*cda5da8dSAndroid Build Coastguard Worker                            standard[k] = v
1945*cda5da8dSAndroid Build Coastguard Worker                        elif k in value_attrs:
1946*cda5da8dSAndroid Build Coastguard Worker                            standard[k] = v
1947*cda5da8dSAndroid Build Coastguard Worker                        else:
1948*cda5da8dSAndroid Build Coastguard Worker                            rest[k] = v
1949*cda5da8dSAndroid Build Coastguard Worker
1950*cda5da8dSAndroid Build Coastguard Worker                    h = standard.get
1951*cda5da8dSAndroid Build Coastguard Worker                    expires = h("expires")
1952*cda5da8dSAndroid Build Coastguard Worker                    discard = h("discard")
1953*cda5da8dSAndroid Build Coastguard Worker                    if expires is not None:
1954*cda5da8dSAndroid Build Coastguard Worker                        expires = iso2time(expires)
1955*cda5da8dSAndroid Build Coastguard Worker                    if expires is None:
1956*cda5da8dSAndroid Build Coastguard Worker                        discard = True
1957*cda5da8dSAndroid Build Coastguard Worker                    domain = h("domain")
1958*cda5da8dSAndroid Build Coastguard Worker                    domain_specified = domain.startswith(".")
1959*cda5da8dSAndroid Build Coastguard Worker                    c = Cookie(h("version"), name, value,
1960*cda5da8dSAndroid Build Coastguard Worker                               h("port"), h("port_spec"),
1961*cda5da8dSAndroid Build Coastguard Worker                               domain, domain_specified, h("domain_dot"),
1962*cda5da8dSAndroid Build Coastguard Worker                               h("path"), h("path_spec"),
1963*cda5da8dSAndroid Build Coastguard Worker                               h("secure"),
1964*cda5da8dSAndroid Build Coastguard Worker                               expires,
1965*cda5da8dSAndroid Build Coastguard Worker                               discard,
1966*cda5da8dSAndroid Build Coastguard Worker                               h("comment"),
1967*cda5da8dSAndroid Build Coastguard Worker                               h("commenturl"),
1968*cda5da8dSAndroid Build Coastguard Worker                               rest)
1969*cda5da8dSAndroid Build Coastguard Worker                    if not ignore_discard and c.discard:
1970*cda5da8dSAndroid Build Coastguard Worker                        continue
1971*cda5da8dSAndroid Build Coastguard Worker                    if not ignore_expires and c.is_expired(now):
1972*cda5da8dSAndroid Build Coastguard Worker                        continue
1973*cda5da8dSAndroid Build Coastguard Worker                    self.set_cookie(c)
1974*cda5da8dSAndroid Build Coastguard Worker        except OSError:
1975*cda5da8dSAndroid Build Coastguard Worker            raise
1976*cda5da8dSAndroid Build Coastguard Worker        except Exception:
1977*cda5da8dSAndroid Build Coastguard Worker            _warn_unhandled_exception()
1978*cda5da8dSAndroid Build Coastguard Worker            raise LoadError("invalid Set-Cookie3 format file %r: %r" %
1979*cda5da8dSAndroid Build Coastguard Worker                            (filename, line))
1980*cda5da8dSAndroid Build Coastguard Worker
1981*cda5da8dSAndroid Build Coastguard Worker
1982*cda5da8dSAndroid Build Coastguard Workerclass MozillaCookieJar(FileCookieJar):
1983*cda5da8dSAndroid Build Coastguard Worker    """
1984*cda5da8dSAndroid Build Coastguard Worker
1985*cda5da8dSAndroid Build Coastguard Worker    WARNING: you may want to backup your browser's cookies file if you use
1986*cda5da8dSAndroid Build Coastguard Worker    this class to save cookies.  I *think* it works, but there have been
1987*cda5da8dSAndroid Build Coastguard Worker    bugs in the past!
1988*cda5da8dSAndroid Build Coastguard Worker
1989*cda5da8dSAndroid Build Coastguard Worker    This class differs from CookieJar only in the format it uses to save and
1990*cda5da8dSAndroid Build Coastguard Worker    load cookies to and from a file.  This class uses the Mozilla/Netscape
1991*cda5da8dSAndroid Build Coastguard Worker    `cookies.txt' format.  curl and lynx use this file format, too.
1992*cda5da8dSAndroid Build Coastguard Worker
1993*cda5da8dSAndroid Build Coastguard Worker    Don't expect cookies saved while the browser is running to be noticed by
1994*cda5da8dSAndroid Build Coastguard Worker    the browser (in fact, Mozilla on unix will overwrite your saved cookies if
1995*cda5da8dSAndroid Build Coastguard Worker    you change them on disk while it's running; on Windows, you probably can't
1996*cda5da8dSAndroid Build Coastguard Worker    save at all while the browser is running).
1997*cda5da8dSAndroid Build Coastguard Worker
1998*cda5da8dSAndroid Build Coastguard Worker    Note that the Mozilla/Netscape format will downgrade RFC2965 cookies to
1999*cda5da8dSAndroid Build Coastguard Worker    Netscape cookies on saving.
2000*cda5da8dSAndroid Build Coastguard Worker
2001*cda5da8dSAndroid Build Coastguard Worker    In particular, the cookie version and port number information is lost,
2002*cda5da8dSAndroid Build Coastguard Worker    together with information about whether or not Path, Port and Discard were
2003*cda5da8dSAndroid Build Coastguard Worker    specified by the Set-Cookie2 (or Set-Cookie) header, and whether or not the
2004*cda5da8dSAndroid Build Coastguard Worker    domain as set in the HTTP header started with a dot (yes, I'm aware some
2005*cda5da8dSAndroid Build Coastguard Worker    domains in Netscape files start with a dot and some don't -- trust me, you
2006*cda5da8dSAndroid Build Coastguard Worker    really don't want to know any more about this).
2007*cda5da8dSAndroid Build Coastguard Worker
2008*cda5da8dSAndroid Build Coastguard Worker    Note that though Mozilla and Netscape use the same format, they use
2009*cda5da8dSAndroid Build Coastguard Worker    slightly different headers.  The class saves cookies using the Netscape
2010*cda5da8dSAndroid Build Coastguard Worker    header by default (Mozilla can cope with that).
2011*cda5da8dSAndroid Build Coastguard Worker
2012*cda5da8dSAndroid Build Coastguard Worker    """
2013*cda5da8dSAndroid Build Coastguard Worker
2014*cda5da8dSAndroid Build Coastguard Worker    def _really_load(self, f, filename, ignore_discard, ignore_expires):
2015*cda5da8dSAndroid Build Coastguard Worker        now = time.time()
2016*cda5da8dSAndroid Build Coastguard Worker
2017*cda5da8dSAndroid Build Coastguard Worker        if not NETSCAPE_MAGIC_RGX.match(f.readline()):
2018*cda5da8dSAndroid Build Coastguard Worker            raise LoadError(
2019*cda5da8dSAndroid Build Coastguard Worker                "%r does not look like a Netscape format cookies file" %
2020*cda5da8dSAndroid Build Coastguard Worker                filename)
2021*cda5da8dSAndroid Build Coastguard Worker
2022*cda5da8dSAndroid Build Coastguard Worker        try:
2023*cda5da8dSAndroid Build Coastguard Worker            while 1:
2024*cda5da8dSAndroid Build Coastguard Worker                line = f.readline()
2025*cda5da8dSAndroid Build Coastguard Worker                rest = {}
2026*cda5da8dSAndroid Build Coastguard Worker
2027*cda5da8dSAndroid Build Coastguard Worker                if line == "": break
2028*cda5da8dSAndroid Build Coastguard Worker
2029*cda5da8dSAndroid Build Coastguard Worker                # httponly is a cookie flag as defined in rfc6265
2030*cda5da8dSAndroid Build Coastguard Worker                # when encoded in a netscape cookie file,
2031*cda5da8dSAndroid Build Coastguard Worker                # the line is prepended with "#HttpOnly_"
2032*cda5da8dSAndroid Build Coastguard Worker                if line.startswith(HTTPONLY_PREFIX):
2033*cda5da8dSAndroid Build Coastguard Worker                    rest[HTTPONLY_ATTR] = ""
2034*cda5da8dSAndroid Build Coastguard Worker                    line = line[len(HTTPONLY_PREFIX):]
2035*cda5da8dSAndroid Build Coastguard Worker
2036*cda5da8dSAndroid Build Coastguard Worker                # last field may be absent, so keep any trailing tab
2037*cda5da8dSAndroid Build Coastguard Worker                if line.endswith("\n"): line = line[:-1]
2038*cda5da8dSAndroid Build Coastguard Worker
2039*cda5da8dSAndroid Build Coastguard Worker                # skip comments and blank lines XXX what is $ for?
2040*cda5da8dSAndroid Build Coastguard Worker                if (line.strip().startswith(("#", "$")) or
2041*cda5da8dSAndroid Build Coastguard Worker                    line.strip() == ""):
2042*cda5da8dSAndroid Build Coastguard Worker                    continue
2043*cda5da8dSAndroid Build Coastguard Worker
2044*cda5da8dSAndroid Build Coastguard Worker                domain, domain_specified, path, secure, expires, name, value = \
2045*cda5da8dSAndroid Build Coastguard Worker                        line.split("\t")
2046*cda5da8dSAndroid Build Coastguard Worker                secure = (secure == "TRUE")
2047*cda5da8dSAndroid Build Coastguard Worker                domain_specified = (domain_specified == "TRUE")
2048*cda5da8dSAndroid Build Coastguard Worker                if name == "":
2049*cda5da8dSAndroid Build Coastguard Worker                    # cookies.txt regards 'Set-Cookie: foo' as a cookie
2050*cda5da8dSAndroid Build Coastguard Worker                    # with no name, whereas http.cookiejar regards it as a
2051*cda5da8dSAndroid Build Coastguard Worker                    # cookie with no value.
2052*cda5da8dSAndroid Build Coastguard Worker                    name = value
2053*cda5da8dSAndroid Build Coastguard Worker                    value = None
2054*cda5da8dSAndroid Build Coastguard Worker
2055*cda5da8dSAndroid Build Coastguard Worker                initial_dot = domain.startswith(".")
2056*cda5da8dSAndroid Build Coastguard Worker                assert domain_specified == initial_dot
2057*cda5da8dSAndroid Build Coastguard Worker
2058*cda5da8dSAndroid Build Coastguard Worker                discard = False
2059*cda5da8dSAndroid Build Coastguard Worker                if expires == "":
2060*cda5da8dSAndroid Build Coastguard Worker                    expires = None
2061*cda5da8dSAndroid Build Coastguard Worker                    discard = True
2062*cda5da8dSAndroid Build Coastguard Worker
2063*cda5da8dSAndroid Build Coastguard Worker                # assume path_specified is false
2064*cda5da8dSAndroid Build Coastguard Worker                c = Cookie(0, name, value,
2065*cda5da8dSAndroid Build Coastguard Worker                           None, False,
2066*cda5da8dSAndroid Build Coastguard Worker                           domain, domain_specified, initial_dot,
2067*cda5da8dSAndroid Build Coastguard Worker                           path, False,
2068*cda5da8dSAndroid Build Coastguard Worker                           secure,
2069*cda5da8dSAndroid Build Coastguard Worker                           expires,
2070*cda5da8dSAndroid Build Coastguard Worker                           discard,
2071*cda5da8dSAndroid Build Coastguard Worker                           None,
2072*cda5da8dSAndroid Build Coastguard Worker                           None,
2073*cda5da8dSAndroid Build Coastguard Worker                           rest)
2074*cda5da8dSAndroid Build Coastguard Worker                if not ignore_discard and c.discard:
2075*cda5da8dSAndroid Build Coastguard Worker                    continue
2076*cda5da8dSAndroid Build Coastguard Worker                if not ignore_expires and c.is_expired(now):
2077*cda5da8dSAndroid Build Coastguard Worker                    continue
2078*cda5da8dSAndroid Build Coastguard Worker                self.set_cookie(c)
2079*cda5da8dSAndroid Build Coastguard Worker
2080*cda5da8dSAndroid Build Coastguard Worker        except OSError:
2081*cda5da8dSAndroid Build Coastguard Worker            raise
2082*cda5da8dSAndroid Build Coastguard Worker        except Exception:
2083*cda5da8dSAndroid Build Coastguard Worker            _warn_unhandled_exception()
2084*cda5da8dSAndroid Build Coastguard Worker            raise LoadError("invalid Netscape format cookies file %r: %r" %
2085*cda5da8dSAndroid Build Coastguard Worker                            (filename, line))
2086*cda5da8dSAndroid Build Coastguard Worker
2087*cda5da8dSAndroid Build Coastguard Worker    def save(self, filename=None, ignore_discard=False, ignore_expires=False):
2088*cda5da8dSAndroid Build Coastguard Worker        if filename is None:
2089*cda5da8dSAndroid Build Coastguard Worker            if self.filename is not None: filename = self.filename
2090*cda5da8dSAndroid Build Coastguard Worker            else: raise ValueError(MISSING_FILENAME_TEXT)
2091*cda5da8dSAndroid Build Coastguard Worker
2092*cda5da8dSAndroid Build Coastguard Worker        with os.fdopen(
2093*cda5da8dSAndroid Build Coastguard Worker            os.open(filename, os.O_CREAT | os.O_WRONLY | os.O_TRUNC, 0o600),
2094*cda5da8dSAndroid Build Coastguard Worker            'w',
2095*cda5da8dSAndroid Build Coastguard Worker        ) as f:
2096*cda5da8dSAndroid Build Coastguard Worker            f.write(NETSCAPE_HEADER_TEXT)
2097*cda5da8dSAndroid Build Coastguard Worker            now = time.time()
2098*cda5da8dSAndroid Build Coastguard Worker            for cookie in self:
2099*cda5da8dSAndroid Build Coastguard Worker                domain = cookie.domain
2100*cda5da8dSAndroid Build Coastguard Worker                if not ignore_discard and cookie.discard:
2101*cda5da8dSAndroid Build Coastguard Worker                    continue
2102*cda5da8dSAndroid Build Coastguard Worker                if not ignore_expires and cookie.is_expired(now):
2103*cda5da8dSAndroid Build Coastguard Worker                    continue
2104*cda5da8dSAndroid Build Coastguard Worker                if cookie.secure: secure = "TRUE"
2105*cda5da8dSAndroid Build Coastguard Worker                else: secure = "FALSE"
2106*cda5da8dSAndroid Build Coastguard Worker                if domain.startswith("."): initial_dot = "TRUE"
2107*cda5da8dSAndroid Build Coastguard Worker                else: initial_dot = "FALSE"
2108*cda5da8dSAndroid Build Coastguard Worker                if cookie.expires is not None:
2109*cda5da8dSAndroid Build Coastguard Worker                    expires = str(cookie.expires)
2110*cda5da8dSAndroid Build Coastguard Worker                else:
2111*cda5da8dSAndroid Build Coastguard Worker                    expires = ""
2112*cda5da8dSAndroid Build Coastguard Worker                if cookie.value is None:
2113*cda5da8dSAndroid Build Coastguard Worker                    # cookies.txt regards 'Set-Cookie: foo' as a cookie
2114*cda5da8dSAndroid Build Coastguard Worker                    # with no name, whereas http.cookiejar regards it as a
2115*cda5da8dSAndroid Build Coastguard Worker                    # cookie with no value.
2116*cda5da8dSAndroid Build Coastguard Worker                    name = ""
2117*cda5da8dSAndroid Build Coastguard Worker                    value = cookie.name
2118*cda5da8dSAndroid Build Coastguard Worker                else:
2119*cda5da8dSAndroid Build Coastguard Worker                    name = cookie.name
2120*cda5da8dSAndroid Build Coastguard Worker                    value = cookie.value
2121*cda5da8dSAndroid Build Coastguard Worker                if cookie.has_nonstandard_attr(HTTPONLY_ATTR):
2122*cda5da8dSAndroid Build Coastguard Worker                    domain = HTTPONLY_PREFIX + domain
2123*cda5da8dSAndroid Build Coastguard Worker                f.write(
2124*cda5da8dSAndroid Build Coastguard Worker                    "\t".join([domain, initial_dot, cookie.path,
2125*cda5da8dSAndroid Build Coastguard Worker                               secure, expires, name, value])+
2126*cda5da8dSAndroid Build Coastguard Worker                    "\n")
2127