test_cookies.py 95 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. """Tests for code in cookies.py.
  4. """
  5. from __future__ import unicode_literals
  6. import re
  7. import sys
  8. import logging
  9. if sys.version_info < (3, 0, 0):
  10. from urllib import quote, unquote
  11. else:
  12. from urllib.parse import quote, unquote
  13. unichr = chr
  14. basestring = str
  15. from datetime import datetime, tzinfo, timedelta
  16. from pytest import raises
  17. from cookies import (
  18. InvalidCookieError, InvalidCookieAttributeError,
  19. Definitions,
  20. Cookie, Cookies,
  21. render_date, parse_date,
  22. parse_string, parse_value, parse_domain, parse_path,
  23. parse_one_response,
  24. encode_cookie_value, encode_extension_av,
  25. valid_value, valid_date, valid_domain, valid_path,
  26. strip_spaces_and_quotes, _total_seconds,
  27. )
  28. class RFC1034:
  29. """Definitions from RFC 1034: 'DOMAIN NAMES - CONCEPTS AND FACILITIES'
  30. section 3.5, as cited in RFC 6265 4.1.1.
  31. """
  32. digit = "[0-9]"
  33. letter = "[A-Za-z]"
  34. let_dig = "[0-9A-Za-z]"
  35. let_dig_hyp = "[0-9A-Za-z\-]"
  36. assert "\\" in let_dig_hyp
  37. ldh_str = "%s+" % let_dig_hyp
  38. label = "(?:%s|%s|%s)" % (
  39. letter,
  40. letter + let_dig,
  41. letter + ldh_str + let_dig)
  42. subdomain = "(?:%s\.)*(?:%s)" % (label, label)
  43. domain = "( |%s)" % (subdomain)
  44. def test_sanity(self):
  45. "Basic smoke tests that definitions transcribed OK"
  46. match = re.compile("^%s\Z" % self.domain).match
  47. assert match("A.ISI.EDU")
  48. assert match("XX.LCS.MIT.EDU")
  49. assert match("SRI-NIC.ARPA")
  50. assert not match("foo+bar")
  51. assert match("foo.com")
  52. assert match("foo9.com")
  53. assert not match("9foo.com")
  54. assert not match("26.0.0.73.COM")
  55. assert not match(".woo.com")
  56. assert not match("blop.foo.")
  57. assert match("foo-bar.com")
  58. assert not match("-foo.com")
  59. assert not match("foo.com-")
  60. class RFC1123:
  61. """Definitions from RFC 1123: "Requirements for Internet Hosts --
  62. Application and Support" section 2.1, cited in RFC 6265 section
  63. 4.1.1 as an update to RFC 1034.
  64. Here this is really just used for testing Domain attribute values.
  65. """
  66. # Changed per 2.1 (similar to some changes in RFC 1101)
  67. # this implementation is a bit simpler...
  68. # n.b.: there are length limits in the real thing
  69. label = "{let_dig}(?:(?:{let_dig_hyp}+)?{let_dig})?".format(
  70. let_dig=RFC1034.let_dig, let_dig_hyp=RFC1034.let_dig_hyp)
  71. subdomain = "(?:%s\.)*(?:%s)" % (label, label)
  72. domain = "( |%s)" % (subdomain)
  73. def test_sanity(self):
  74. "Basic smoke tests that definitions transcribed OK"
  75. match = re.compile("^%s\Z" % self.domain).match
  76. assert match("A.ISI.EDU")
  77. assert match("XX.LCS.MIT.EDU")
  78. assert match("SRI-NIC.ARPA")
  79. assert not match("foo+bar")
  80. assert match("foo.com")
  81. assert match("9foo.com")
  82. assert match("3Com.COM")
  83. assert match("3M.COM")
  84. class RFC2616:
  85. """Definitions from RFC 2616 section 2.2, as cited in RFC 6265 4.1.1
  86. """
  87. SEPARATORS = '()<>@,;:\\"/[]?={} \t'
  88. class RFC5234:
  89. """Basic definitions per RFC 5234: 'Augmented BNF for Syntax
  90. Specifications'
  91. """
  92. CHAR = "".join([chr(i) for i in range(0, 127 + 1)])
  93. CTL = "".join([chr(i) for i in range(0, 31 + 1)]) + "\x7f"
  94. # this isn't in the RFC but it can be handy
  95. NONCTL = "".join([chr(i) for i in range(32, 127)])
  96. # this is what the RFC says about a token more or less verbatim
  97. TOKEN = "".join(sorted(set(NONCTL) - set(RFC2616.SEPARATORS)))
  98. class FixedOffsetTz(tzinfo):
  99. """A tzinfo subclass for attaching to datetime objects.
  100. Used for various tests involving date parsing, since Python stdlib does not
  101. obviously provide tzinfo subclasses and testing this module only requires
  102. a very simple one.
  103. """
  104. def __init__(self, offset):
  105. # tzinfo.utcoffset() throws an error for sub-minute amounts,
  106. # so round
  107. minutes = round(offset / 60.0, 0)
  108. self.__offset = timedelta(minutes=minutes)
  109. def utcoffset(self, dt):
  110. return self.__offset
  111. def tzname(self, dt):
  112. return "FixedOffsetTz" + str(self.__offset.seconds)
  113. def dst(self, dt):
  114. return timedelta(0)
  115. class TestInvalidCookieError(object):
  116. """Exercise the trivial behavior of the InvalidCookieError exception.
  117. """
  118. def test_simple(self):
  119. "This be the test"
  120. def exception(data):
  121. "Gather an InvalidCookieError exception"
  122. try:
  123. raise InvalidCookieError(data)
  124. except InvalidCookieError as exception:
  125. return exception
  126. # other exceptions will pass through
  127. return None
  128. assert exception("no donut").data == "no donut"
  129. # Spot check for obvious junk in loggable representations.
  130. e = exception("yay\x00whee")
  131. assert "\x00" not in repr(e)
  132. assert "\x00" not in str(e)
  133. assert "yaywhee" not in repr(e)
  134. assert "yaywhee" not in str(e)
  135. assert "\n" not in repr(exception("foo\nbar"))
  136. class TestInvalidCookieAttributeError(object):
  137. """Exercise the trivial behavior of InvalidCookieAttributeError.
  138. """
  139. def exception(self, *args, **kwargs):
  140. "Generate an InvalidCookieAttributeError exception naturally"
  141. try:
  142. raise InvalidCookieAttributeError(*args, **kwargs)
  143. except InvalidCookieAttributeError as exception:
  144. return exception
  145. return None
  146. def test_simple(self):
  147. e = self.exception("foo", "bar")
  148. assert e.name == "foo"
  149. assert e.value == "bar"
  150. def test_junk_in_loggables(self):
  151. # Spot check for obvious junk in loggable representations.
  152. # This isn't completely idle: for example, nulls are ignored in
  153. # %-formatted text, and this could be very misleading
  154. e = self.exception("ya\x00y", "whee")
  155. assert "\x00" not in repr(e)
  156. assert "\x00" not in str(e)
  157. assert "yay" not in repr(e)
  158. assert "yay" not in str(e)
  159. e = self.exception("whee", "ya\x00y")
  160. assert "\x00" not in repr(e)
  161. assert "\x00" not in str(e)
  162. assert "yay" not in repr(e)
  163. assert "yay" not in str(e)
  164. assert "\n" not in repr(self.exception("yay", "foo\nbar"))
  165. assert "\n" not in repr(self.exception("foo\nbar", "yay"))
  166. def test_no_name(self):
  167. # not recommended to do this, but we want to handle it if people do
  168. e = self.exception(None, "stuff")
  169. assert e.name == None
  170. assert e.value == "stuff"
  171. assert e.reason == None
  172. assert 'stuff' in str(e)
  173. class TestDefinitions(object):
  174. """Test the patterns in cookies.Definitions against specs.
  175. """
  176. def test_cookie_name(self, check_unicode=False):
  177. """Check COOKIE_NAME against the token definition in RFC 2616 2.2 (as
  178. cited in RFC 6265):
  179. token = 1*<any CHAR except CTLs or separators>
  180. separators = "(" | ")" | "<" | ">" | "@"
  181. | "," | ";" | ":" | "\" | <">
  182. | "/" | "[" | "]" | "?" | "="
  183. | "{" | "}" | SP | HT
  184. (Definitions.COOKIE_NAME is regex-ready while RFC5234.TOKEN is more
  185. clearly related to the RFC; they should be functionally the same)
  186. """
  187. regex = Definitions.COOKIE_NAME_RE
  188. assert regex.match(RFC5234.TOKEN)
  189. assert not regex.match(RFC5234.NONCTL)
  190. for c in RFC5234.CTL:
  191. assert not regex.match(c)
  192. for c in RFC2616.SEPARATORS:
  193. # Skip special case - some number of Java and PHP apps have used
  194. # colon in names, while this is dumb we want to not choke on this
  195. # by default since it may be the single biggest cause of bugs filed
  196. # against Python's cookie libraries
  197. if c == ':':
  198. continue
  199. assert not regex.match(c)
  200. # Unicode over 7 bit ASCII shouldn't match, but this takes a while
  201. if check_unicode:
  202. for i in range(127, 0x10FFFF + 1):
  203. assert not regex.match(unichr(i))
  204. def test_cookie_octet(self):
  205. """Check COOKIE_OCTET against the definition in RFC 6265:
  206. cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E
  207. ; US-ASCII characters excluding CTLs,
  208. ; whitespace DQUOTE, comma, semicolon,
  209. ; and backslash
  210. """
  211. match = re.compile("^[%s]+\Z" % Definitions.COOKIE_OCTET).match
  212. for c in RFC5234.CTL:
  213. assert not match(c)
  214. assert not match("a%sb" % c)
  215. # suspect RFC typoed 'whitespace, DQUOTE' as 'whitespace DQUOTE'
  216. assert not match(' ')
  217. assert not match('"')
  218. assert not match(',')
  219. assert not match(';')
  220. assert not match('\\')
  221. # the spec above DOES include =.-
  222. assert match("=")
  223. assert match(".")
  224. assert match("-")
  225. # Check that everything else in CHAR works.
  226. safe_cookie_octet = "".join(sorted(
  227. set(RFC5234.NONCTL) - set(' ",;\\')))
  228. assert match(safe_cookie_octet)
  229. def test_set_cookie_header(self):
  230. """Smoke test SET_COOKIE_HEADER (used to compile SET_COOKIE_HEADER_RE)
  231. against HEADER_CASES.
  232. """
  233. # should match if expectation is not an error, shouldn't match if it is
  234. # an error. set-cookie-header is for responses not requests, so use
  235. # response expectation rather than request expectation
  236. match = re.compile(Definitions.SET_COOKIE_HEADER).match
  237. for case in HEADER_CASES:
  238. arg, kwargs, request_result, expected = case
  239. this_match = match(arg)
  240. if expected and not isinstance(expected, type):
  241. assert this_match, "should match as response: " + repr(arg)
  242. else:
  243. if not request_result:
  244. assert not this_match, \
  245. "should not match as response: " + repr(arg)
  246. def test_cookie_cases(self):
  247. """Smoke test COOKIE_HEADER (used to compile COOKIE_HEADER_RE) against
  248. HEADER_CASES.
  249. """
  250. # should match if expectation is not an error, shouldn't match if it is
  251. # an error. cookie-header is for requests not responses, so use request
  252. # expectation rather than response expectation
  253. match = re.compile(Definitions.COOKIE).match
  254. for case in HEADER_CASES:
  255. arg, kwargs, expected, response_result = case
  256. this_match = match(arg)
  257. if expected and not isinstance(expected, type):
  258. assert this_match, "should match as request: " + repr(arg)
  259. else:
  260. if not response_result:
  261. assert not this_match, \
  262. "should not match as request: " + repr(arg)
  263. def test_cookie_pattern(self):
  264. """Smoke test Definitions.COOKIE (used to compile COOKIE_RE) against
  265. the grammar for cookie-header as in RFC 6265.
  266. cookie-header = "Cookie:" OWS cookie-string OWS
  267. cookie-string = cookie-pair *( ";" SP cookie-pair )
  268. cookie-pair = cookie-name "=" cookie-value
  269. cookie-name = token
  270. cookie-value = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE )
  271. cookie-name and cookie-value are not broken apart for separate
  272. testing, as the former is essentially just token and the latter
  273. essentially just cookie-octet.
  274. """
  275. match = re.compile(Definitions.COOKIE).match
  276. # cookie-pair behavior around =
  277. assert match("foo").group('invalid')
  278. assert match("foo=bar")
  279. # Looks dumb, but this is legal because "=" is valid for cookie-octet.
  280. assert match("a=b=c")
  281. # DQUOTE *cookie-octet DQUOTE - allowed
  282. assert match('foo="bar"')
  283. # for testing on the contents of cookie name and cookie value,
  284. # see test_cookie_name and test_cookie_octet.
  285. regex = re.compile(Definitions.COOKIE)
  286. correct = [
  287. ('foo', 'yar', ''),
  288. ('bar', 'eeg', ''),
  289. ('baz', 'wog', ''),
  290. ('frob', 'laz', '')]
  291. def assert_correct(s):
  292. #naive = re.findall(" *([^;]+)=([^;]+) *(?:;|\Z)", s)
  293. result = regex.findall(s)
  294. assert result == correct
  295. # normal-looking case should work normally
  296. assert_correct("foo=yar; bar=eeg; baz=wog; frob=laz")
  297. # forgive lack of whitespace as long as semicolons are explicit
  298. assert_correct("foo=yar;bar=eeg;baz=wog;frob=laz")
  299. # forgive too much whitespace AROUND values
  300. assert_correct(" foo=yar; bar=eeg; baz=wog; frob=laz ")
  301. # Actually literal spaces are NOT allowed in cookie values per RFC 6265
  302. # and it is UNWISE to put them in without escaping. But we want the
  303. # flexibility to let this pass with a warning, because this is the kind
  304. # of bad idea which is very common and results in loud complaining on
  305. # issue trackers on the grounds that PHP does it or something. So the
  306. # regex is weakened, but the presence of a space should still be at
  307. # least noted, and an exception must be raised if = is also used
  308. # - because that would often indicate the loss of cookies due to
  309. # forgotten separator, as in "foo=yar bar=eeg baz=wog frob=laz".
  310. assert regex.findall("foo=yar; bar=eeg; baz=wog; frob=l az") == [
  311. ('foo', 'yar', ''),
  312. ('bar', 'eeg', ''),
  313. ('baz', 'wog', ''),
  314. # handle invalid internal whitespace.
  315. ('frob', 'l az', '')
  316. ]
  317. # Without semicolons or inside semicolon-delimited blocks, the part
  318. # before the first = should be interpreted as a name, and the rest as
  319. # a value (since = is not forbidden for cookie values). Thus:
  320. result = regex.findall("foo=yarbar=eegbaz=wogfrob=laz")
  321. assert result[0][0] == 'foo'
  322. assert result[0][1] == 'yarbar=eegbaz=wogfrob=laz'
  323. assert result[0][2] == ''
  324. # Make some bad values and see that it's handled reasonably.
  325. # (related to http://bugs.python.org/issue2988)
  326. # don't test on semicolon because the regexp stops there, reasonably.
  327. for c in '\x00",\\':
  328. nasty = "foo=yar" + c + "bar"
  329. result = regex.findall(nasty + "; baz=bam")
  330. # whole bad pair reported in the 'invalid' group (the third one)
  331. assert result[0][2] == nasty
  332. # kept on truckin' and got the other one just fine.
  333. assert result[1] == ('baz', 'bam', '')
  334. # same thing if the good one is first and the bad one second
  335. result = regex.findall("baz=bam; " + nasty)
  336. assert result[0] == ('baz', 'bam', '')
  337. assert result[1][2] == ' ' + nasty
  338. def test_extension_av(self, check_unicode=False):
  339. """Test Definitions.EXTENSION_AV against extension-av per RFC 6265.
  340. extension-av = <any CHAR except CTLs or ";">
  341. """
  342. # This is how it's defined in RFC 6265, just about verbatim.
  343. extension_av_explicit = "".join(sorted(
  344. set(RFC5234.CHAR) - set(RFC5234.CTL + ";")))
  345. # ... that should turn out to be the same as Definitions.EXTENSION_AV
  346. match = re.compile("^([%s]+)\Z" % Definitions.EXTENSION_AV).match
  347. # Verify I didn't mess up on escaping here first
  348. assert match(r']')
  349. assert match(r'[')
  350. assert match(r"'")
  351. assert match(r'"')
  352. assert match("\\")
  353. assert match(extension_av_explicit)
  354. # There should be some CHAR not matched
  355. assert not match(RFC5234.CHAR)
  356. # Every single CTL should not match
  357. for c in RFC5234.CTL + ";":
  358. assert not match(c)
  359. # Unicode over 7 bit ASCII shouldn't match, but this takes a while
  360. if check_unicode:
  361. for i in range(127, 0x10FFFF + 1):
  362. assert not match(unichr(i))
  363. def test_max_age_av(self):
  364. "Smoke test Definitions.MAX_AGE_AV"
  365. # Not a lot to this, it's just digits
  366. match = re.compile("^%s\Z" % Definitions.MAX_AGE_AV).match
  367. assert not match("")
  368. assert not match("Whiskers")
  369. assert not match("Max-Headroom=992")
  370. for c in "123456789":
  371. assert not match(c)
  372. assert match("Max-Age=%s" % c)
  373. assert match("Max-Age=0")
  374. for c in RFC5234.CHAR:
  375. assert not match(c)
  376. def test_label(self, check_unicode=False):
  377. "Test label, as used in Domain attribute"
  378. match = re.compile("^(%s)\Z" % Definitions.LABEL).match
  379. for i in range(0, 10):
  380. assert match(str(i))
  381. assert not match(".")
  382. assert not match(",")
  383. for c in RFC5234.CTL:
  384. assert not match("a%sb" % c)
  385. assert not match("%sb" % c)
  386. assert not match("a%s" % c)
  387. # Unicode over 7 bit ASCII shouldn't match, but this takes a while
  388. if check_unicode:
  389. for i in range(127, 0x10FFFF + 1):
  390. assert not match(unichr(i))
  391. def test_domain_av(self):
  392. "Smoke test Definitions.DOMAIN_AV"
  393. # This is basically just RFC1123.subdomain, which has its own
  394. # assertions in the class definition
  395. bad_domains = [
  396. ""
  397. ]
  398. good_domains = [
  399. "foobar.com",
  400. "foo-bar.com",
  401. "3Com.COM"
  402. ]
  403. # First test DOMAIN via DOMAIN_RE
  404. match = Definitions.DOMAIN_RE.match
  405. for domain in bad_domains:
  406. assert not match(domain)
  407. for domain in good_domains:
  408. assert match(domain)
  409. # Now same tests through DOMAIN_AV
  410. match = re.compile("^%s\Z" % Definitions.DOMAIN_AV).match
  411. for domain in bad_domains:
  412. assert not match("Domain=%s" % domain)
  413. for domain in good_domains:
  414. assert not match(domain)
  415. assert match("Domain=%s" % domain)
  416. # This is NOT valid and shouldn't be tolerated in cookies we create,
  417. # but it should be tolerated in existing cookies since people do it;
  418. # interpreted by stripping the initial .
  419. assert match("Domain=.foo.net")
  420. def test_path_av(self):
  421. "Smoke test PATH and PATH_AV"
  422. # This is basically just EXTENSION_AV, see test_extension_av
  423. bad_paths = [
  424. ""
  425. ]
  426. good_paths = [
  427. "/",
  428. "/foo",
  429. "/foo/bar"
  430. ]
  431. match = Definitions.PATH_RE.match
  432. for path in bad_paths:
  433. assert not match(path)
  434. for path in good_paths:
  435. assert match(path)
  436. match = re.compile("^%s\Z" % Definitions.PATH_AV).match
  437. for path in bad_paths:
  438. assert not match("Path=%s" % path)
  439. for path in good_paths:
  440. assert not match(path)
  441. assert match("Path=%s" % path)
  442. def test_months(self):
  443. """Sanity checks on MONTH_SHORT and MONTH_LONG month name recognizers.
  444. The RFCs set these in stone, they aren't locale-dependent.
  445. """
  446. match = re.compile(Definitions.MONTH_SHORT).match
  447. assert match("Jan")
  448. assert match("Feb")
  449. assert match("Mar")
  450. assert match("Apr")
  451. assert match("May")
  452. assert match("Jun")
  453. assert match("Jul")
  454. assert match("Aug")
  455. assert match("Sep")
  456. assert match("Oct")
  457. assert match("Nov")
  458. assert match("Dec")
  459. match = re.compile(Definitions.MONTH_LONG).match
  460. assert match("January")
  461. assert match("February")
  462. assert match("March")
  463. assert match("April")
  464. assert match("May")
  465. assert match("June")
  466. assert match("July")
  467. assert match("August")
  468. assert match("September")
  469. assert match("October")
  470. assert match("November")
  471. assert match("December")
  472. def test_weekdays(self):
  473. """Sanity check on WEEKDAY_SHORT and WEEKDAY_LONG weekday
  474. recognizers.
  475. The RFCs set these in stone, they aren't locale-dependent.
  476. """
  477. match = re.compile(Definitions.WEEKDAY_SHORT).match
  478. assert match("Mon")
  479. assert match("Tue")
  480. assert match("Wed")
  481. assert match("Thu")
  482. assert match("Fri")
  483. assert match("Sat")
  484. assert match("Sun")
  485. match = re.compile(Definitions.WEEKDAY_LONG).match
  486. assert match("Monday")
  487. assert match("Tuesday")
  488. assert match("Wednesday")
  489. assert match("Thursday")
  490. assert match("Friday")
  491. assert match("Saturday")
  492. assert match("Sunday")
  493. def test_day_of_month(self):
  494. """Check that the DAY_OF_MONTH regex allows all actual days, but
  495. excludes obviously wrong ones (so they are tossed in the first pass).
  496. """
  497. match = re.compile(Definitions.DAY_OF_MONTH).match
  498. for day in ['01', '02', '03', '04', '05', '06', '07', '08', '09', ' 1',
  499. ' 2', ' 3', ' 4', ' 5', ' 6', ' 7', ' 8', ' 9', '1', '2', '3',
  500. '4', '5', '6', '7', '8', '9'] \
  501. + [str(i) for i in range(10, 32)]:
  502. assert match(day)
  503. assert not match("0")
  504. assert not match("00")
  505. assert not match("000")
  506. assert not match("111")
  507. assert not match("99")
  508. assert not match("41")
  509. def test_expires_av(self):
  510. "Smoke test the EXPIRES_AV regex pattern"
  511. # Definitions.EXPIRES_AV is actually pretty bad because it's a disaster
  512. # to test three different date formats with lots of definition
  513. # dependencies, and odds are good that other implementations are loose.
  514. # so this parser is also loose. "liberal in what you accept,
  515. # conservative in what you produce"
  516. match = re.compile("^%s\Z" % Definitions.EXPIRES_AV).match
  517. assert not match("")
  518. assert not match("Expires=")
  519. assert match("Expires=Tue, 15-Jan-2013 21:47:38 GMT")
  520. assert match("Expires=Sun, 06 Nov 1994 08:49:37 GMT")
  521. assert match("Expires=Sunday, 06-Nov-94 08:49:37 GMT")
  522. assert match("Expires=Sun Nov 6 08:49:37 1994")
  523. # attributed to Netscape in RFC 2109 10.1.2
  524. assert match("Expires=Mon, 13-Jun-93 10:00:00 GMT")
  525. assert not match("Expires=S9n, 06 Nov 1994 08:49:37 GMT")
  526. assert not match("Expires=Sun3ay, 06-Nov-94 08:49:37 GMT")
  527. assert not match("Expires=S9n Nov 6 08:49:37 1994")
  528. assert not match("Expires=Sun, A6 Nov 1994 08:49:37 GMT")
  529. assert not match("Expires=Sunday, 0B-Nov-94 08:49:37 GMT")
  530. assert not match("Expires=Sun No8 6 08:49:37 1994")
  531. assert not match("Expires=Sun, 06 N3v 1994 08:49:37 GMT")
  532. assert not match("Expires=Sunday, 06-N8v-94 08:49:37 GMT")
  533. assert not match("Expires=Sun Nov A 08:49:37 1994")
  534. assert not match("Expires=Sun, 06 Nov 1B94 08:49:37 GMT")
  535. assert not match("Expires=Sunday, 06-Nov-C4 08:49:37 GMT")
  536. assert not match("Expires=Sun Nov 6 08:49:37 1Z94")
  537. def test_no_obvious_need_for_disjunctive_attr_pattern(self):
  538. """Smoke test the assumption that extension-av is a reasonable set of
  539. chars for all attrs (and thus that there is no reason to use a fancy
  540. disjunctive pattern in the findall that splits out the attrs, freeing
  541. us to use EXTENSION_AV instead).
  542. If this works, then ATTR should work
  543. """
  544. match = re.compile("^[%s]+\Z" % Definitions.EXTENSION_AV).match
  545. assert match("Expires=Sun, 06 Nov 1994 08:49:37 GMT")
  546. assert match("Expires=Sunday, 06-Nov-94 08:49:37 GMT")
  547. assert match("Expires=Sun Nov 6 08:49:37 1994")
  548. assert match("Max-Age=14658240962")
  549. assert match("Domain=FoO.b9ar.baz")
  550. assert match("Path=/flakes")
  551. assert match("Secure")
  552. assert match("HttpOnly")
  553. def test_attr(self):
  554. """Smoke test ATTR, used to compile ATTR_RE.
  555. """
  556. match = re.compile(Definitions.ATTR).match
  557. def recognized(pattern):
  558. "macro for seeing if ATTR recognized something"
  559. this_match = match(pattern)
  560. if not this_match:
  561. return False
  562. groupdict = this_match.groupdict()
  563. if groupdict['unrecognized']:
  564. return False
  565. return True
  566. # Quickly test that a batch of attributes matching the explicitly
  567. # recognized patterns make it through without anything in the
  568. # 'unrecognized' catchall capture group.
  569. for pattern in [
  570. "Secure",
  571. "HttpOnly",
  572. "Max-Age=9523052",
  573. "Domain=frobble.com",
  574. "Domain=3Com.COM",
  575. "Path=/",
  576. "Expires=Wed, 09 Jun 2021 10:18:14 GMT",
  577. ]:
  578. assert recognized(pattern)
  579. # Anything else is in extension-av and that's very broad;
  580. # see test_extension_av for that test.
  581. # This is only about the recognized ones.
  582. assert not recognized("Frob=mugmannary")
  583. assert not recognized("Fqjewp@1j5j510923")
  584. assert not recognized(";aqjwe")
  585. assert not recognized("ETJpqw;fjw")
  586. assert not recognized("fjq;")
  587. assert not recognized("Expires=\x00")
  588. # Verify interface from regexp for extracting values isn't changed;
  589. # a little rigidity here is a good idea
  590. expires = "Wed, 09 Jun 2021 10:18:14 GMT"
  591. m = match("Expires=%s" % expires)
  592. assert m.group("expires") == expires
  593. max_age = "233951698"
  594. m = match("Max-Age=%s" % max_age)
  595. assert m.group("max_age") == max_age
  596. domain = "flarp"
  597. m = match("Domain=%s" % domain)
  598. assert m.group("domain") == domain
  599. path = "2903"
  600. m = match("Path=%s" % path)
  601. assert m.group("path") == path
  602. m = match("Secure")
  603. assert m.group("secure")
  604. assert not m.group("httponly")
  605. m = match("HttpOnly")
  606. assert not m.group("secure")
  607. assert m.group("httponly")
  608. def test_date_accepts_formats(self):
  609. """Check that DATE matches most formats used in Expires: headers,
  610. and explain what the different formats are about.
  611. The value extraction of this regexp is more comprehensively exercised
  612. by test_date_parsing().
  613. """
  614. # Date formats vary widely in the wild. Even the standards vary widely.
  615. # This series of tests does spot-checks with instances of formats that
  616. # it makes sense to support. In the following comments, each format is
  617. # discussed and the rationale for the overall regexp is developed.
  618. match = re.compile(Definitions.DATE).match
  619. # The most common formats, related to the old Netscape cookie spec
  620. # (NCSP), are supposed to follow this template:
  621. #
  622. # Wdy, DD-Mon-YYYY HH:MM:SS GMT
  623. #
  624. # (where 'Wdy' is a short weekday, and 'Mon' is a named month).
  625. assert match("Mon, 20-Jan-1994 00:00:00 GMT")
  626. # Similarly, RFC 850 proposes this format:
  627. #
  628. # Weekday, DD-Mon-YY HH:MM:SS GMT
  629. #
  630. # (with a long-form weekday and a 2-digit year).
  631. assert match("Tuesday, 12-Feb-92 23:25:42 GMT")
  632. # RFC 1036 obsoleted the RFC 850 format:
  633. #
  634. # Wdy, DD Mon YY HH:MM:SS GMT
  635. #
  636. # (shortening the weekday format and changing dashes to spaces).
  637. assert match("Wed, 30 Mar 92 13:16:12 GMT")
  638. # RFC 6265 cites a definition from RFC 2616, which uses the RFC 1123
  639. # definition but limits it to GMT (consonant with NCSP). RFC 1123
  640. # expanded RFC 822 with 2-4 digit years (more permissive than NCSP);
  641. # RFC 822 left weekday and seconds as optional, and a day of 1-2 digits
  642. # (all more permissive than NCSP). Giving something like this:
  643. #
  644. # [Wdy, ][D]D Mon [YY]YY HH:MM[:SS] GMT
  645. #
  646. assert match("Thu, 3 Apr 91 12:46 GMT")
  647. # No weekday, two digit year.
  648. assert match("13 Apr 91 12:46 GMT")
  649. # Similarly, there is RFC 2822:
  650. #
  651. # [Wdy, ][D]D Mon YYYY HH:MM[:SS] GMT
  652. # (which only differs in requiring a 4-digit year, where RFC 1123
  653. # permits 2 or 3 digit years).
  654. assert match("13 Apr 1991 12:46 GMT")
  655. assert match("Wed, 13 Apr 1991 12:46 GMT")
  656. # The generalized format given above encompasses RFC 1036 and RFC 2822
  657. # and would encompass NCSP except for the dashes; allowing long-form
  658. # weekdays also encompasses the format proposed in RFC 850. Taken
  659. # together, this should cover something like 99% of Expires values
  660. # (see, e.g., https://bugzilla.mozilla.org/show_bug.cgi?id=610218)
  661. # Finally, we also want to support asctime format, as mentioned in RFC
  662. # 850 and RFC 2616 and occasionally seen in the wild:
  663. # Wdy Mon DD HH:MM:SS YYYY
  664. # e.g.: Sun Nov 6 08:49:37 1994
  665. assert match("Sun Nov 6 08:49:37 1994")
  666. assert match("Sun Nov 26 08:49:37 1994")
  667. # Reportedly someone has tacked 'GMT' on to the end of an asctime -
  668. # although this is not RFC valid, it is pretty harmless
  669. assert match("Sun Nov 26 08:49:37 1994 GMT")
  670. # This test is not passed until it is shown that it wasn't trivially
  671. # because DATE was matching .* or similar. This isn't intended to be
  672. # a thorough test, just rule out the obvious reason. See test_date()
  673. # for a more thorough workout of the whole parse and render mechanisms
  674. assert not match("")
  675. assert not match(" ")
  676. assert not match("wobbly")
  677. assert not match("Mon")
  678. assert not match("Mon, 20")
  679. assert not match("Mon, 20 Jan")
  680. assert not match("Mon, 20,Jan,1994 00:00:00 GMT")
  681. assert not match("Tuesday, 12-Feb-992 23:25:42 GMT")
  682. assert not match("Wed, 30 Mar 92 13:16:1210 GMT")
  683. assert not match("Wed, 30 Mar 92 13:16:12:10 GMT")
  684. assert not match("Thu, 3 Apr 91 12:461 GMT")
  685. def test_eol(self):
  686. """Test that the simple EOL regex works basically as expected.
  687. """
  688. split = Definitions.EOL.split
  689. assert split("foo\nbar") == ["foo", "bar"]
  690. assert split("foo\r\nbar") == ["foo", "bar"]
  691. letters = list("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
  692. assert split("\n".join(letters)) == letters
  693. assert split("\r\n".join(letters)) == letters
  694. def test_compiled(self):
  695. """Check that certain patterns are present as compiled regexps
  696. """
  697. re_type = type(re.compile(''))
  698. def present(name):
  699. "Macro for testing existence of an re in Definitions"
  700. item = getattr(Definitions, name)
  701. return item and isinstance(item, re_type)
  702. assert present("COOKIE_NAME_RE")
  703. assert present("COOKIE_RE")
  704. assert present("SET_COOKIE_HEADER_RE")
  705. assert present("ATTR_RE")
  706. assert present("DATE_RE")
  707. assert present("EOL")
  708. def _test_init(cls, args, kwargs, expected):
  709. "Core instance test function for test_init"
  710. print("test_init", cls, args, kwargs)
  711. try:
  712. instance = cls(*args, **kwargs)
  713. except Exception as exception:
  714. if type(exception) == expected:
  715. return
  716. logging.error("expected %s, got %s", expected, repr(exception))
  717. raise
  718. if isinstance(expected, type) and issubclass(expected, Exception):
  719. raise AssertionError("No exception raised; "
  720. "expected %s for %s/%s" % (
  721. expected.__name__,
  722. repr(args),
  723. repr(kwargs)))
  724. for attr_name, attr_value in expected.items():
  725. assert getattr(instance, attr_name) == attr_value
  726. class TestCookie(object):
  727. """Tests for the Cookie class.
  728. """
  729. # Test cases exercising different constructor calls to make a new Cookie
  730. # from scratch. Each case is tuple:
  731. # args, kwargs, exception or dict of expected attribute values
  732. # this exercises the default validators as well.
  733. creation_cases = [
  734. # bad call gives TypeError
  735. (("foo",), {}, TypeError),
  736. (("a", "b", "c"), {}, TypeError),
  737. # give un-ascii-able name - raises error due to likely
  738. # compatibility problems (cookie ignored, etc.)
  739. # in value it's fine, it'll be encoded and not inspected anyway.
  740. (("ăŊĻ", "b"), {}, InvalidCookieError),
  741. (("b", "ăŊĻ"), {}, {'name': 'b', 'value': "ăŊĻ"}),
  742. # normal simple construction gives name and value
  743. (("foo", "bar"), {}, {'name': 'foo', 'value': 'bar'}),
  744. # add a valid attribute and get it set
  745. (("baz", "bam"), {'max_age': 9},
  746. {'name': 'baz', 'value': 'bam', 'max_age': 9}),
  747. # multiple valid attributes
  748. (("x", "y"), {'max_age': 9, 'comment': 'fruity'},
  749. {'name': 'x', 'value': 'y',
  750. 'max_age': 9, 'comment': 'fruity'}),
  751. # invalid max-age
  752. (("w", "m"), {'max_age': 'loopy'}, InvalidCookieAttributeError),
  753. (("w", "m"), {'max_age': -1}, InvalidCookieAttributeError),
  754. (("w", "m"), {'max_age': 1.2}, InvalidCookieAttributeError),
  755. # invalid expires
  756. (("w", "m"), {'expires': 0}, InvalidCookieAttributeError),
  757. (("w", "m"), {'expires':
  758. datetime(2010, 1, 1, tzinfo=FixedOffsetTz(600))},
  759. InvalidCookieAttributeError),
  760. # control: valid expires
  761. (("w", "m"),
  762. {'expires': datetime(2010, 1, 1)},
  763. {'expires': datetime(2010, 1, 1)}),
  764. # invalid domain
  765. (("w", "m"), {'domain': ''}, InvalidCookieAttributeError),
  766. (("w", "m"), {'domain': '@'}, InvalidCookieAttributeError),
  767. (("w", "m"), {'domain': '.foo.net'}, {'domain': '.foo.net'}),
  768. # control: valid domain
  769. (("w", "m"),
  770. {'domain': 'foo.net'},
  771. {'domain': 'foo.net'},),
  772. # invalid path
  773. (("w", "m"), {'path': ''}, InvalidCookieAttributeError),
  774. (("w", "m"), {'path': '""'}, InvalidCookieAttributeError),
  775. (("w", "m"), {'path': 'foo'}, InvalidCookieAttributeError),
  776. (("w", "m"), {'path': '"/foo"'}, InvalidCookieAttributeError),
  777. (("w", "m"), {'path': ' /foo '}, InvalidCookieAttributeError),
  778. # control: valid path
  779. (("w", "m"), {'path': '/'},
  780. {'path': '/'}),
  781. (("w", "m"), {'path': '/axes'},
  782. {'path': '/axes'}),
  783. # invalid version per RFC 2109/RFC 2965
  784. (("w", "m"), {'version': ''}, InvalidCookieAttributeError),
  785. (("w", "m"), {'version': 'baa'}, InvalidCookieAttributeError),
  786. (("w", "m"), {'version': -2}, InvalidCookieAttributeError),
  787. (("w", "m"), {'version': 2.3}, InvalidCookieAttributeError),
  788. # control: valid version
  789. (("w", "m"), {'version': 0}, {'version': 0}),
  790. (("w", "m"), {'version': 1}, {'version': 1}),
  791. (("w", "m"), {'version': 3042}, {'version': 3042}),
  792. # invalid secure, httponly
  793. (("w", "m"), {'secure': ''}, InvalidCookieAttributeError),
  794. (("w", "m"), {'secure': 0}, InvalidCookieAttributeError),
  795. (("w", "m"), {'secure': 1}, InvalidCookieAttributeError),
  796. (("w", "m"), {'secure': 'a'}, InvalidCookieAttributeError),
  797. (("w", "m"), {'httponly': ''}, InvalidCookieAttributeError),
  798. (("w", "m"), {'httponly': 0}, InvalidCookieAttributeError),
  799. (("w", "m"), {'httponly': 1}, InvalidCookieAttributeError),
  800. (("w", "m"), {'httponly': 'a'}, InvalidCookieAttributeError),
  801. # valid comment
  802. (("w", "m"), {'comment': 'a'}, {'comment': 'a'}),
  803. # invalid names
  804. # (unicode cases are done last because they mess with pytest print)
  805. ((None, "m"), {}, InvalidCookieError),
  806. (("", "m"), {}, InvalidCookieError),
  807. (("ü", "m"), {}, InvalidCookieError),
  808. # invalid values
  809. (("w", None), {}, {'name': 'w'}),
  810. # a control - unicode is valid value, just gets encoded on way out
  811. (("w", "üm"), {}, {'value': "üm"}),
  812. # comma
  813. (('a', ','), {}, {'value': ','}),
  814. # semicolons
  815. (('a', ';'), {}, {'value': ';'}),
  816. # spaces
  817. (('a', ' '), {}, {'value': ' '}),
  818. ]
  819. def test_init(self):
  820. """Exercise __init__ and validators.
  821. This is important both because it is a user-facing API, and also
  822. because the parse/render tests depend heavily on it.
  823. """
  824. creation_cases = self.creation_cases + [
  825. (("a", "b"), {'frob': 10}, InvalidCookieAttributeError)
  826. ]
  827. counter = 0
  828. for args, kwargs, expected in creation_cases:
  829. counter += 1
  830. logging.error("counter %d, %s, %s, %s", counter, args, kwargs,
  831. expected)
  832. _test_init(Cookie, args, kwargs, expected)
  833. def test_set_attributes(self):
  834. """Exercise setting, validation and getting of attributes without
  835. much involving __init__. Also sets value and name.
  836. """
  837. for args, kwargs, expected in self.creation_cases:
  838. if not kwargs:
  839. continue
  840. try:
  841. cookie = Cookie("yarp", "flam")
  842. for attr, value in kwargs.items():
  843. setattr(cookie, attr, value)
  844. if args:
  845. cookie.name = args[0]
  846. cookie.value = args[1]
  847. except Exception as e:
  848. if type(e) == expected:
  849. continue
  850. raise
  851. if isinstance(expected, type) and issubclass(expected, Exception):
  852. raise AssertionError("No exception raised; "
  853. "expected %s for %s" % (
  854. expected.__name__,
  855. repr(kwargs)))
  856. for attr_name, attr_value in expected.items():
  857. assert getattr(cookie, attr_name) == attr_value
  858. def test_get_defaults(self):
  859. "Test that defaults are right for cookie attrs"
  860. cookie = Cookie("foo", "bar")
  861. for attr in (
  862. "expires",
  863. "max_age",
  864. "domain",
  865. "path",
  866. "comment",
  867. "version",
  868. "secure",
  869. "httponly"):
  870. assert hasattr(cookie, attr)
  871. assert getattr(cookie, attr) == None
  872. # Verify that not every name is getting something
  873. for attr in ("foo", "bar", "baz"):
  874. assert not hasattr(cookie, attr)
  875. with raises(AttributeError):
  876. getattr(cookie, attr)
  877. names_values = [
  878. ("a", "b"),
  879. ("foo", "bar"),
  880. ("baz", "1234567890"),
  881. ("!!#po99!", "blah"),
  882. ("^_~`*", "foo"),
  883. ("%s+|-.&$", "snah"),
  884. ("lub", "!@#$%^&*()[]{}|/:'<>~.?`"),
  885. ("woah", "====+-_"),
  886. ]
  887. def test_render_response(self):
  888. "Test rendering Cookie object for Set-Cookie: header"
  889. for name, value in self.names_values:
  890. cookie = Cookie(name, value)
  891. expected = "{name}={value}".format(
  892. name=name, value=value)
  893. assert cookie.render_response() == expected
  894. for data, result in [
  895. ({'name': 'a', 'value': 'b'}, "a=b"),
  896. ({'name': 'foo', 'value': 'bar'}, "foo=bar"),
  897. ({'name': 'baz', 'value': 'bam'}, "baz=bam"),
  898. ({'name': 'baz', 'value': 'bam', 'max_age': 2},
  899. "baz=bam; Max-Age=2"),
  900. ({'name': 'baz', 'value': 'bam',
  901. 'max_age': 2, 'comment': 'foobarbaz'},
  902. "baz=bam; Max-Age=2; Comment=foobarbaz"),
  903. ({'name': 'baz', 'value': 'bam',
  904. 'max_age': 2,
  905. 'expires': datetime(1970, 1, 1),
  906. },
  907. "baz=bam; Max-Age=2; "
  908. "Expires=Thu, 01 Jan 1970 00:00:00 GMT"),
  909. ({'name': 'baz', 'value': 'bam', 'path': '/yams',
  910. 'domain': '3Com.COM'},
  911. "baz=bam; Domain=3Com.COM; Path=/yams"),
  912. ({'name': 'baz', 'value': 'bam', 'path': '/', 'secure': True,
  913. 'httponly': True},
  914. "baz=bam; Path=/; Secure; HttpOnly"),
  915. ({'name': 'baz', 'value': 'bam', 'domain': '.domain'},
  916. 'baz=bam; Domain=domain'),
  917. ]:
  918. cookie = Cookie(**data)
  919. actual = sorted(cookie.render_response().split("; "))
  920. ideal = sorted(result.split("; "))
  921. assert actual == ideal
  922. def test_render_encode(self):
  923. """Test encoding of a few special characters.
  924. as in http://bugs.python.org/issue9824
  925. """
  926. cases = {
  927. ("x", "foo,bar;baz"): 'x=foo%2Cbar%3Bbaz',
  928. ("y", 'yap"bip'): 'y=yap%22bip',
  929. }
  930. for args, ideal in cases.items():
  931. cookie = Cookie(*args)
  932. assert cookie.render_response() == ideal
  933. assert cookie.render_request() == ideal
  934. def test_legacy_quotes(self):
  935. """Check that cookies which delimit values with quotes are understood
  936. but that this non-6265 behavior is not repeated in the output
  937. """
  938. cookie = Cookie.from_string(
  939. 'Set-Cookie: y="foo"; version="1"; Path="/foo"')
  940. assert cookie.name == 'y'
  941. assert cookie.value == 'foo'
  942. assert cookie.version == 1
  943. assert cookie.path == "/foo"
  944. pieces = cookie.render_response().split("; ")
  945. assert pieces[0] == 'y=foo'
  946. assert set(pieces[1:]) == set([
  947. 'Path=/foo', 'Version=1'
  948. ])
  949. def test_render_response_expires(self):
  950. "Simple spot check of cookie expires rendering"
  951. a = Cookie('a', 'blah')
  952. a.expires = parse_date("Wed, 23-Jan-1992 00:01:02 GMT")
  953. assert a.render_response() == \
  954. 'a=blah; Expires=Thu, 23 Jan 1992 00:01:02 GMT'
  955. b = Cookie('b', 'blr')
  956. b.expires = parse_date("Sun Nov 6 08:49:37 1994")
  957. assert b.render_response() == \
  958. 'b=blr; Expires=Sun, 06 Nov 1994 08:49:37 GMT'
  959. def test_eq(self):
  960. "Smoke test equality/inequality with Cookie objects"
  961. ref = Cookie('a', 'b')
  962. # trivial cases
  963. assert ref == ref
  964. assert not (ref != ref)
  965. assert None != ref
  966. assert not (None == ref)
  967. assert ref != None
  968. assert not (ref == None)
  969. # equivalence and nonequivalence
  970. assert Cookie('a', 'b') is not ref
  971. assert Cookie('a', 'b') == ref
  972. assert Cookie('x', 'y') != ref
  973. assert Cookie('a', 'y') != ref
  974. assert Cookie('a', 'b', path='/') != ref
  975. assert {'c': 'd'} != ref
  976. assert ref != {'c': 'd'}
  977. # unlike attribute values and sets of attributes
  978. assert Cookie('a', 'b', path='/a') \
  979. != Cookie('a', 'b', path='/')
  980. assert Cookie('x', 'y', max_age=3) != \
  981. Cookie('x', 'y', path='/b')
  982. assert Cookie('yargo', 'z', max_age=5) != \
  983. Cookie('yargo', 'z', max_age=6)
  984. assert ref != Cookie('a', 'b', domain='yab')
  985. # Exercise bytes conversion
  986. assert Cookie(b'a', 'b') == Cookie('a', 'b')
  987. assert Cookie(b'a', 'b') == Cookie(b'a', 'b')
  988. def test_manifest(self):
  989. "Test presence of important stuff on Cookie class"
  990. for name in ("attribute_names", "attribute_renderers",
  991. "attribute_parsers", "attribute_validators"):
  992. dictionary = getattr(Cookie, name)
  993. assert dictionary
  994. assert isinstance(dictionary, dict)
  995. def test_simple_extension(self):
  996. "Trivial example/smoke test of extending Cookie"
  997. count_state = [0]
  998. def call_counter(item=None):
  999. count_state[0] += 1
  1000. return True if item else False
  1001. class Cookie2(Cookie):
  1002. "Example Cookie subclass with new behavior"
  1003. attribute_names = {
  1004. 'foo': 'Foo',
  1005. 'bar': 'Bar',
  1006. 'baz': 'Baz',
  1007. 'ram': 'Ram',
  1008. }
  1009. attribute_parsers = {
  1010. 'foo': lambda s: "/".join(s),
  1011. 'bar': call_counter,
  1012. 'value': lambda s:
  1013. parse_value(s, allow_spaces=True),
  1014. }
  1015. attribute_validators = {
  1016. 'foo': lambda item: True,
  1017. 'bar': call_counter,
  1018. 'baz': lambda item: False,
  1019. }
  1020. attribute_renderers = {
  1021. 'foo': lambda s: "|".join(s) if s else None,
  1022. 'bar': call_counter,
  1023. 'name': lambda item: item,
  1024. }
  1025. cookie = Cookie2("a", "b")
  1026. for key in Cookie2.attribute_names:
  1027. assert hasattr(cookie, key)
  1028. assert getattr(cookie, key) == None
  1029. cookie.foo = "abc"
  1030. assert cookie.render_request() == "a=b"
  1031. assert cookie.render_response() == "a=b; Foo=a|b|c"
  1032. cookie.foo = None
  1033. # Setting it to None makes it drop from the listing
  1034. assert cookie.render_response() == "a=b"
  1035. cookie.bar = "what"
  1036. assert cookie.bar == "what"
  1037. assert cookie.render_request() == "a=b"
  1038. # bar's renderer returns a bool; if it's True we get Bar.
  1039. # that's a special case for flags like HttpOnly.
  1040. assert cookie.render_response() == "a=b; Bar"
  1041. with raises(InvalidCookieAttributeError):
  1042. cookie.baz = "anything"
  1043. Cookie2('a', 'b fog')
  1044. Cookie2('a', ' b=fo g')
  1045. def test_from_string(self):
  1046. with raises(InvalidCookieError):
  1047. Cookie.from_string("")
  1048. with raises(InvalidCookieError):
  1049. Cookie.from_string("", ignore_bad_attributes=True)
  1050. assert Cookie.from_string("", ignore_bad_cookies=True) == None
  1051. def test_from_dict(self):
  1052. assert Cookie.from_dict({'name': 'a', 'value': 'b'}) == \
  1053. Cookie('a', 'b')
  1054. assert Cookie.from_dict(
  1055. {'name': 'a', 'value': 'b', 'duh': 'no'},
  1056. ignore_bad_attributes=True) == \
  1057. Cookie('a', 'b')
  1058. with raises(InvalidCookieError):
  1059. Cookie.from_dict({}, ignore_bad_attributes=True)
  1060. with raises(InvalidCookieError):
  1061. Cookie.from_dict({}, ignore_bad_attributes=False)
  1062. with raises(InvalidCookieError):
  1063. Cookie.from_dict({'name': ''}, ignore_bad_attributes=False)
  1064. with raises(InvalidCookieError):
  1065. Cookie.from_dict({'name': None, 'value': 'b'},
  1066. ignore_bad_attributes=False)
  1067. assert Cookie.from_dict({'name': 'foo'}) == Cookie('foo', None)
  1068. assert Cookie.from_dict({'name': 'foo', 'value': ''}) == \
  1069. Cookie('foo', None)
  1070. with raises(InvalidCookieAttributeError):
  1071. assert Cookie.from_dict(
  1072. {'name': 'a', 'value': 'b', 'duh': 'no'},
  1073. ignore_bad_attributes=False)
  1074. assert Cookie.from_dict({'name': 'a', 'value': 'b', 'expires': 2},
  1075. ignore_bad_attributes=True) == Cookie('a', 'b')
  1076. with raises(InvalidCookieAttributeError):
  1077. assert Cookie.from_dict({'name': 'a', 'value': 'b', 'expires': 2},
  1078. ignore_bad_attributes=False)
  1079. class Scone(object):
  1080. """Non-useful alternative to Cookie class for tests only.
  1081. """
  1082. def __init__(self, name, value):
  1083. self.name = name
  1084. self.value = value
  1085. @classmethod
  1086. def from_dict(cls, cookie_dict):
  1087. instance = cls(cookie_dict['name'], cookie_dict['value'])
  1088. return instance
  1089. def __eq__(self, other):
  1090. if type(self) != type(other):
  1091. return False
  1092. if self.name != other.name:
  1093. return False
  1094. if self.value != other.value:
  1095. return False
  1096. return True
  1097. class Scones(Cookies):
  1098. """Non-useful alternative to Cookies class for tests only.
  1099. """
  1100. DEFAULT_COOKIE_CLASS = Scone
  1101. class TestCookies(object):
  1102. """Tests for the Cookies class.
  1103. """
  1104. creation_cases = [
  1105. # Only args - simple
  1106. ((Cookie("a", "b"),), {}, 1),
  1107. # Only kwargs - simple
  1108. (tuple(), {'a': 'b'}, 1),
  1109. # Only kwargs - bigger
  1110. (tuple(),
  1111. {'axl': 'bosk',
  1112. 'x': 'y',
  1113. 'foo': 'bar',
  1114. 'baz': 'bam'}, 4),
  1115. # Sum between args/kwargs
  1116. ((Cookie('a', 'b'),),
  1117. {'axl': 'bosk',
  1118. 'x': 'y',
  1119. 'foo': 'bar',
  1120. 'baz': 'bam'}, 5),
  1121. # Redundant between args/kwargs
  1122. ((Cookie('a', 'b'),
  1123. Cookie('x', 'y')),
  1124. {'axl': 'bosk',
  1125. 'x': 'y',
  1126. 'foo': 'bar',
  1127. 'baz': 'bam'}, 5),
  1128. ]
  1129. def test_init(self):
  1130. """Create some Cookies objects with __init__, varying the constructor
  1131. arguments, and check on the results.
  1132. Exercises __init__, __repr__, render_request, render_response, and
  1133. simple cases of parse_response and parse_request.
  1134. """
  1135. def same(a, b):
  1136. keys = sorted(set(a.keys() + b.keys()))
  1137. for key in keys:
  1138. assert a[key] == b[key]
  1139. for args, kwargs, length in self.creation_cases:
  1140. # Make a Cookies object using the args.
  1141. cookies = Cookies(*args, **kwargs)
  1142. assert len(cookies) == length
  1143. # Render into various text formats.
  1144. rep = repr(cookies)
  1145. res = cookies.render_response()
  1146. req = cookies.render_request()
  1147. # Very basic sanity check on renders, fail fast and in a simple way
  1148. # if output is truly terrible
  1149. assert rep.count('=') == length
  1150. assert len(res) == length
  1151. assert [item.count('=') == 1 for item in res]
  1152. assert req.count('=') == length
  1153. assert len(req.split(";")) == length
  1154. # Explicitly parse out the data (this can be simple since the
  1155. # output should be in a highly consistent format)
  1156. pairs = [item.split("=") for item in req.split("; ")]
  1157. assert len(pairs) == length
  1158. for name, value in pairs:
  1159. cookie = cookies[name]
  1160. assert cookie.name == name
  1161. assert cookie.value == value
  1162. # Parse the rendered output, check that result is equal to the
  1163. # originally produced object.
  1164. parsed = Cookies()
  1165. parsed.parse_request(req)
  1166. assert parsed == cookies
  1167. parsed = Cookies()
  1168. for item in res:
  1169. parsed.parse_response(item)
  1170. assert parsed == cookies
  1171. # Check that all the requested cookies were created correctly:
  1172. # indexed with correct names in dict, also with correctly set name
  1173. # and value attributes.
  1174. for cookie in args:
  1175. assert cookies[cookie.name] == cookie
  1176. for name, value in kwargs.items():
  1177. cookie = cookies[name]
  1178. assert cookie.name == name
  1179. assert cookie.value == value
  1180. assert name in rep
  1181. assert value in rep
  1182. # Spot check that setting an attribute still works
  1183. # with these particular parameters. Not a torture test.
  1184. for key in cookies:
  1185. cookies[key].max_age = 42
  1186. for line in cookies.render_response():
  1187. assert line.endswith("Max-Age=42")
  1188. # Spot check attribute deletion
  1189. assert cookies[key].max_age
  1190. del cookies[key].max_age
  1191. assert cookies[key].max_age is None
  1192. # Spot check cookie deletion
  1193. keys = [key for key in cookies.keys()]
  1194. for key in keys:
  1195. del cookies[key]
  1196. assert key not in cookies
  1197. def test_eq(self):
  1198. "Smoke test equality/inequality of Cookies objects"
  1199. ref = Cookies(a='b')
  1200. assert Cookies(a='b') == ref
  1201. assert Cookies(b='c') != ref
  1202. assert ref != Cookies(d='e')
  1203. assert Cookies(a='x') != ref
  1204. class Dummy(object):
  1205. "Just any old object"
  1206. pass
  1207. x = Dummy()
  1208. x.keys = True
  1209. with raises(TypeError):
  1210. assert ref != x
  1211. def test_add(self):
  1212. "Test the Cookies.add method"
  1213. for args, kwargs, length in self.creation_cases:
  1214. cookies = Cookies()
  1215. cookies.add(*args, **kwargs)
  1216. assert len(cookies) == length
  1217. for cookie in args:
  1218. assert cookies[cookie.name] == cookie
  1219. for name, value in kwargs.items():
  1220. cookie = cookies[name]
  1221. assert cookie.value == value
  1222. count = len(cookies)
  1223. assert 'w' not in cookies
  1224. cookies.add(w='m')
  1225. assert 'w' in cookies
  1226. assert count == len(cookies) - 1
  1227. assert cookies['w'].value == 'm'
  1228. def test_empty(self):
  1229. "Trivial test of behavior of empty Cookies object"
  1230. cookies = Cookies()
  1231. assert len(cookies) == 0
  1232. assert Cookies() == cookies
  1233. def test_parse_request(self):
  1234. """Test Cookies.parse_request.
  1235. """
  1236. def run(arg, **kwargs):
  1237. "run Cookies.parse_request on an instance"
  1238. cookies = Cookies()
  1239. result = runner(cookies.parse_request)(arg, **kwargs)
  1240. return result
  1241. for i, case in enumerate(HEADER_CASES):
  1242. arg, kwargs, expected, response_result = case
  1243. # parse_request doesn't take ignore_bad_attributes. remove it
  1244. # without changing original kwargs for further tests
  1245. kwargs = kwargs.copy()
  1246. if 'ignore_bad_attributes' in kwargs:
  1247. del kwargs['ignore_bad_attributes']
  1248. def expect(arg, kwargs):
  1249. "repeated complex assertion"
  1250. result = run(arg, **kwargs)
  1251. assert result == expected \
  1252. or isinstance(expected, type) \
  1253. and type(result) == expected, \
  1254. "unexpected result for (%s): %s. should be %s" \
  1255. % (repr(arg), repr(result), repr(expected))
  1256. # Check result - should be same with and without the prefix
  1257. expect("Cookie: " + arg, kwargs)
  1258. expect(arg, kwargs)
  1259. # But it should not match with the response prefix.
  1260. other_result = run("Set-Cookie: " + arg, **kwargs)
  1261. assert other_result != expected
  1262. assert other_result != response_result
  1263. # If case expects InvalidCookieError, verify that it is suppressed
  1264. # by ignore_bad_cookies.
  1265. if expected == InvalidCookieError:
  1266. kwargs2 = kwargs.copy()
  1267. kwargs2['ignore_bad_cookies'] = True
  1268. cookies = Cookies()
  1269. # Let natural exception raise, easier to figure out
  1270. cookies.parse_request(arg, **kwargs2)
  1271. # Spot check that exception is raised for clearly wrong format
  1272. assert not isinstance(run("Cookie: a=b"), InvalidCookieError)
  1273. assert isinstance(run("Set-Cookie: a=b"), InvalidCookieError)
  1274. def test_parse_response(self):
  1275. """Test Cookies.parse_response.
  1276. """
  1277. def run(arg, **kwargs):
  1278. "run parse_response method of a Cookies instance"
  1279. cookies = Cookies()
  1280. return runner(cookies.parse_response)(arg, **kwargs)
  1281. for case in HEADER_CASES:
  1282. arg, kwargs, request_result, expected = case
  1283. # If we expect InvalidCookieError or InvalidCookieAttributeError,
  1284. # telling the function to ignore those should result in no
  1285. # exception.
  1286. kwargs2 = kwargs.copy()
  1287. if expected == InvalidCookieError:
  1288. kwargs2['ignore_bad_cookies'] = True
  1289. assert not isinstance(
  1290. run(arg, **kwargs2),
  1291. Exception)
  1292. elif expected == InvalidCookieAttributeError:
  1293. kwargs2['ignore_bad_attributes'] = True
  1294. result = run(arg, **kwargs2)
  1295. if isinstance(result, InvalidCookieAttributeError):
  1296. raise AssertionError("InvalidCookieAttributeError "
  1297. "should have been silenced/logged")
  1298. else:
  1299. assert not isinstance(result, Exception)
  1300. # Check result - should be same with and without the prefix
  1301. sys.stdout.flush()
  1302. result = run(arg, **kwargs)
  1303. assert result == expected \
  1304. or isinstance(expected, type) \
  1305. and type(result) == expected, \
  1306. "unexpected result for (%s): %s. should be %s" \
  1307. % (repr(arg), repr(result), repr(expected))
  1308. result = run("Set-Cookie: " + arg, **kwargs)
  1309. assert result == expected \
  1310. or isinstance(expected, type) \
  1311. and type(result) == expected, \
  1312. "unexpected result for (%s): %s. should be %s" \
  1313. % (repr("Set-Cookie: " + arg),
  1314. repr(result), repr(expected))
  1315. # But it should not match with the request prefix.
  1316. other_result = run("Cookie: " + arg, **kwargs)
  1317. assert other_result != expected
  1318. assert other_result != request_result
  1319. assert not isinstance(run("Set-Cookie: a=b"), InvalidCookieError)
  1320. assert isinstance(run("Cookie: a=b"), InvalidCookieError)
  1321. def test_exercise_parse_one_response_asctime(self):
  1322. asctime = 'Sun Nov 6 08:49:37 1994'
  1323. line = "Set-Cookie: a=b; Expires=%s" % asctime
  1324. response_dict = parse_one_response(line)
  1325. assert response_dict == \
  1326. {'expires': 'Sun Nov 6 08:49:37 1994', 'name': 'a', 'value': 'b'}
  1327. assert Cookie.from_dict(response_dict) == \
  1328. Cookie('a', 'b', expires=parse_date(asctime))
  1329. def test_get_all(self):
  1330. cookies = Cookies.from_request('a=b; a=c; b=x')
  1331. assert cookies['a'].value == 'b'
  1332. assert cookies['b'].value == 'x'
  1333. values = [cookie.value for cookie in cookies.get_all('a')]
  1334. assert values == ['b', 'c']
  1335. def test_custom_cookie_class_on_instance(self):
  1336. cookies = Cookies(_cookie_class=Scone)
  1337. cookies.add(a="b")
  1338. assert cookies['a'] == Scone("a", "b")
  1339. def test_custom_cookie_class_on_subclass(self):
  1340. cookies = Scones()
  1341. cookies.add(a="b")
  1342. assert cookies['a'] == Scone("a", "b")
  1343. def test_custom_cookie_class_on_instance_parse_request(self):
  1344. cookies = Scones()
  1345. cookies.parse_request("Cookie: c=d")
  1346. assert cookies['c'] == Scone("c", "d")
  1347. def test_custom_cookie_class_on_instance_parse_response(self):
  1348. cookies = Scones()
  1349. cookies.parse_response("Set-Cookie: c=d")
  1350. assert cookies['c'] == Scone("c", "d")
  1351. def test_parse_date():
  1352. """Throw a ton of dirty samples at the date parse/render and verify the
  1353. exact output of rendering the parsed version of the sample.
  1354. """
  1355. cases = [
  1356. # Obviously off format
  1357. ("", None),
  1358. (" ", None),
  1359. ("\t", None),
  1360. ("\n", None),
  1361. ("\x02\x03\x04", None),
  1362. ("froppity", None),
  1363. ("@@@@@%@#:%", None),
  1364. ("foo bar baz", None),
  1365. # We'll do a number of overall manglings.
  1366. # First, show that the baseline passes
  1367. ("Sat, 10 Oct 2009 13:47:21 GMT", "Sat, 10 Oct 2009 13:47:21 GMT"),
  1368. # Delete semantically important pieces
  1369. (" Oct 2009 13:47:21 GMT", None),
  1370. ("Fri, Oct 2009 13:47:21 GMT", None),
  1371. ("Fri, 10 2009 13:47:21 GMT", None),
  1372. ("Sat, 10 Oct 2009 :47:21 GMT", None),
  1373. ("Sat, 10 Oct 2009 13::21 GMT", None),
  1374. ("Sat, 10 Oct 2009 13:47: GMT", None),
  1375. # Replace single characters out of tokens with spaces - harder to
  1376. # do programmatically because some whitespace can reasonably be
  1377. # tolerated.
  1378. ("F i, 10 Oct 2009 13:47:21 GMT", None),
  1379. ("Fr , 10 Oct 2009 13:47:21 GMT", None),
  1380. ("Fri, 10 ct 2009 13:47:21 GMT", None),
  1381. ("Fri, 10 O t 2009 13:47:21 GMT", None),
  1382. ("Fri, 10 Oc 2009 13:47:21 GMT", None),
  1383. ("Sat, 10 Oct 009 13:47:21 GMT", None),
  1384. ("Sat, 10 Oct 2 09 13:47:21 GMT", None),
  1385. ("Sat, 10 Oct 20 9 13:47:21 GMT", None),
  1386. ("Sat, 10 Oct 200 13:47:21 GMT", None),
  1387. ("Sat, 10 Oct 2009 1 :47:21 GMT", None),
  1388. ("Sat, 10 Oct 2009 13 47:21 GMT", None),
  1389. ("Sat, 10 Oct 2009 13: 7:21 GMT", None),
  1390. ("Sat, 10 Oct 2009 13:4 :21 GMT", None),
  1391. ("Sat, 10 Oct 2009 13:47 21 GMT", None),
  1392. ("Sat, 10 Oct 2009 13:47: 1 GMT", None),
  1393. ("Sat, 10 Oct 2009 13:47:2 GMT", None),
  1394. ("Sat, 10 Oct 2009 13:47:21 MT", None),
  1395. ("Sat, 10 Oct 2009 13:47:21 G T", None),
  1396. ("Sat, 10 Oct 2009 13:47:21 GM ", None),
  1397. # Replace numeric elements with stuff that contains A-Z
  1398. ("Fri, Burp Oct 2009 13:47:21 GMT", None),
  1399. ("Fri, 10 Tabalqplar 2009 13:47:21 GMT", None),
  1400. ("Sat, 10 Oct Fruit 13:47:21 GMT", None),
  1401. ("Sat, 10 Oct 2009 13:47:21 Fruits", None),
  1402. # Weekday
  1403. (", Dec 31 00:00:00 2003", None),
  1404. ("T, Dec 31 00:00:00 2003", None),
  1405. ("Tu, Dec 31 00:00:00 2003", None),
  1406. ("Hi, Dec 31 00:00:00 2003", None),
  1407. ("Heretounforeseen, Dec 31 00:00:00 2003", None),
  1408. ("Wednesday2, Dec 31 00:00:00 2003", None),
  1409. ("Mon\x00frobs, Dec 31 00:00:00 2003", None),
  1410. ("Mon\x10day, Dec 31 00:00:00 2003", None),
  1411. # Day of month
  1412. ("Fri, Oct 2009 13:47:21 GMT", None),
  1413. ("Fri, 110 Oct 2009 13:47:21 GMT", None),
  1414. ("Fri, 0 Oct 2009 13:47:21 GMT", None),
  1415. ("Fri, 00 Oct 2009 13:47:21 GMT", None),
  1416. ("Fri, 0 Oct 2009 13:47:21 GMT", None),
  1417. ("Fri, 0 Oct 2009 13:47:21 GMT", None),
  1418. ("Fri, 00 Oct 2009 13:47:21 GMT", None),
  1419. ("Fri, 33 Oct 2009 13:47:21 GMT", None),
  1420. ("Fri, 40 Oct 2009 13:47:21 GMT", None),
  1421. ("Fri, A2 Oct 2009 13:47:21 GMT", None),
  1422. ("Fri, 2\x00 Oct 2009 13:47:21 GMT", None),
  1423. ("Fri, \t3 Oct 2009 13:47:21 GMT", None),
  1424. ("Fri, 3\t Oct 2009 13:47:21 GMT", None),
  1425. # Month
  1426. ("Fri, 10 2009 13:47:21 GMT", None),
  1427. ("Fri, 10 O 2009 13:47:21 GMT", None),
  1428. ("Fri, 10 Oc 2009 13:47:21 GMT", None),
  1429. ("Sat, 10 Octuarial 2009 13:47:21 GMT", None),
  1430. ("Sat, 10 Octuary 2009 13:47:21 GMT", None),
  1431. ("Sat, 10 Octubre 2009 13:47:21 GMT", None),
  1432. # Year
  1433. ("Sat, 10 Oct 009 13:47:21 GMT", None),
  1434. ("Sat, 10 Oct 200 13:47:21 GMT", None),
  1435. ("Sat, 10 Oct 209 13:47:21 GMT", None),
  1436. ("Sat, 10 Oct 20 9 13:47:21 GMT", None),
  1437. # Hour
  1438. ("Sat, 10 Oct 2009 25:47:21 GMT", None),
  1439. ("Sat, 10 Oct 2009 1@:47:21 GMT", None),
  1440. # Minute
  1441. ("Sat, 10 Oct 2009 13:71:21 GMT", None),
  1442. ("Sat, 10 Oct 2009 13:61:21 GMT", None),
  1443. ("Sat, 10 Oct 2009 13:60:21 GMT", None),
  1444. ("Sat, 10 Oct 2009 24:01:00 GMT", None),
  1445. # Second
  1446. ("Sat, 10 Oct 2009 13:47 GMT", "Sat, 10 Oct 2009 13:47:00 GMT"),
  1447. ("Sat, 10 Oct 2009 13:47:00 GMT", "Sat, 10 Oct 2009 13:47:00 GMT"),
  1448. ("Sat, 10 Oct 2009 24:00:01 GMT", None),
  1449. # Some reasonable cases (ignore weekday)
  1450. ("Mon Dec 24 16:32:39 1977 GMT", "Sat, 24 Dec 1977 16:32:39 GMT"),
  1451. ("Sat, 7 Dec 1991 13:56:05 GMT", "Sat, 07 Dec 1991 13:56:05 GMT"),
  1452. ("Saturday, 8-Mar-2012 21:35:09 GMT", "Thu, 08 Mar 2012 21:35:09 GMT"),
  1453. ("Sun, 1-Feb-1998 00:00:00 GMT", "Sun, 01 Feb 1998 00:00:00 GMT"),
  1454. ("Thursday, 01-Jan-1983 01:01:01 GMT",
  1455. "Sat, 01 Jan 1983 01:01:01 GMT"),
  1456. ("Tue, 15-Nov-1973 22:23:24 GMT", "Thu, 15 Nov 1973 22:23:24 GMT"),
  1457. ("Wed, 09 Dec 1999 23:59:59 GMT", "Thu, 09 Dec 1999 23:59:59 GMT"),
  1458. ("Mon, 12-May-05 20:25:03 GMT", "Thu, 12 May 2005 20:25:03 GMT"),
  1459. ("Thursday, 01-Jan-12 09:00:00 GMT", "Sun, 01 Jan 2012 09:00:00 GMT"),
  1460. # starts like asctime, but flips the time and year - nonsense
  1461. ("Wed Mar 12 2007 08:25:07 GMT", None),
  1462. # starts like RFC 1123, but flips the time and year - nonsense
  1463. ("Thu, 31 Dec 23:55:55 2107 GMT", None),
  1464. ('Fri, 21-May-2004 10:40:51 GMT', "Fri, 21 May 2004 10:40:51 GMT"),
  1465. # extra 2-digit year exercises
  1466. ("Sat, 10 Oct 11 13:47:21 GMT", "Mon, 10 Oct 2011 13:47:21 GMT"),
  1467. ("Sat, 10 Oct 09 13:47:22 GMT", "Sat, 10 Oct 2009 13:47:22 GMT"),
  1468. ("Sat, 10 Oct 93 13:47:23 GMT", "Sun, 10 Oct 1993 13:47:23 GMT"),
  1469. ("Sat, 10 Oct 85 13:47:24 GMT", "Thu, 10 Oct 1985 13:47:24 GMT"),
  1470. ("Sat, 10 Oct 70 13:47:25 GMT", "Sat, 10 Oct 1970 13:47:25 GMT"),
  1471. ("Sat, 10 Oct 69 13:47:26 GMT", "Thu, 10 Oct 2069 13:47:26 GMT"),
  1472. # dealing with 3-digit year is incredibly tedious, will do as needed
  1473. ("Sat, 10 Oct 969 13:47:26 GMT", None),
  1474. ("Sat, 10 Oct 9 13:47:26 GMT", None),
  1475. ("Fri, 10 Oct 19691 13:47:26 GMT", None),
  1476. ]
  1477. def change(string, position, new_value):
  1478. "Macro to change a string"
  1479. return string[:position] + new_value + string[position + 1:]
  1480. original = "Sat, 10 Oct 2009 13:47:21 GMT"
  1481. # Stuff garbage in every position - none of these characters should
  1482. # ever be allowed in a date string.
  1483. # not included because pytest chokes: "¿�␦"
  1484. bad_chars = "/<>()\\*$#&=;\x00\b\f\n\r\"\'`?"
  1485. for pos in range(0, len(original)):
  1486. for bad_char in bad_chars:
  1487. cases.append((change(original, pos, bad_char), None))
  1488. # Invalidate each letter
  1489. letter_positions = [i for (i, c) in enumerate(original) \
  1490. if re.match("[A-Za-z]", c)]
  1491. for pos in letter_positions:
  1492. cases.append((change(original, pos, 'q'), None))
  1493. cases.append((change(original, pos, '0'), None))
  1494. cases.append((change(original, pos, '-'), None))
  1495. cases.append((change(original, pos, ''), None))
  1496. # But do tolerate case changes.
  1497. c = original[pos]
  1498. if c.isupper():
  1499. c = c.lower()
  1500. else:
  1501. c = c.upper()
  1502. cases.append((change(original, pos, c), original))
  1503. # Invalidate each digit
  1504. digit_positions = [i for (i, c) in enumerate(original) \
  1505. if c in "0123456789"]
  1506. for pos in digit_positions:
  1507. c = original[pos]
  1508. cases.append((change(original, pos, 'q'), None))
  1509. cases.append((change(original, pos, '-' + c), None))
  1510. cases.append((change(original, pos, '+' + c), None))
  1511. # Invalidate each space
  1512. space_positions = [i for (i, c) in enumerate(original) \
  1513. if c in " \t\n\r"]
  1514. for pos in space_positions:
  1515. cases.append((change(original, pos, 'x'), None))
  1516. cases.append((change(original, pos, '\t'), None))
  1517. cases.append((change(original, pos, ' '), None))
  1518. cases.append((change(original, pos, ''), None))
  1519. # Invalidate each colon
  1520. colon_positions = [i for (i, c) in enumerate(original) \
  1521. if c == ":"]
  1522. for pos in colon_positions:
  1523. cases.append((change(original, pos, 'z'), None))
  1524. cases.append((change(original, pos, '0'), None))
  1525. cases.append((change(original, pos, ' '), None))
  1526. cases.append((change(original, pos, ''), None))
  1527. for data, ideal in cases:
  1528. actual = render_date(parse_date(data))
  1529. assert actual == ideal
  1530. def runner(function):
  1531. """Generate a function which collects the result/exception from another
  1532. function, for easier assertions.
  1533. """
  1534. def run(*args, **kwargs):
  1535. "Function which collects result/exception"
  1536. actual_result, actual_exception = None, None
  1537. try:
  1538. actual_result = function(*args, **kwargs)
  1539. except Exception as exception:
  1540. actual_exception = exception
  1541. return actual_exception or actual_result
  1542. return run
  1543. # Define cases for testing parsing and rendering.
  1544. # Format: input, kwargs, expected parse_request result, expected parse_response
  1545. # result.
  1546. HEADER_CASES = [
  1547. # cases with nothing that can be parsed out result in
  1548. # InvalidCookieError. unless ignore_bad_cookies=True, then they give an
  1549. # empty Cookies().
  1550. ("", {},
  1551. InvalidCookieError,
  1552. InvalidCookieError),
  1553. ('a', {},
  1554. InvalidCookieError,
  1555. InvalidCookieError),
  1556. (" ", {},
  1557. InvalidCookieError,
  1558. InvalidCookieError),
  1559. (";;;;;", {},
  1560. InvalidCookieError,
  1561. InvalidCookieError),
  1562. ("qwejrkqlwjere", {},
  1563. InvalidCookieError,
  1564. InvalidCookieError),
  1565. # vacuous headers should give invalid
  1566. ('Cookie: ', {},
  1567. InvalidCookieError,
  1568. InvalidCookieError),
  1569. ('Set-Cookie: ', {},
  1570. InvalidCookieError,
  1571. InvalidCookieError),
  1572. # Single pair should work the same as request or response
  1573. ("foo=bar", {},
  1574. Cookies(foo='bar'),
  1575. Cookies(foo='bar')),
  1576. ("SID=242d96421d4e", {},
  1577. Cookies(SID='242d96421d4e'),
  1578. Cookies(SID='242d96421d4e')),
  1579. # Two pairs on SAME line should work with request, fail with response.
  1580. # if ignore_bad_attributes, response should not raise.
  1581. # and ignore_bad_attributes behavior should be default
  1582. ("a=b; c=dx", {'ignore_bad_attributes': True},
  1583. Cookies(a='b', c='dx'),
  1584. Cookies(a='b')),
  1585. ("a=b; c=d", {'ignore_bad_attributes': False},
  1586. Cookies(a='b', c='d'),
  1587. InvalidCookieAttributeError),
  1588. ('g=h;j=k', {},
  1589. Cookies(g='h', j='k'),
  1590. Cookies(g='h')),
  1591. # tolerance: response shouldn't barf on unrecognized attr by default,
  1592. # but request should recognize as malformed
  1593. ('a=b; brains', {},
  1594. InvalidCookieError,
  1595. Cookies(a='b')),
  1596. # tolerance: should strip quotes and spaces
  1597. ('A="BBB"', {},
  1598. Cookies(A='BBB'),
  1599. Cookies(A='BBB'),
  1600. ),
  1601. ('A= "BBB" ', {},
  1602. Cookies(A='BBB'),
  1603. Cookies(A='BBB'),
  1604. ),
  1605. # tolerance: should ignore dumb trailing ;
  1606. ('foo=bar;', {},
  1607. Cookies(foo='bar'),
  1608. Cookies(foo='bar'),
  1609. ),
  1610. ('A="BBB";', {},
  1611. Cookies(A='BBB'),
  1612. Cookies(A='BBB'),
  1613. ),
  1614. ('A= "BBB" ;', {},
  1615. Cookies(A='BBB'),
  1616. Cookies(A='BBB'),
  1617. ),
  1618. # empty value
  1619. ("lang=; Expires=Sun, 06 Nov 1994 08:49:37 GMT", {},
  1620. InvalidCookieError,
  1621. Cookies(
  1622. Cookie('lang', '',
  1623. expires=parse_date(
  1624. "Sun, 06 Nov 1994 08:49:37 GMT")))),
  1625. # normal examples of varying complexity
  1626. ("frob=varvels; Expires=Wed, 09 Jun 2021 10:18:14 GMT", {},
  1627. InvalidCookieError,
  1628. Cookies(
  1629. Cookie('frob', 'varvels',
  1630. expires=parse_date(
  1631. "Wed, 09 Jun 2021 10:18:14 GMT"
  1632. )))),
  1633. ("lang=en-US; Expires=Wed, 03 Jun 2019 10:18:14 GMT", {},
  1634. InvalidCookieError,
  1635. Cookies(
  1636. Cookie('lang', 'en-US',
  1637. expires=parse_date(
  1638. "Wed, 03 Jun 2019 10:18:14 GMT"
  1639. )))),
  1640. # easily interpretable as multiple request cookies!
  1641. ("CID=39b4d9be4d42; Path=/; Domain=example.com", {},
  1642. Cookies(CID="39b4d9be4d42", Path='/', Domain='example.com'),
  1643. Cookies(Cookie('CID', '39b4d9be4d42', path='/',
  1644. domain='example.com'))),
  1645. ("lang=en-US; Path=/; Domain=example.com", {},
  1646. Cookies(lang='en-US', Path='/', Domain='example.com'),
  1647. Cookies(Cookie('lang', 'en-US',
  1648. path='/', domain='example.com'))),
  1649. ("foo=bar; path=/; expires=Mon, 04-Dec-2001 12:43:00 GMT", {},
  1650. InvalidCookieError,
  1651. Cookies(
  1652. Cookie('foo', 'bar', path='/',
  1653. expires=parse_date("Mon, 04-Dec-2001 12:43:00 GMT")
  1654. ))),
  1655. ("SID=0fae49; Path=/; Secure; HttpOnly", {},
  1656. InvalidCookieError,
  1657. Cookies(Cookie('SID', '0fae49',
  1658. path='/', secure=True, httponly=True))),
  1659. ('TMID=DQAAXKEaeo_aYp; Domain=mail.nauk.com; '
  1660. 'Path=/accounts; Expires=Wed, 13-Jan-2021 22:23:01 GMT; '
  1661. 'Secure; HttpOnly', {},
  1662. InvalidCookieError,
  1663. Cookies(
  1664. Cookie('TMID', 'DQAAXKEaeo_aYp',
  1665. domain='mail.nauk.com',
  1666. path='/accounts', secure=True, httponly=True,
  1667. expires=parse_date("Wed, 13-Jan-2021 22:23:01 GMT")
  1668. ))),
  1669. ("test=some_value; expires=Sat, 01-Jan-2000 00:00:00 GMT; "
  1670. "path=/;", {},
  1671. InvalidCookieError,
  1672. Cookies(
  1673. Cookie('test', 'some_value', path='/',
  1674. expires=parse_date('Sat, 01 Jan 2000 00:00:00 GMT')
  1675. ))),
  1676. # From RFC 2109 - accept the lots-of-dquotes style but don't produce.
  1677. ('Customer="WILE_E_COYOTE"; Version="1"; Path="/acme"; '
  1678. 'Part_Number="Rocket_Launcher_0001"', {},
  1679. Cookies(Customer='WILE_E_COYOTE', Version='1', Path='/acme',
  1680. Part_Number='Rocket_Launcher_0001'),
  1681. Cookies(Cookie('Customer', 'WILE_E_COYOTE',
  1682. version=1, path='/acme'))),
  1683. # However, we don't honor RFC 2109 type meta-attributes
  1684. ('Cookie: $Version="1"; Customer="WILE_E_COYOTE"; $Path="/acme"', {},
  1685. InvalidCookieError,
  1686. InvalidCookieError),
  1687. # degenerate Domain=. is common, so should be handled though invalid
  1688. ("lu=Qg3OHJZLehYLjVgAqiZbZbzo; Expires=Tue, 15-Jan-2013 "
  1689. "21:47:38 GMT; Path=/; Domain=.foo.com; HttpOnly", {},
  1690. InvalidCookieError,
  1691. Cookies(Cookie('lu', "Qg3OHJZLehYLjVgAqiZbZbzo",
  1692. expires=parse_date('Tue, 15 Jan 2013 21:47:38 GMT'),
  1693. path='/', domain='.foo.com', httponly=True,
  1694. ))),
  1695. ('ZQID=AYBEVnDKrdst; Domain=.nauk.com; Path=/; '
  1696. 'Expires=Wed, 13-Jan-2021 22:23:01 GMT; HttpOnly', {},
  1697. InvalidCookieError,
  1698. Cookies(Cookie('ZQID', "AYBEVnDKrdst",
  1699. httponly=True, domain='.nauk.com', path='/',
  1700. expires=parse_date('Wed, 13 Jan 2021 22:23:01 GMT'),
  1701. ))),
  1702. ("OMID=Ap4PQQEq; Domain=.nauk.com; Path=/; "
  1703. 'Expires=Wed, 13-Jan-2021 22:23:01 GMT; Secure; HttpOnly', {},
  1704. InvalidCookieError,
  1705. Cookies(Cookie('OMID', "Ap4PQQEq",
  1706. path='/', domain='.nauk.com', secure=True, httponly=True,
  1707. expires=parse_date('Wed, 13 Jan 2021 22:23:01 GMT')
  1708. ))),
  1709. # question mark in value
  1710. ('foo="?foo"; Path=/', {},
  1711. Cookies(foo='?foo', Path='/'),
  1712. Cookies(Cookie('foo', '?foo', path='/'))),
  1713. # unusual format for secure/httponly
  1714. ("a=b; Secure=true; HttpOnly=true;", {},
  1715. Cookies(a='b', Secure='true', HttpOnly='true'),
  1716. Cookies(Cookie('a', 'b', secure=True, httponly=True))),
  1717. # invalid per RFC to have spaces in value, but here they are
  1718. # URL-encoded by default. Extend the mechanism if this is no good
  1719. ('user=RJMmei IORqmD; expires=Wed, 3 Nov 2007 23:20:39 GMT; path=/',
  1720. {},
  1721. InvalidCookieError,
  1722. Cookies(
  1723. Cookie('user', 'RJMmei IORqmD', path='/',
  1724. expires=parse_date("Wed, 3 Nov 2007 23:20:39 GMT")))),
  1725. # Most characters from 32 to \x31 + 1 should be allowed in values -
  1726. # not including space/32, dquote/34, comma/44.
  1727. ("x=!#$%&'()*+-./01", {},
  1728. Cookies(x="!#$%&'()*+-./01"),
  1729. Cookies(x="!#$%&'()*+-./01")),
  1730. # don't crash when value wrapped with quotes
  1731. # http://bugs.python.org/issue3924
  1732. ('a=b; version="1"', {},
  1733. Cookies(a='b', version='1'),
  1734. Cookies(Cookie('a', 'b', version=1))),
  1735. # cookie with name 'expires'. inadvisable, but valid.
  1736. # http://bugs.python.org/issue1117339
  1737. ('expires=foo', {},
  1738. Cookies(expires='foo'),
  1739. Cookies(expires='foo')),
  1740. # http://bugs.python.org/issue8826
  1741. # quick date parsing spot-check, see test_parse_date for a real workout
  1742. ('foo=bar; expires=Fri, 31-Dec-2010 23:59:59 GMT', {},
  1743. InvalidCookieError,
  1744. Cookies(
  1745. Cookie('foo', 'bar',
  1746. expires=datetime(2010, 12, 31, 23, 59, 59)))),
  1747. # allow VALID equals sign in values - not even an issue in RFC 6265 or
  1748. # this module, but very helpful for base64 and always worth checking.
  1749. # http://bugs.python.org/issue403473
  1750. ('a=Zm9vIGJhcg==', {},
  1751. Cookies(a='Zm9vIGJhcg=='),
  1752. Cookies(a='Zm9vIGJhcg==')),
  1753. ('blah="Foo=2"', {},
  1754. Cookies(blah='Foo=2'),
  1755. Cookies(blah='Foo=2')),
  1756. # take the first cookie in request parsing.
  1757. # (response parse ignores the second one as a bad attribute)
  1758. # http://bugs.python.org/issue1375011
  1759. # http://bugs.python.org/issue1372650
  1760. # http://bugs.python.org/issue7504
  1761. ('foo=33;foo=34', {},
  1762. Cookies(foo='33'),
  1763. Cookies(foo='33')),
  1764. # Colons in names (invalid!), as used by some dumb old Java/PHP code
  1765. # http://bugs.python.org/issue2988
  1766. # http://bugs.python.org/issue472646
  1767. # http://bugs.python.org/issue2193
  1768. ('a:b=c', {},
  1769. Cookies(
  1770. Cookie('a:b', 'c')),
  1771. Cookies(
  1772. Cookie('a:b', 'c'))),
  1773. # # http://bugs.python.org/issue991266
  1774. # # This module doesn't do the backslash quoting so this would
  1775. # # effectively require allowing all possible characters inside arbitrary
  1776. # # attributes, which does not seem reasonable.
  1777. # ('foo=bar; Comment="\342\230\243"', {},
  1778. # Cookies(foo='bar', Comment='\342\230\243'),
  1779. # Cookies(
  1780. # Cookie('foo', 'bar', comment='\342\230\243')
  1781. # )),
  1782. ]
  1783. def _cheap_request_parse(arg1, arg2):
  1784. """Really cheap parse like what client code often does, for
  1785. testing request rendering (determining order-insensitively whether two
  1786. cookies-as-text are equivalent). 'a=b; x=y' type format
  1787. """
  1788. def crumble(arg):
  1789. "Break down string into pieces"
  1790. pieces = [piece.strip('\r\n ;') for piece in re.split("(\r\n|;)", arg)]
  1791. pieces = [piece for piece in pieces if piece and '=' in piece]
  1792. pieces = [tuple(piece.split("=", 1)) for piece in pieces]
  1793. pieces = [(name.strip(), value.strip('" ')) for name, value in pieces]
  1794. # Keep the first one in front (can use set down the line);
  1795. # the rest are sorted
  1796. if len(pieces) > 1:
  1797. pieces = [pieces[0]] + sorted(pieces[1:])
  1798. return pieces
  1799. def dedupe(pieces):
  1800. "Eliminate duplicate pieces"
  1801. deduped = {}
  1802. for name, value in pieces:
  1803. if name in deduped:
  1804. continue
  1805. deduped[name] = value
  1806. return sorted(deduped.items(),
  1807. key=pieces.index)
  1808. return dedupe(crumble(arg1)), crumble(arg2)
  1809. def _cheap_response_parse(arg1, arg2):
  1810. """Silly parser for 'name=value; attr=attrvalue' format,
  1811. to test out response renders
  1812. """
  1813. def crumble(arg):
  1814. "Break down string into pieces"
  1815. lines = [line for line in arg if line]
  1816. done = []
  1817. for line in lines:
  1818. clauses = [clause for clause in line.split(';')]
  1819. import logging
  1820. logging.error("clauses %r", clauses)
  1821. name, value = re.split(" *= *", clauses[0], 1)
  1822. value = unquote(value.strip(' "'))
  1823. attrs = [re.split(" *= *", clause, 1) \
  1824. for clause in clauses[1:] if clause]
  1825. attrs = [attr for attr in attrs \
  1826. if attr[0] in Cookie.attribute_names]
  1827. attrs = [(k, v.strip(' "')) for k, v in attrs]
  1828. done.append((name, value, tuple(attrs)))
  1829. return done
  1830. result1 = crumble([arg1])
  1831. result2 = crumble(arg2)
  1832. return result1, result2
  1833. def test_render_request():
  1834. """Test the request renderer against HEADER_CASES.
  1835. Perhaps a wider range of values is tested in TestCookies.test_init.
  1836. """
  1837. for case in HEADER_CASES:
  1838. arg, kwargs, cookies, _ = case
  1839. # can't reproduce examples which are supposed to throw parse errors
  1840. if isinstance(cookies, type) and issubclass(cookies, Exception):
  1841. continue
  1842. rendered = cookies.render_request()
  1843. expected, actual = _cheap_request_parse(arg, rendered)
  1844. # we can only use set() here because requests aren't order sensitive.
  1845. assert set(actual) == set(expected)
  1846. def test_render_response():
  1847. """Test the response renderer against HEADER_CASES.
  1848. Perhaps a wider range of values is tested in TestCookies.test_init.
  1849. """
  1850. def filter_attrs(items):
  1851. "Filter out the items which are Cookie attributes"
  1852. return [(name, value) for (name, value) in items \
  1853. if name.lower() in Cookie.attribute_names]
  1854. for case in HEADER_CASES:
  1855. arg, kwargs, _, cookies = case
  1856. # can't reproduce examples which are supposed to throw parse errors
  1857. if isinstance(cookies, type) and issubclass(cookies, Exception):
  1858. continue
  1859. rendered = cookies.render_response()
  1860. expected, actual = _cheap_response_parse(arg, rendered)
  1861. expected, actual = set(expected), set(actual)
  1862. assert actual == expected, \
  1863. "failed: %s -> %s | %s != %s" % (arg, repr(cookies), actual,
  1864. expected)
  1865. def test_backslash_roundtrip():
  1866. """Check that backslash in input or value stays backslash internally but
  1867. goes out as %5C, and comes back in again as a backslash.
  1868. """
  1869. reference = Cookie('xx', '\\')
  1870. assert len(reference.value) == 1
  1871. reference_request = reference.render_request()
  1872. reference_response = reference.render_response()
  1873. assert '\\' not in reference_request
  1874. assert '\\' not in reference_response
  1875. assert '%5C' in reference_request
  1876. assert '%5C' in reference_response
  1877. # Parse from multiple entry points
  1878. raw_cookie = r'xx="\"'
  1879. parsed_cookies = [Cookie.from_string(raw_cookie),
  1880. Cookies.from_request(raw_cookie)['xx'],
  1881. Cookies.from_response(raw_cookie)['xx']]
  1882. for parsed_cookie in parsed_cookies:
  1883. assert parsed_cookie.name == reference.name
  1884. assert parsed_cookie.value == reference.value
  1885. # Renders should match exactly
  1886. request = parsed_cookie.render_request()
  1887. response = parsed_cookie.render_response()
  1888. assert request == reference_request
  1889. assert response == reference_response
  1890. # Reparses should too
  1891. rrequest = Cookies.from_request(request)['xx']
  1892. rresponse = Cookies.from_response(response)['xx']
  1893. assert rrequest.name == reference.name
  1894. assert rrequest.value == reference.value
  1895. assert rresponse.name == reference.name
  1896. assert rresponse.value == reference.value
  1897. def _simple_test(function, case_dict):
  1898. "Macro for making simple case-based tests for a function call"
  1899. def actual_test():
  1900. "Test generated by _simple_test"
  1901. for arg, expected in case_dict.items():
  1902. logging.info("case for %s: %s %s",
  1903. repr(function), repr(arg), repr(expected))
  1904. result = function(arg)
  1905. assert result == expected, \
  1906. "%s(%s) != %s, rather %s" % (
  1907. function.__name__,
  1908. repr(arg),
  1909. repr(expected),
  1910. repr(result))
  1911. actual_test.cases = case_dict
  1912. return actual_test
  1913. test_strip_spaces_and_quotes = _simple_test(strip_spaces_and_quotes, {
  1914. ' ': '',
  1915. '""': '',
  1916. '"': '"',
  1917. "''": "''",
  1918. ' foo ': 'foo',
  1919. 'foo ': 'foo',
  1920. ' foo': 'foo',
  1921. ' "" ': '',
  1922. ' " " ': ' ',
  1923. ' " ': '"',
  1924. 'foo bar': 'foo bar',
  1925. '"foo bar': '"foo bar',
  1926. 'foo bar"': 'foo bar"',
  1927. '"foo bar"': 'foo bar',
  1928. '"dquoted"': 'dquoted',
  1929. ' "dquoted"': 'dquoted',
  1930. '"dquoted" ': 'dquoted',
  1931. ' "dquoted" ': 'dquoted',
  1932. })
  1933. test_parse_string = _simple_test(parse_string, {
  1934. None: None,
  1935. '': '',
  1936. b'': '',
  1937. })
  1938. test_parse_domain = _simple_test(parse_domain, {
  1939. ' foo ': 'foo',
  1940. '"foo"': 'foo',
  1941. ' "foo" ': 'foo',
  1942. '.foo': '.foo',
  1943. })
  1944. test_parse_path = _simple_test(parse_path, {
  1945. })
  1946. def test_render_date():
  1947. "Test date render routine directly with raw datetime objects"
  1948. # Date rendering is also exercised pretty well in test_parse_date.
  1949. cases = {
  1950. # Error for anything which is not known UTC/GMT
  1951. datetime(2001, 10, 11, tzinfo=FixedOffsetTz(60 * 60)):
  1952. AssertionError,
  1953. # A couple of baseline tests
  1954. datetime(1970, 1, 1, 0, 0, 0):
  1955. 'Thu, 01 Jan 1970 00:00:00 GMT',
  1956. datetime(2007, 9, 2, 13, 59, 49):
  1957. 'Sun, 02 Sep 2007 13:59:49 GMT',
  1958. # Don't produce 1-digit hour
  1959. datetime(2007, 9, 2, 1, 59, 49):
  1960. "Sun, 02 Sep 2007 01:59:49 GMT",
  1961. # Don't produce 1-digit minute
  1962. datetime(2007, 9, 2, 1, 1, 49):
  1963. "Sun, 02 Sep 2007 01:01:49 GMT",
  1964. # Don't produce 1-digit second
  1965. datetime(2007, 9, 2, 1, 1, 2):
  1966. "Sun, 02 Sep 2007 01:01:02 GMT",
  1967. # Allow crazy past/future years for cookie delete/persist
  1968. datetime(1900, 9, 2, 1, 1, 2):
  1969. "Sun, 02 Sep 1900 01:01:02 GMT",
  1970. datetime(3000, 9, 2, 1, 1, 2):
  1971. "Tue, 02 Sep 3000 01:01:02 GMT"
  1972. }
  1973. for dt, expected in cases.items():
  1974. if isinstance(expected, type) and issubclass(expected, Exception):
  1975. try:
  1976. render_date(dt)
  1977. except expected:
  1978. continue
  1979. except Exception as exception:
  1980. raise AssertionError("expected %s, got %s"
  1981. % (expected, exception))
  1982. raise AssertionError("expected %s, got no exception"
  1983. % (expected))
  1984. else:
  1985. assert render_date(dt) == expected
  1986. def test_encoding_assumptions(check_unicode=False):
  1987. "Document and test assumptions underlying URL encoding scheme"
  1988. # Use the RFC 6265 based character class to build a regexp matcher that
  1989. # will tell us whether or not a character is okay to put in cookie values.
  1990. cookie_value_re = re.compile("[%s]" % Definitions.COOKIE_OCTET)
  1991. # Figure out which characters are okay. (unichr doesn't exist in Python 3,
  1992. # in Python 2 it shouldn't be an issue)
  1993. cookie_value_safe1 = set(chr(i) for i in range(0, 256) \
  1994. if cookie_value_re.match(chr(i)))
  1995. cookie_value_safe2 = set(unichr(i) for i in range(0, 256) \
  1996. if cookie_value_re.match(unichr(i)))
  1997. # These two are NOT the same on Python3
  1998. assert cookie_value_safe1 == cookie_value_safe2
  1999. # Now which of these are quoted by urllib.quote?
  2000. # caveat: Python 2.6 crashes if chr(127) is passed to quote and safe="",
  2001. # so explicitly set it to b"" to avoid the issue
  2002. safe_but_quoted = set(c for c in cookie_value_safe1
  2003. if quote(c, safe=b"") != c)
  2004. # Produce a set of characters to give to urllib.quote for the safe parm.
  2005. dont_quote = "".join(sorted(safe_but_quoted))
  2006. # Make sure it works (and that it works because of what we passed)
  2007. for c in dont_quote:
  2008. assert quote(c, safe="") != c
  2009. assert quote(c, safe=dont_quote) == c
  2010. # Make sure that the result of using dont_quote as the safe characters for
  2011. # urllib.quote produces stuff which is safe as a cookie value, but not
  2012. # different unless it has to be.
  2013. for i in range(0, 255):
  2014. original = chr(i)
  2015. quoted = quote(original, safe=dont_quote)
  2016. # If it is a valid value for a cookie, that quoting should leave it
  2017. # alone.
  2018. if cookie_value_re.match(original):
  2019. assert original == quoted
  2020. # If it isn't a valid value, then the quoted value should be valid.
  2021. else:
  2022. assert cookie_value_re.match(quoted)
  2023. assert set(dont_quote) == set("!#$%&'()*+/:<=>?@[]^`{|}~")
  2024. # From 128 on urllib.quote will not work on a unichr() return value.
  2025. # We'll want to encode utf-8 values into ASCII, then do the quoting.
  2026. # Verify that this is reversible.
  2027. if check_unicode:
  2028. for c in (unichr(i) for i in range(0, 1114112)):
  2029. asc = c.encode('utf-8')
  2030. quoted = quote(asc, safe=dont_quote)
  2031. unquoted = unquote(asc)
  2032. unicoded = unquoted.decode('utf-8')
  2033. assert unicoded == c
  2034. # Now do the same for extension-av.
  2035. extension_av_re = re.compile("[%s]" % Definitions.EXTENSION_AV)
  2036. extension_av_safe = set(chr(i) for i in range(0, 256) \
  2037. if extension_av_re.match(chr(i)))
  2038. safe_but_quoted = set(c for c in extension_av_safe \
  2039. if quote(c, safe="") != c)
  2040. dont_quote = "".join(sorted(safe_but_quoted))
  2041. for c in dont_quote:
  2042. assert quote(c, safe="") != c
  2043. assert quote(c, safe=dont_quote) == c
  2044. for i in range(0, 255):
  2045. original = chr(i)
  2046. quoted = quote(original, safe=dont_quote)
  2047. if extension_av_re.match(original):
  2048. assert original == quoted
  2049. else:
  2050. assert extension_av_re.match(quoted)
  2051. assert set(dont_quote) == set(' !"#$%&\'()*+,/:<=>?@[\\]^`{|}~')
  2052. test_encode_cookie_value = _simple_test(encode_cookie_value,
  2053. {
  2054. None: None,
  2055. ' ': '%20',
  2056. # let through
  2057. '!': '!',
  2058. '#': '#',
  2059. '$': '$',
  2060. '%': '%',
  2061. '&': '&',
  2062. "'": "'",
  2063. '(': '(',
  2064. ')': ')',
  2065. '*': '*',
  2066. '+': '+',
  2067. '/': '/',
  2068. ':': ':',
  2069. '<': '<',
  2070. '=': '=',
  2071. '>': '>',
  2072. '?': '?',
  2073. '@': '@',
  2074. '[': '[',
  2075. ']': ']',
  2076. '^': '^',
  2077. '`': '`',
  2078. '{': '{',
  2079. '|': '|',
  2080. '}': '}',
  2081. '~': '~',
  2082. # not let through
  2083. ' ': '%20',
  2084. '"': '%22',
  2085. ',': '%2C',
  2086. '\\': '%5C',
  2087. 'crud,': 'crud%2C',
  2088. })
  2089. test_encode_extension_av = _simple_test(encode_extension_av,
  2090. {
  2091. None: '',
  2092. '': '',
  2093. 'foo': 'foo',
  2094. # stuff this lets through that cookie-value does not
  2095. ' ': ' ',
  2096. '"': '"',
  2097. ',': ',',
  2098. '\\': '\\',
  2099. 'yo\\b': 'yo\\b',
  2100. })
  2101. test_valid_value = _simple_test(valid_value,
  2102. {
  2103. None: False,
  2104. '': True,
  2105. 'ಠ_ಠ': True,
  2106. 'μῆνιν ἄειδε θεὰ Πηληϊάδεω Ἀχιλῆος': True,
  2107. '这事情得搞好啊': True,
  2108. '宮崎 駿': True,
  2109. 'أم كلثوم': True,
  2110. 'ედუარდ შევარდნაძე': True,
  2111. 'Myötähäpeä': True,
  2112. 'Pedro Almodóvar': True,
  2113. # b'': True,
  2114. # b'ABCDEFGHIJKLMNOPQRSTUVWXYZ': True,
  2115. 'Pedro Almodóvar'.encode('utf-8'): False,
  2116. })
  2117. test_valid_date = _simple_test(valid_date,
  2118. {
  2119. datetime(2011, 1, 1): True,
  2120. datetime(2011, 1, 1, tzinfo=FixedOffsetTz(1000)): False,
  2121. datetime(2011, 1, 1, tzinfo=FixedOffsetTz(0)): True,
  2122. })
  2123. test_valid_domain = _simple_test(valid_domain,
  2124. {
  2125. '': False,
  2126. ' ': False,
  2127. '.': False,
  2128. '..': False,
  2129. '.foo': True,
  2130. '"foo"': False,
  2131. 'foo': True,
  2132. })
  2133. test_valid_path = _simple_test(valid_path,
  2134. {
  2135. '': False,
  2136. ' ': False,
  2137. '/': True,
  2138. 'a': False,
  2139. '/a': True,
  2140. '\x00': False,
  2141. '/\x00': False,
  2142. })
  2143. def test_many_pairs():
  2144. """Simple 'lots of pairs' test
  2145. """
  2146. from_request = Cookies.from_request
  2147. header = "a0=0"
  2148. for i in range(1, 100):
  2149. i_range = list(range(0, i))
  2150. cookies = from_request(header)
  2151. assert len(cookies) == i
  2152. for j in i_range:
  2153. key = 'a%d' % j
  2154. assert cookies[key].value == str(j * 10)
  2155. assert cookies[key].render_request() == \
  2156. "a%d=%d" % (j, j * 10)
  2157. # same test, different entry point
  2158. cookies = Cookies()
  2159. cookies.parse_request(header)
  2160. assert len(cookies) == i
  2161. for j in i_range:
  2162. key = 'a%d' % j
  2163. assert cookies[key].value == str(j * 10)
  2164. assert cookies[key].render_request() == \
  2165. "a%d=%d" % (j, j * 10)
  2166. # Add another piece to the header
  2167. header += "; a%d=%d" % (i, i * 10)
  2168. def test_parse_value():
  2169. # this really just glues together strip_spaces_and_quotes
  2170. # and parse_string, so reuse their test cases
  2171. cases = {}
  2172. cases.update(test_strip_spaces_and_quotes.cases)
  2173. cases.update(test_parse_string.cases)
  2174. for inp, expected in cases.items():
  2175. print("case", inp, expected)
  2176. # Test with spaces allowed
  2177. obtained = parse_value(inp, allow_spaces=True)
  2178. assert obtained == expected
  2179. # Test with spaces disallowed, if it could do anything
  2180. if (isinstance(inp, bytes) and ' ' in inp.decode('utf-8').strip()) \
  2181. or (not isinstance(inp, bytes) and inp and ' ' in inp.strip()):
  2182. try:
  2183. obtained = parse_value(inp, allow_spaces=False)
  2184. except AssertionError:
  2185. pass
  2186. else:
  2187. raise AssertionError("parse_value(%s, allow_spaces=False) "
  2188. "did not raise" % repr(inp))
  2189. def test_total_seconds():
  2190. """This wrapper probably doesn't need testing so much, and it's not
  2191. entirely trivial to fully exercise, but the coverage is nice to have
  2192. """
  2193. def basic_sanity(td_type):
  2194. assert _total_seconds(td_type(seconds=1)) == 1
  2195. assert _total_seconds(td_type(seconds=1, minutes=1)) == 1 + 60
  2196. assert _total_seconds(td_type(seconds=1, minutes=1, hours=1)) == \
  2197. 1 + 60 + 60 * 60
  2198. basic_sanity(timedelta)
  2199. class FakeTimeDelta(object):
  2200. def __init__(self, days=0, hours=0, minutes=0, seconds=0,
  2201. microseconds=0):
  2202. self.days = days
  2203. self.seconds = seconds + minutes * 60 + hours * 60 * 60
  2204. self.microseconds = microseconds
  2205. assert not hasattr(FakeTimeDelta, "total_seconds")
  2206. basic_sanity(FakeTimeDelta)
  2207. FakeTimeDelta.total_seconds = lambda: None.missing_attribute
  2208. try:
  2209. _total_seconds(None)
  2210. except AttributeError as e:
  2211. assert 'total_seconds' not in str(e)
  2212. def test_valid_value_bad_quoter():
  2213. def bad_quote(s):
  2214. return "Frogs"
  2215. assert valid_value("eep", quote=bad_quote) == False