Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 9c1a9b2

Browse files
Issue #2211: Updated the implementation of the http.cookies.Morsel class.
Setting attributes key, value and coded_value directly now is deprecated. update() and setdefault() now transform and check keys. Comparing for equality now takes into account attributes key, value and coded_value. copy() now returns a Morsel, not a dict. repr() now contains all attributes. Optimized checking keys and quoting values. Added new tests. Original patch by Demian Brecht.
1 parent 3505bd8 commit 9c1a9b2

5 files changed

Lines changed: 299 additions & 86 deletions

File tree

Doc/library/http.cookies.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,16 +148,28 @@ Morsel Objects
148148

149149
The value of the cookie.
150150

151+
.. deprecated:: 3.5
152+
Setting :attr:`~Morsel.value` directly has been deprecated in favour of
153+
using :func:`~Morsel.set`
154+
151155

152156
.. attribute:: Morsel.coded_value
153157

154158
The encoded value of the cookie --- this is what should be sent.
155159

160+
.. deprecated:: 3.5
161+
Setting :attr:`~Morsel.coded_value` directly has been deprecated in
162+
favour of using :func:`~Morsel.set`
163+
156164

157165
.. attribute:: Morsel.key
158166

159167
The name of the cookie.
160168

169+
.. deprecated:: 3.5
170+
Setting :attr:`~Morsel.key` directly has been deprecated in
171+
favour of using :func:`~Morsel.set`
172+
161173

162174
.. method:: Morsel.set(key, value, coded_value)
163175

Doc/whatsnew/3.5.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,12 @@ Deprecated Python modules, functions and methods
512512
``True``, but this default is deprecated. Specify the *decode_data* keyword
513513
with an appropriate value to avoid the deprecation warning.
514514

515+
* :class:`~http.cookies.Morsel` has previously allowed for setting attributes
516+
:attr:`~http.cookies.Morsel.key`, :attr:`~http.cookies.Morsel.value` and
517+
:attr:`~http.cookies.Morsel.coded_value`. Use the preferred
518+
:func:`~http.cookies.Morsel.set` method in order to avoid the deprecation
519+
warning.
520+
515521

516522
Deprecated functions and types of the C API
517523
-------------------------------------------

Lib/http/cookies.py

Lines changed: 94 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,12 @@
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):
241195
def _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

Comments
 (0)