138138_semispacejoin = '; ' .join
139139_spacejoin = ' ' .join
140140
141+ def _warn_deprecated_setter (setter ):
142+ import warnings
143+ msg = ('The .%s setter is deprecated. The attribute will be read-only in '
144+ 'future releases. Please use the set() method instead.' % setter )
145+ warnings .warn (msg , DeprecationWarning , stacklevel = 3 )
146+
141147#
142148# Define an exception visible to External modules
143149#
@@ -151,88 +157,36 @@ class CookieError(Exception):
151157# into a 4 character sequence: a forward-slash followed by the
152158# three-digit octal equivalent of the character. Any '\' or '"' is
153159# quoted with a preceeding '\' slash.
160+ # Because of the way browsers really handle cookies (as opposed to what
161+ # the RFC says) we also encode "," and ";".
154162#
155163# These are taken from RFC2068 and RFC2109.
156164# _LegalChars is the list of chars which don't require "'s
157165# _Translator hash-table for fast quoting
158166#
159- _LegalChars = string .ascii_letters + string .digits + "!#$%&'*+-.^_`|~:"
160- _Translator = {
161- '\000 ' : '\\ 000' , '\001 ' : '\\ 001' , '\002 ' : '\\ 002' ,
162- '\003 ' : '\\ 003' , '\004 ' : '\\ 004' , '\005 ' : '\\ 005' ,
163- '\006 ' : '\\ 006' , '\007 ' : '\\ 007' , '\010 ' : '\\ 010' ,
164- '\011 ' : '\\ 011' , '\012 ' : '\\ 012' , '\013 ' : '\\ 013' ,
165- '\014 ' : '\\ 014' , '\015 ' : '\\ 015' , '\016 ' : '\\ 016' ,
166- '\017 ' : '\\ 017' , '\020 ' : '\\ 020' , '\021 ' : '\\ 021' ,
167- '\022 ' : '\\ 022' , '\023 ' : '\\ 023' , '\024 ' : '\\ 024' ,
168- '\025 ' : '\\ 025' , '\026 ' : '\\ 026' , '\027 ' : '\\ 027' ,
169- '\030 ' : '\\ 030' , '\031 ' : '\\ 031' , '\032 ' : '\\ 032' ,
170- '\033 ' : '\\ 033' , '\034 ' : '\\ 034' , '\035 ' : '\\ 035' ,
171- '\036 ' : '\\ 036' , '\037 ' : '\\ 037' ,
172-
173- # Because of the way browsers really handle cookies (as opposed
174- # to what the RFC says) we also encode , and ;
175-
176- ',' : '\\ 054' , ';' : '\\ 073' ,
177-
178- '"' : '\\ "' , '\\ ' : '\\ \\ ' ,
179-
180- '\177 ' : '\\ 177' , '\200 ' : '\\ 200' , '\201 ' : '\\ 201' ,
181- '\202 ' : '\\ 202' , '\203 ' : '\\ 203' , '\204 ' : '\\ 204' ,
182- '\205 ' : '\\ 205' , '\206 ' : '\\ 206' , '\207 ' : '\\ 207' ,
183- '\210 ' : '\\ 210' , '\211 ' : '\\ 211' , '\212 ' : '\\ 212' ,
184- '\213 ' : '\\ 213' , '\214 ' : '\\ 214' , '\215 ' : '\\ 215' ,
185- '\216 ' : '\\ 216' , '\217 ' : '\\ 217' , '\220 ' : '\\ 220' ,
186- '\221 ' : '\\ 221' , '\222 ' : '\\ 222' , '\223 ' : '\\ 223' ,
187- '\224 ' : '\\ 224' , '\225 ' : '\\ 225' , '\226 ' : '\\ 226' ,
188- '\227 ' : '\\ 227' , '\230 ' : '\\ 230' , '\231 ' : '\\ 231' ,
189- '\232 ' : '\\ 232' , '\233 ' : '\\ 233' , '\234 ' : '\\ 234' ,
190- '\235 ' : '\\ 235' , '\236 ' : '\\ 236' , '\237 ' : '\\ 237' ,
191- '\240 ' : '\\ 240' , '\241 ' : '\\ 241' , '\242 ' : '\\ 242' ,
192- '\243 ' : '\\ 243' , '\244 ' : '\\ 244' , '\245 ' : '\\ 245' ,
193- '\246 ' : '\\ 246' , '\247 ' : '\\ 247' , '\250 ' : '\\ 250' ,
194- '\251 ' : '\\ 251' , '\252 ' : '\\ 252' , '\253 ' : '\\ 253' ,
195- '\254 ' : '\\ 254' , '\255 ' : '\\ 255' , '\256 ' : '\\ 256' ,
196- '\257 ' : '\\ 257' , '\260 ' : '\\ 260' , '\261 ' : '\\ 261' ,
197- '\262 ' : '\\ 262' , '\263 ' : '\\ 263' , '\264 ' : '\\ 264' ,
198- '\265 ' : '\\ 265' , '\266 ' : '\\ 266' , '\267 ' : '\\ 267' ,
199- '\270 ' : '\\ 270' , '\271 ' : '\\ 271' , '\272 ' : '\\ 272' ,
200- '\273 ' : '\\ 273' , '\274 ' : '\\ 274' , '\275 ' : '\\ 275' ,
201- '\276 ' : '\\ 276' , '\277 ' : '\\ 277' , '\300 ' : '\\ 300' ,
202- '\301 ' : '\\ 301' , '\302 ' : '\\ 302' , '\303 ' : '\\ 303' ,
203- '\304 ' : '\\ 304' , '\305 ' : '\\ 305' , '\306 ' : '\\ 306' ,
204- '\307 ' : '\\ 307' , '\310 ' : '\\ 310' , '\311 ' : '\\ 311' ,
205- '\312 ' : '\\ 312' , '\313 ' : '\\ 313' , '\314 ' : '\\ 314' ,
206- '\315 ' : '\\ 315' , '\316 ' : '\\ 316' , '\317 ' : '\\ 317' ,
207- '\320 ' : '\\ 320' , '\321 ' : '\\ 321' , '\322 ' : '\\ 322' ,
208- '\323 ' : '\\ 323' , '\324 ' : '\\ 324' , '\325 ' : '\\ 325' ,
209- '\326 ' : '\\ 326' , '\327 ' : '\\ 327' , '\330 ' : '\\ 330' ,
210- '\331 ' : '\\ 331' , '\332 ' : '\\ 332' , '\333 ' : '\\ 333' ,
211- '\334 ' : '\\ 334' , '\335 ' : '\\ 335' , '\336 ' : '\\ 336' ,
212- '\337 ' : '\\ 337' , '\340 ' : '\\ 340' , '\341 ' : '\\ 341' ,
213- '\342 ' : '\\ 342' , '\343 ' : '\\ 343' , '\344 ' : '\\ 344' ,
214- '\345 ' : '\\ 345' , '\346 ' : '\\ 346' , '\347 ' : '\\ 347' ,
215- '\350 ' : '\\ 350' , '\351 ' : '\\ 351' , '\352 ' : '\\ 352' ,
216- '\353 ' : '\\ 353' , '\354 ' : '\\ 354' , '\355 ' : '\\ 355' ,
217- '\356 ' : '\\ 356' , '\357 ' : '\\ 357' , '\360 ' : '\\ 360' ,
218- '\361 ' : '\\ 361' , '\362 ' : '\\ 362' , '\363 ' : '\\ 363' ,
219- '\364 ' : '\\ 364' , '\365 ' : '\\ 365' , '\366 ' : '\\ 366' ,
220- '\367 ' : '\\ 367' , '\370 ' : '\\ 370' , '\371 ' : '\\ 371' ,
221- '\372 ' : '\\ 372' , '\373 ' : '\\ 373' , '\374 ' : '\\ 374' ,
222- '\375 ' : '\\ 375' , '\376 ' : '\\ 376' , '\377 ' : '\\ 377'
223- }
167+ _LegalChars = string .ascii_letters + string .digits + "!#$%&'*+-.^_`|~:"
168+ _UnescapedChars = _LegalChars + ' ()/<=>?@[]{}'
169+
170+ _Translator = {n : '\\ %03o' % n
171+ for n in set (range (256 )) - set (map (ord , _UnescapedChars ))}
172+ _Translator .update ({
173+ ord ('"' ): '\\ "' ,
174+ ord ('\\ ' ): '\\ \\ ' ,
175+ })
224176
225- def _quote (str , LegalChars = _LegalChars ):
177+ _is_legal_key = re .compile ('[%s]+' % _LegalChars ).fullmatch
178+
179+ def _quote (str ):
226180 r"""Quote a string for use in a cookie header.
227181
228182 If the string does not need to be double-quoted, then just return the
229183 string. Otherwise, surround the string in doublequotes and quote
230184 (with a \) special characters.
231185 """
232- if all ( c in LegalChars for c in str ):
186+ if str is None or _is_legal_key ( str ):
233187 return str
234188 else :
235- return '"' + _nulljoin ( _Translator . get ( s , s ) for s in str ) + '"'
189+ return '"' + str . translate ( _Translator ) + '"'
236190
237191
238192_OctalPatt = re .compile (r"\\[0-3][0-7][0-7]" )
@@ -241,7 +195,7 @@ def _quote(str, LegalChars=_LegalChars):
241195def _unquote (str ):
242196 # If there aren't any doublequotes,
243197 # then there can't be any special characters. See RFC 2109.
244- if len (str ) < 2 :
198+ if str is None or len (str ) < 2 :
245199 return str
246200 if str [0 ] != '"' or str [- 1 ] != '"' :
247201 return str
@@ -339,42 +293,97 @@ class Morsel(dict):
339293
340294 def __init__ (self ):
341295 # Set defaults
342- self .key = self .value = self .coded_value = None
296+ self ._key = self ._value = self ._coded_value = None
343297
344298 # Set default attributes
345299 for key in self ._reserved :
346300 dict .__setitem__ (self , key , "" )
347301
302+ @property
303+ def key (self ):
304+ return self ._key
305+
306+ @key .setter
307+ def key (self , key ):
308+ _warn_deprecated_setter ('key' )
309+ self ._key = key
310+
311+ @property
312+ def value (self ):
313+ return self ._value
314+
315+ @value .setter
316+ def value (self , value ):
317+ _warn_deprecated_setter ('value' )
318+ self ._value = value
319+
320+ @property
321+ def coded_value (self ):
322+ return self ._coded_value
323+
324+ @coded_value .setter
325+ def coded_value (self , coded_value ):
326+ _warn_deprecated_setter ('coded_value' )
327+ self ._coded_value = coded_value
328+
348329 def __setitem__ (self , K , V ):
349330 K = K .lower ()
350331 if not K in self ._reserved :
351- raise CookieError ("Invalid Attribute %s " % K )
332+ raise CookieError ("Invalid attribute %r " % ( K ,) )
352333 dict .__setitem__ (self , K , V )
353334
335+ def setdefault (self , key , val = None ):
336+ key = key .lower ()
337+ if key not in self ._reserved :
338+ raise CookieError ("Invalid attribute %r" % (key ,))
339+ return dict .setdefault (self , key , val )
340+
341+ def __eq__ (self , morsel ):
342+ if not isinstance (morsel , Morsel ):
343+ return NotImplemented
344+ return (dict .__eq__ (self , morsel ) and
345+ self ._value == morsel ._value and
346+ self ._key == morsel ._key and
347+ self ._coded_value == morsel ._coded_value )
348+
349+ __ne__ = object .__ne__
350+
351+ def copy (self ):
352+ morsel = Morsel ()
353+ dict .update (morsel , self )
354+ morsel .__dict__ .update (self .__dict__ )
355+ return morsel
356+
357+ def update (self , values ):
358+ data = {}
359+ for key , val in dict (values ).items ():
360+ key = key .lower ()
361+ if key not in self ._reserved :
362+ raise CookieError ("Invalid attribute %r" % (key ,))
363+ data [key ] = val
364+ dict .update (self , data )
365+
354366 def isReservedKey (self , K ):
355367 return K .lower () in self ._reserved
356368
357- def set (self , key , val , coded_val , LegalChars = _LegalChars ):
358- # First we verify that the key isn't a reserved word
359- # Second we make sure it only contains legal characters
369+ def set (self , key , val , coded_val ):
360370 if key .lower () in self ._reserved :
361- raise CookieError (" Attempt to set a reserved key: %s" % key )
362- if any ( c not in LegalChars for c in key ):
363- raise CookieError (" Illegal key value: %s" % key )
371+ raise CookieError (' Attempt to set a reserved key %r' % ( key ,) )
372+ if not _is_legal_key ( key ):
373+ raise CookieError (' Illegal key %r' % ( key ,) )
364374
365375 # It's a good key, so save it.
366- self .key = key
367- self .value = val
368- self .coded_value = coded_val
376+ self ._key = key
377+ self ._value = val
378+ self ._coded_value = coded_val
369379
370380 def output (self , attrs = None , header = "Set-Cookie:" ):
371381 return "%s %s" % (header , self .OutputString (attrs ))
372382
373383 __str__ = output
374384
375385 def __repr__ (self ):
376- return '<%s: %s=%s>' % (self .__class__ .__name__ ,
377- self .key , repr (self .value ))
386+ return '<%s: %s>' % (self .__class__ .__name__ , self .OutputString ())
378387
379388 def js_output (self , attrs = None ):
380389 # Print javascript
@@ -408,10 +417,9 @@ def OutputString(self, attrs=None):
408417 append ("%s=%s" % (self ._reserved [key ], _getdate (value )))
409418 elif key == "max-age" and isinstance (value , int ):
410419 append ("%s=%d" % (self ._reserved [key ], value ))
411- elif key == "secure" :
412- append (str (self ._reserved [key ]))
413- elif key == "httponly" :
414- append (str (self ._reserved [key ]))
420+ elif key in self ._flags :
421+ if value :
422+ append (str (self ._reserved [key ]))
415423 else :
416424 append ("%s=%s" % (self ._reserved [key ], value ))
417425
0 commit comments