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