Commit | Line | Data |
---|---|---|
86530b38 AT |
1 | #!/usr/bin/env python |
2 | # | |
3 | ||
4 | #### | |
5 | # Copyright 2000 by Timothy O'Malley <timo@alum.mit.edu> | |
6 | # | |
7 | # All Rights Reserved | |
8 | # | |
9 | # Permission to use, copy, modify, and distribute this software | |
10 | # and its documentation for any purpose and without fee is hereby | |
11 | # granted, provided that the above copyright notice appear in all | |
12 | # copies and that both that copyright notice and this permission | |
13 | # notice appear in supporting documentation, and that the name of | |
14 | # Timothy O'Malley not be used in advertising or publicity | |
15 | # pertaining to distribution of the software without specific, written | |
16 | # prior permission. | |
17 | # | |
18 | # Timothy O'Malley DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS | |
19 | # SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY | |
20 | # AND FITNESS, IN NO EVENT SHALL Timothy O'Malley BE LIABLE FOR | |
21 | # ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | |
22 | # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, | |
23 | # WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS | |
24 | # ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR | |
25 | # PERFORMANCE OF THIS SOFTWARE. | |
26 | # | |
27 | #### | |
28 | # | |
29 | # Id: Cookie.py,v 2.29 2000/08/23 05:28:49 timo Exp | |
30 | # by Timothy O'Malley <timo@alum.mit.edu> | |
31 | # | |
32 | # Cookie.py is a Python module for the handling of HTTP | |
33 | # cookies as a Python dictionary. See RFC 2109 for more | |
34 | # information on cookies. | |
35 | # | |
36 | # The original idea to treat Cookies as a dictionary came from | |
37 | # Dave Mitchell (davem@magnet.com) in 1995, when he released the | |
38 | # first version of nscookie.py. | |
39 | # | |
40 | #### | |
41 | ||
42 | r""" | |
43 | Here's a sample session to show how to use this module. | |
44 | At the moment, this is the only documentation. | |
45 | ||
46 | The Basics | |
47 | ---------- | |
48 | ||
49 | Importing is easy.. | |
50 | ||
51 | >>> import Cookie | |
52 | ||
53 | Most of the time you start by creating a cookie. Cookies come in | |
54 | three flavors, each with slightly different encoding semantics, but | |
55 | more on that later. | |
56 | ||
57 | >>> C = Cookie.SimpleCookie() | |
58 | >>> C = Cookie.SerialCookie() | |
59 | >>> C = Cookie.SmartCookie() | |
60 | ||
61 | [Note: Long-time users of Cookie.py will remember using | |
62 | Cookie.Cookie() to create an Cookie object. Although deprecated, it | |
63 | is still supported by the code. See the Backward Compatibility notes | |
64 | for more information.] | |
65 | ||
66 | Once you've created your Cookie, you can add values just as if it were | |
67 | a dictionary. | |
68 | ||
69 | >>> C = Cookie.SmartCookie() | |
70 | >>> C["fig"] = "newton" | |
71 | >>> C["sugar"] = "wafer" | |
72 | >>> print C | |
73 | Set-Cookie: fig=newton; | |
74 | Set-Cookie: sugar=wafer; | |
75 | ||
76 | Notice that the printable representation of a Cookie is the | |
77 | appropriate format for a Set-Cookie: header. This is the | |
78 | default behavior. You can change the header and printed | |
79 | attributes by using the .output() function | |
80 | ||
81 | >>> C = Cookie.SmartCookie() | |
82 | >>> C["rocky"] = "road" | |
83 | >>> C["rocky"]["path"] = "/cookie" | |
84 | >>> print C.output(header="Cookie:") | |
85 | Cookie: rocky=road; Path=/cookie; | |
86 | >>> print C.output(attrs=[], header="Cookie:") | |
87 | Cookie: rocky=road; | |
88 | ||
89 | The load() method of a Cookie extracts cookies from a string. In a | |
90 | CGI script, you would use this method to extract the cookies from the | |
91 | HTTP_COOKIE environment variable. | |
92 | ||
93 | >>> C = Cookie.SmartCookie() | |
94 | >>> C.load("chips=ahoy; vienna=finger") | |
95 | >>> print C | |
96 | Set-Cookie: chips=ahoy; | |
97 | Set-Cookie: vienna=finger; | |
98 | ||
99 | The load() method is darn-tootin smart about identifying cookies | |
100 | within a string. Escaped quotation marks, nested semicolons, and other | |
101 | such trickeries do not confuse it. | |
102 | ||
103 | >>> C = Cookie.SmartCookie() | |
104 | >>> C.load('keebler="E=everybody; L=\\"Loves\\"; fudge=\\012;";') | |
105 | >>> print C | |
106 | Set-Cookie: keebler="E=everybody; L=\"Loves\"; fudge=\012;"; | |
107 | ||
108 | Each element of the Cookie also supports all of the RFC 2109 | |
109 | Cookie attributes. Here's an example which sets the Path | |
110 | attribute. | |
111 | ||
112 | >>> C = Cookie.SmartCookie() | |
113 | >>> C["oreo"] = "doublestuff" | |
114 | >>> C["oreo"]["path"] = "/" | |
115 | >>> print C | |
116 | Set-Cookie: oreo=doublestuff; Path=/; | |
117 | ||
118 | Each dictionary element has a 'value' attribute, which gives you | |
119 | back the value associated with the key. | |
120 | ||
121 | >>> C = Cookie.SmartCookie() | |
122 | >>> C["twix"] = "none for you" | |
123 | >>> C["twix"].value | |
124 | 'none for you' | |
125 | ||
126 | ||
127 | A Bit More Advanced | |
128 | ------------------- | |
129 | ||
130 | As mentioned before, there are three different flavors of Cookie | |
131 | objects, each with different encoding/decoding semantics. This | |
132 | section briefly discusses the differences. | |
133 | ||
134 | SimpleCookie | |
135 | ||
136 | The SimpleCookie expects that all values should be standard strings. | |
137 | Just to be sure, SimpleCookie invokes the str() builtin to convert | |
138 | the value to a string, when the values are set dictionary-style. | |
139 | ||
140 | >>> C = Cookie.SimpleCookie() | |
141 | >>> C["number"] = 7 | |
142 | >>> C["string"] = "seven" | |
143 | >>> C["number"].value | |
144 | '7' | |
145 | >>> C["string"].value | |
146 | 'seven' | |
147 | >>> print C | |
148 | Set-Cookie: number=7; | |
149 | Set-Cookie: string=seven; | |
150 | ||
151 | ||
152 | SerialCookie | |
153 | ||
154 | The SerialCookie expects that all values should be serialized using | |
155 | cPickle (or pickle, if cPickle isn't available). As a result of | |
156 | serializing, SerialCookie can save almost any Python object to a | |
157 | value, and recover the exact same object when the cookie has been | |
158 | returned. (SerialCookie can yield some strange-looking cookie | |
159 | values, however.) | |
160 | ||
161 | >>> C = Cookie.SerialCookie() | |
162 | >>> C["number"] = 7 | |
163 | >>> C["string"] = "seven" | |
164 | >>> C["number"].value | |
165 | 7 | |
166 | >>> C["string"].value | |
167 | 'seven' | |
168 | >>> print C | |
169 | Set-Cookie: number="I7\012."; | |
170 | Set-Cookie: string="S'seven'\012p1\012."; | |
171 | ||
172 | Be warned, however, if SerialCookie cannot de-serialize a value (because | |
173 | it isn't a valid pickle'd object), IT WILL RAISE AN EXCEPTION. | |
174 | ||
175 | ||
176 | SmartCookie | |
177 | ||
178 | The SmartCookie combines aspects of each of the other two flavors. | |
179 | When setting a value in a dictionary-fashion, the SmartCookie will | |
180 | serialize (ala cPickle) the value *if and only if* it isn't a | |
181 | Python string. String objects are *not* serialized. Similarly, | |
182 | when the load() method parses out values, it attempts to de-serialize | |
183 | the value. If it fails, then it fallsback to treating the value | |
184 | as a string. | |
185 | ||
186 | >>> C = Cookie.SmartCookie() | |
187 | >>> C["number"] = 7 | |
188 | >>> C["string"] = "seven" | |
189 | >>> C["number"].value | |
190 | 7 | |
191 | >>> C["string"].value | |
192 | 'seven' | |
193 | >>> print C | |
194 | Set-Cookie: number="I7\012."; | |
195 | Set-Cookie: string=seven; | |
196 | ||
197 | ||
198 | Backwards Compatibility | |
199 | ----------------------- | |
200 | ||
201 | In order to keep compatibilty with earlier versions of Cookie.py, | |
202 | it is still possible to use Cookie.Cookie() to create a Cookie. In | |
203 | fact, this simply returns a SmartCookie. | |
204 | ||
205 | >>> C = Cookie.Cookie() | |
206 | >>> print C.__class__.__name__ | |
207 | SmartCookie | |
208 | ||
209 | ||
210 | Finis. | |
211 | """ #" | |
212 | # ^ | |
213 | # |----helps out font-lock | |
214 | ||
215 | # | |
216 | # Import our required modules | |
217 | # | |
218 | import string | |
219 | ||
220 | try: | |
221 | from cPickle import dumps, loads | |
222 | except ImportError: | |
223 | from pickle import dumps, loads | |
224 | ||
225 | import re, warnings | |
226 | ||
227 | __all__ = ["CookieError","BaseCookie","SimpleCookie","SerialCookie", | |
228 | "SmartCookie","Cookie"] | |
229 | ||
230 | _nulljoin = ''.join | |
231 | _spacejoin = ' '.join | |
232 | ||
233 | # | |
234 | # Define an exception visible to External modules | |
235 | # | |
236 | class CookieError(Exception): | |
237 | pass | |
238 | ||
239 | ||
240 | # These quoting routines conform to the RFC2109 specification, which in | |
241 | # turn references the character definitions from RFC2068. They provide | |
242 | # a two-way quoting algorithm. Any non-text character is translated | |
243 | # into a 4 character sequence: a forward-slash followed by the | |
244 | # three-digit octal equivalent of the character. Any '\' or '"' is | |
245 | # quoted with a preceeding '\' slash. | |
246 | # | |
247 | # These are taken from RFC2068 and RFC2109. | |
248 | # _LegalChars is the list of chars which don't require "'s | |
249 | # _Translator hash-table for fast quoting | |
250 | # | |
251 | _LegalChars = string.ascii_letters + string.digits + "!#$%&'*+-.^_`|~" | |
252 | _Translator = { | |
253 | '\000' : '\\000', '\001' : '\\001', '\002' : '\\002', | |
254 | '\003' : '\\003', '\004' : '\\004', '\005' : '\\005', | |
255 | '\006' : '\\006', '\007' : '\\007', '\010' : '\\010', | |
256 | '\011' : '\\011', '\012' : '\\012', '\013' : '\\013', | |
257 | '\014' : '\\014', '\015' : '\\015', '\016' : '\\016', | |
258 | '\017' : '\\017', '\020' : '\\020', '\021' : '\\021', | |
259 | '\022' : '\\022', '\023' : '\\023', '\024' : '\\024', | |
260 | '\025' : '\\025', '\026' : '\\026', '\027' : '\\027', | |
261 | '\030' : '\\030', '\031' : '\\031', '\032' : '\\032', | |
262 | '\033' : '\\033', '\034' : '\\034', '\035' : '\\035', | |
263 | '\036' : '\\036', '\037' : '\\037', | |
264 | ||
265 | '"' : '\\"', '\\' : '\\\\', | |
266 | ||
267 | '\177' : '\\177', '\200' : '\\200', '\201' : '\\201', | |
268 | '\202' : '\\202', '\203' : '\\203', '\204' : '\\204', | |
269 | '\205' : '\\205', '\206' : '\\206', '\207' : '\\207', | |
270 | '\210' : '\\210', '\211' : '\\211', '\212' : '\\212', | |
271 | '\213' : '\\213', '\214' : '\\214', '\215' : '\\215', | |
272 | '\216' : '\\216', '\217' : '\\217', '\220' : '\\220', | |
273 | '\221' : '\\221', '\222' : '\\222', '\223' : '\\223', | |
274 | '\224' : '\\224', '\225' : '\\225', '\226' : '\\226', | |
275 | '\227' : '\\227', '\230' : '\\230', '\231' : '\\231', | |
276 | '\232' : '\\232', '\233' : '\\233', '\234' : '\\234', | |
277 | '\235' : '\\235', '\236' : '\\236', '\237' : '\\237', | |
278 | '\240' : '\\240', '\241' : '\\241', '\242' : '\\242', | |
279 | '\243' : '\\243', '\244' : '\\244', '\245' : '\\245', | |
280 | '\246' : '\\246', '\247' : '\\247', '\250' : '\\250', | |
281 | '\251' : '\\251', '\252' : '\\252', '\253' : '\\253', | |
282 | '\254' : '\\254', '\255' : '\\255', '\256' : '\\256', | |
283 | '\257' : '\\257', '\260' : '\\260', '\261' : '\\261', | |
284 | '\262' : '\\262', '\263' : '\\263', '\264' : '\\264', | |
285 | '\265' : '\\265', '\266' : '\\266', '\267' : '\\267', | |
286 | '\270' : '\\270', '\271' : '\\271', '\272' : '\\272', | |
287 | '\273' : '\\273', '\274' : '\\274', '\275' : '\\275', | |
288 | '\276' : '\\276', '\277' : '\\277', '\300' : '\\300', | |
289 | '\301' : '\\301', '\302' : '\\302', '\303' : '\\303', | |
290 | '\304' : '\\304', '\305' : '\\305', '\306' : '\\306', | |
291 | '\307' : '\\307', '\310' : '\\310', '\311' : '\\311', | |
292 | '\312' : '\\312', '\313' : '\\313', '\314' : '\\314', | |
293 | '\315' : '\\315', '\316' : '\\316', '\317' : '\\317', | |
294 | '\320' : '\\320', '\321' : '\\321', '\322' : '\\322', | |
295 | '\323' : '\\323', '\324' : '\\324', '\325' : '\\325', | |
296 | '\326' : '\\326', '\327' : '\\327', '\330' : '\\330', | |
297 | '\331' : '\\331', '\332' : '\\332', '\333' : '\\333', | |
298 | '\334' : '\\334', '\335' : '\\335', '\336' : '\\336', | |
299 | '\337' : '\\337', '\340' : '\\340', '\341' : '\\341', | |
300 | '\342' : '\\342', '\343' : '\\343', '\344' : '\\344', | |
301 | '\345' : '\\345', '\346' : '\\346', '\347' : '\\347', | |
302 | '\350' : '\\350', '\351' : '\\351', '\352' : '\\352', | |
303 | '\353' : '\\353', '\354' : '\\354', '\355' : '\\355', | |
304 | '\356' : '\\356', '\357' : '\\357', '\360' : '\\360', | |
305 | '\361' : '\\361', '\362' : '\\362', '\363' : '\\363', | |
306 | '\364' : '\\364', '\365' : '\\365', '\366' : '\\366', | |
307 | '\367' : '\\367', '\370' : '\\370', '\371' : '\\371', | |
308 | '\372' : '\\372', '\373' : '\\373', '\374' : '\\374', | |
309 | '\375' : '\\375', '\376' : '\\376', '\377' : '\\377' | |
310 | } | |
311 | ||
312 | def _quote(str, LegalChars=_LegalChars, | |
313 | idmap=string._idmap, translate=string.translate): | |
314 | # | |
315 | # If the string does not need to be double-quoted, | |
316 | # then just return the string. Otherwise, surround | |
317 | # the string in doublequotes and precede quote (with a \) | |
318 | # special characters. | |
319 | # | |
320 | if "" == translate(str, idmap, LegalChars): | |
321 | return str | |
322 | else: | |
323 | return '"' + _nulljoin( map(_Translator.get, str, str) ) + '"' | |
324 | # end _quote | |
325 | ||
326 | ||
327 | _OctalPatt = re.compile(r"\\[0-3][0-7][0-7]") | |
328 | _QuotePatt = re.compile(r"[\\].") | |
329 | ||
330 | def _unquote(str): | |
331 | # If there aren't any doublequotes, | |
332 | # then there can't be any special characters. See RFC 2109. | |
333 | if len(str) < 2: | |
334 | return str | |
335 | if str[0] != '"' or str[-1] != '"': | |
336 | return str | |
337 | ||
338 | # We have to assume that we must decode this string. | |
339 | # Down to work. | |
340 | ||
341 | # Remove the "s | |
342 | str = str[1:-1] | |
343 | ||
344 | # Check for special sequences. Examples: | |
345 | # \012 --> \n | |
346 | # \" --> " | |
347 | # | |
348 | i = 0 | |
349 | n = len(str) | |
350 | res = [] | |
351 | while 0 <= i < n: | |
352 | Omatch = _OctalPatt.search(str, i) | |
353 | Qmatch = _QuotePatt.search(str, i) | |
354 | if not Omatch and not Qmatch: # Neither matched | |
355 | res.append(str[i:]) | |
356 | break | |
357 | # else: | |
358 | j = k = -1 | |
359 | if Omatch: j = Omatch.start(0) | |
360 | if Qmatch: k = Qmatch.start(0) | |
361 | if Qmatch and ( not Omatch or k < j ): # QuotePatt matched | |
362 | res.append(str[i:k]) | |
363 | res.append(str[k+1]) | |
364 | i = k+2 | |
365 | else: # OctalPatt matched | |
366 | res.append(str[i:j]) | |
367 | res.append( chr( int(str[j+1:j+4], 8) ) ) | |
368 | i = j+4 | |
369 | return _nulljoin(res) | |
370 | # end _unquote | |
371 | ||
372 | # The _getdate() routine is used to set the expiration time in | |
373 | # the cookie's HTTP header. By default, _getdate() returns the | |
374 | # current time in the appropriate "expires" format for a | |
375 | # Set-Cookie header. The one optional argument is an offset from | |
376 | # now, in seconds. For example, an offset of -3600 means "one hour ago". | |
377 | # The offset may be a floating point number. | |
378 | # | |
379 | ||
380 | _weekdayname = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] | |
381 | ||
382 | _monthname = [None, | |
383 | 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', | |
384 | 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] | |
385 | ||
386 | def _getdate(future=0, weekdayname=_weekdayname, monthname=_monthname): | |
387 | from time import gmtime, time | |
388 | now = time() | |
389 | year, month, day, hh, mm, ss, wd, y, z = gmtime(now + future) | |
390 | return "%s, %02d-%3s-%4d %02d:%02d:%02d GMT" % \ | |
391 | (weekdayname[wd], day, monthname[month], year, hh, mm, ss) | |
392 | ||
393 | ||
394 | # | |
395 | # A class to hold ONE key,value pair. | |
396 | # In a cookie, each such pair may have several attributes. | |
397 | # so this class is used to keep the attributes associated | |
398 | # with the appropriate key,value pair. | |
399 | # This class also includes a coded_value attribute, which | |
400 | # is used to hold the network representation of the | |
401 | # value. This is most useful when Python objects are | |
402 | # pickled for network transit. | |
403 | # | |
404 | ||
405 | class Morsel(dict): | |
406 | # RFC 2109 lists these attributes as reserved: | |
407 | # path comment domain | |
408 | # max-age secure version | |
409 | # | |
410 | # For historical reasons, these attributes are also reserved: | |
411 | # expires | |
412 | # | |
413 | # This dictionary provides a mapping from the lowercase | |
414 | # variant on the left to the appropriate traditional | |
415 | # formatting on the right. | |
416 | _reserved = { "expires" : "expires", | |
417 | "path" : "Path", | |
418 | "comment" : "Comment", | |
419 | "domain" : "Domain", | |
420 | "max-age" : "Max-Age", | |
421 | "secure" : "secure", | |
422 | "version" : "Version", | |
423 | } | |
424 | ||
425 | def __init__(self): | |
426 | # Set defaults | |
427 | self.key = self.value = self.coded_value = None | |
428 | ||
429 | # Set default attributes | |
430 | for K in self._reserved: | |
431 | dict.__setitem__(self, K, "") | |
432 | # end __init__ | |
433 | ||
434 | def __setitem__(self, K, V): | |
435 | K = K.lower() | |
436 | if not K in self._reserved: | |
437 | raise CookieError("Invalid Attribute %s" % K) | |
438 | dict.__setitem__(self, K, V) | |
439 | # end __setitem__ | |
440 | ||
441 | def isReservedKey(self, K): | |
442 | return K.lower() in self._reserved | |
443 | # end isReservedKey | |
444 | ||
445 | def set(self, key, val, coded_val, | |
446 | LegalChars=_LegalChars, | |
447 | idmap=string._idmap, translate=string.translate ): | |
448 | # First we verify that the key isn't a reserved word | |
449 | # Second we make sure it only contains legal characters | |
450 | if key.lower() in self._reserved: | |
451 | raise CookieError("Attempt to set a reserved key: %s" % key) | |
452 | if "" != translate(key, idmap, LegalChars): | |
453 | raise CookieError("Illegal key value: %s" % key) | |
454 | ||
455 | # It's a good key, so save it. | |
456 | self.key = key | |
457 | self.value = val | |
458 | self.coded_value = coded_val | |
459 | # end set | |
460 | ||
461 | def output(self, attrs=None, header = "Set-Cookie:"): | |
462 | return "%s %s" % ( header, self.OutputString(attrs) ) | |
463 | ||
464 | __str__ = output | |
465 | ||
466 | def __repr__(self): | |
467 | return '<%s: %s=%s>' % (self.__class__.__name__, | |
468 | self.key, repr(self.value) ) | |
469 | ||
470 | def js_output(self, attrs=None): | |
471 | # Print javascript | |
472 | return """ | |
473 | <SCRIPT LANGUAGE="JavaScript"> | |
474 | <!-- begin hiding | |
475 | document.cookie = \"%s\" | |
476 | // end hiding --> | |
477 | </script> | |
478 | """ % ( self.OutputString(attrs), ) | |
479 | # end js_output() | |
480 | ||
481 | def OutputString(self, attrs=None): | |
482 | # Build up our result | |
483 | # | |
484 | result = [] | |
485 | RA = result.append | |
486 | ||
487 | # First, the key=value pair | |
488 | RA("%s=%s;" % (self.key, self.coded_value)) | |
489 | ||
490 | # Now add any defined attributes | |
491 | if attrs is None: | |
492 | attrs = self._reserved | |
493 | items = self.items() | |
494 | items.sort() | |
495 | for K,V in items: | |
496 | if V == "": continue | |
497 | if K not in attrs: continue | |
498 | if K == "expires" and type(V) == type(1): | |
499 | RA("%s=%s;" % (self._reserved[K], _getdate(V))) | |
500 | elif K == "max-age" and type(V) == type(1): | |
501 | RA("%s=%d;" % (self._reserved[K], V)) | |
502 | elif K == "secure": | |
503 | RA("%s;" % self._reserved[K]) | |
504 | else: | |
505 | RA("%s=%s;" % (self._reserved[K], V)) | |
506 | ||
507 | # Return the result | |
508 | return _spacejoin(result) | |
509 | # end OutputString | |
510 | # end Morsel class | |
511 | ||
512 | ||
513 | ||
514 | # | |
515 | # Pattern for finding cookie | |
516 | # | |
517 | # This used to be strict parsing based on the RFC2109 and RFC2068 | |
518 | # specifications. I have since discovered that MSIE 3.0x doesn't | |
519 | # follow the character rules outlined in those specs. As a | |
520 | # result, the parsing rules here are less strict. | |
521 | # | |
522 | ||
523 | _LegalCharsPatt = r"[\w\d!#%&'~_`><@,:/\$\*\+\-\.\^\|\)\(\?\}\{\=]" | |
524 | _CookiePattern = re.compile( | |
525 | r"(?x)" # This is a Verbose pattern | |
526 | r"(?P<key>" # Start of group 'key' | |
527 | ""+ _LegalCharsPatt +"+?" # Any word of at least one letter, nongreedy | |
528 | r")" # End of group 'key' | |
529 | r"\s*=\s*" # Equal Sign | |
530 | r"(?P<val>" # Start of group 'val' | |
531 | r'"(?:[^\\"]|\\.)*"' # Any doublequoted string | |
532 | r"|" # or | |
533 | ""+ _LegalCharsPatt +"*" # Any word or empty string | |
534 | r")" # End of group 'val' | |
535 | r"\s*;?" # Probably ending in a semi-colon | |
536 | ) | |
537 | ||
538 | ||
539 | # At long last, here is the cookie class. | |
540 | # Using this class is almost just like using a dictionary. | |
541 | # See this module's docstring for example usage. | |
542 | # | |
543 | class BaseCookie(dict): | |
544 | # A container class for a set of Morsels | |
545 | # | |
546 | ||
547 | def value_decode(self, val): | |
548 | """real_value, coded_value = value_decode(STRING) | |
549 | Called prior to setting a cookie's value from the network | |
550 | representation. The VALUE is the value read from HTTP | |
551 | header. | |
552 | Override this function to modify the behavior of cookies. | |
553 | """ | |
554 | return val, val | |
555 | # end value_encode | |
556 | ||
557 | def value_encode(self, val): | |
558 | """real_value, coded_value = value_encode(VALUE) | |
559 | Called prior to setting a cookie's value from the dictionary | |
560 | representation. The VALUE is the value being assigned. | |
561 | Override this function to modify the behavior of cookies. | |
562 | """ | |
563 | strval = str(val) | |
564 | return strval, strval | |
565 | # end value_encode | |
566 | ||
567 | def __init__(self, input=None): | |
568 | if input: self.load(input) | |
569 | # end __init__ | |
570 | ||
571 | def __set(self, key, real_value, coded_value): | |
572 | """Private method for setting a cookie's value""" | |
573 | M = self.get(key, Morsel()) | |
574 | M.set(key, real_value, coded_value) | |
575 | dict.__setitem__(self, key, M) | |
576 | # end __set | |
577 | ||
578 | def __setitem__(self, key, value): | |
579 | """Dictionary style assignment.""" | |
580 | rval, cval = self.value_encode(value) | |
581 | self.__set(key, rval, cval) | |
582 | # end __setitem__ | |
583 | ||
584 | def output(self, attrs=None, header="Set-Cookie:", sep="\n"): | |
585 | """Return a string suitable for HTTP.""" | |
586 | result = [] | |
587 | items = self.items() | |
588 | items.sort() | |
589 | for K,V in items: | |
590 | result.append( V.output(attrs, header) ) | |
591 | return sep.join(result) | |
592 | # end output | |
593 | ||
594 | __str__ = output | |
595 | ||
596 | def __repr__(self): | |
597 | L = [] | |
598 | items = self.items() | |
599 | items.sort() | |
600 | for K,V in items: | |
601 | L.append( '%s=%s' % (K,repr(V.value) ) ) | |
602 | return '<%s: %s>' % (self.__class__.__name__, _spacejoin(L)) | |
603 | ||
604 | def js_output(self, attrs=None): | |
605 | """Return a string suitable for JavaScript.""" | |
606 | result = [] | |
607 | items = self.items() | |
608 | items.sort() | |
609 | for K,V in items: | |
610 | result.append( V.js_output(attrs) ) | |
611 | return _nulljoin(result) | |
612 | # end js_output | |
613 | ||
614 | def load(self, rawdata): | |
615 | """Load cookies from a string (presumably HTTP_COOKIE) or | |
616 | from a dictionary. Loading cookies from a dictionary 'd' | |
617 | is equivalent to calling: | |
618 | map(Cookie.__setitem__, d.keys(), d.values()) | |
619 | """ | |
620 | if type(rawdata) == type(""): | |
621 | self.__ParseString(rawdata) | |
622 | else: | |
623 | self.update(rawdata) | |
624 | return | |
625 | # end load() | |
626 | ||
627 | def __ParseString(self, str, patt=_CookiePattern): | |
628 | i = 0 # Our starting point | |
629 | n = len(str) # Length of string | |
630 | M = None # current morsel | |
631 | ||
632 | while 0 <= i < n: | |
633 | # Start looking for a cookie | |
634 | match = patt.search(str, i) | |
635 | if not match: break # No more cookies | |
636 | ||
637 | K,V = match.group("key"), match.group("val") | |
638 | i = match.end(0) | |
639 | ||
640 | # Parse the key, value in case it's metainfo | |
641 | if K[0] == "$": | |
642 | # We ignore attributes which pertain to the cookie | |
643 | # mechanism as a whole. See RFC 2109. | |
644 | # (Does anyone care?) | |
645 | if M: | |
646 | M[ K[1:] ] = V | |
647 | elif K.lower() in Morsel._reserved: | |
648 | if M: | |
649 | M[ K ] = _unquote(V) | |
650 | else: | |
651 | rval, cval = self.value_decode(V) | |
652 | self.__set(K, rval, cval) | |
653 | M = self[K] | |
654 | # end __ParseString | |
655 | # end BaseCookie class | |
656 | ||
657 | class SimpleCookie(BaseCookie): | |
658 | """SimpleCookie | |
659 | SimpleCookie supports strings as cookie values. When setting | |
660 | the value using the dictionary assignment notation, SimpleCookie | |
661 | calls the builtin str() to convert the value to a string. Values | |
662 | received from HTTP are kept as strings. | |
663 | """ | |
664 | def value_decode(self, val): | |
665 | return _unquote( val ), val | |
666 | def value_encode(self, val): | |
667 | strval = str(val) | |
668 | return strval, _quote( strval ) | |
669 | # end SimpleCookie | |
670 | ||
671 | class SerialCookie(BaseCookie): | |
672 | """SerialCookie | |
673 | SerialCookie supports arbitrary objects as cookie values. All | |
674 | values are serialized (using cPickle) before being sent to the | |
675 | client. All incoming values are assumed to be valid Pickle | |
676 | representations. IF AN INCOMING VALUE IS NOT IN A VALID PICKLE | |
677 | FORMAT, THEN AN EXCEPTION WILL BE RAISED. | |
678 | ||
679 | Note: Large cookie values add overhead because they must be | |
680 | retransmitted on every HTTP transaction. | |
681 | ||
682 | Note: HTTP has a 2k limit on the size of a cookie. This class | |
683 | does not check for this limit, so be careful!!! | |
684 | """ | |
685 | def __init__(self, input=None): | |
686 | warnings.warn("SerialCookie class is insecure; do not use it", | |
687 | DeprecationWarning) | |
688 | BaseCookie.__init__(self, input) | |
689 | # end __init__ | |
690 | def value_decode(self, val): | |
691 | # This could raise an exception! | |
692 | return loads( _unquote(val) ), val | |
693 | def value_encode(self, val): | |
694 | return val, _quote( dumps(val) ) | |
695 | # end SerialCookie | |
696 | ||
697 | class SmartCookie(BaseCookie): | |
698 | """SmartCookie | |
699 | SmartCookie supports arbitrary objects as cookie values. If the | |
700 | object is a string, then it is quoted. If the object is not a | |
701 | string, however, then SmartCookie will use cPickle to serialize | |
702 | the object into a string representation. | |
703 | ||
704 | Note: Large cookie values add overhead because they must be | |
705 | retransmitted on every HTTP transaction. | |
706 | ||
707 | Note: HTTP has a 2k limit on the size of a cookie. This class | |
708 | does not check for this limit, so be careful!!! | |
709 | """ | |
710 | def __init__(self, input=None): | |
711 | warnings.warn("Cookie/SmartCookie class is insecure; do not use it", | |
712 | DeprecationWarning) | |
713 | BaseCookie.__init__(self, input) | |
714 | # end __init__ | |
715 | def value_decode(self, val): | |
716 | strval = _unquote(val) | |
717 | try: | |
718 | return loads(strval), val | |
719 | except: | |
720 | return strval, val | |
721 | def value_encode(self, val): | |
722 | if type(val) == type(""): | |
723 | return val, _quote(val) | |
724 | else: | |
725 | return val, _quote( dumps(val) ) | |
726 | # end SmartCookie | |
727 | ||
728 | ||
729 | ########################################################### | |
730 | # Backwards Compatibility: Don't break any existing code! | |
731 | ||
732 | # We provide Cookie() as an alias for SmartCookie() | |
733 | Cookie = SmartCookie | |
734 | ||
735 | # | |
736 | ########################################################### | |
737 | ||
738 | def _test(): | |
739 | import doctest, Cookie | |
740 | return doctest.testmod(Cookie) | |
741 | ||
742 | if __name__ == "__main__": | |
743 | _test() | |
744 | ||
745 | ||
746 | #Local Variables: | |
747 | #tab-width: 4 | |
748 | #end: |