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