(root)/
Python-3.11.7/
Lib/
http/
cookies.py
       1  ####
       2  # Copyright 2000 by Timothy O'Malley <timo@alum.mit.edu>
       3  #
       4  #                All Rights Reserved
       5  #
       6  # Permission to use, copy, modify, and distribute this software
       7  # and its documentation for any purpose and without fee is hereby
       8  # granted, provided that the above copyright notice appear in all
       9  # copies and that both that copyright notice and this permission
      10  # notice appear in supporting documentation, and that the name of
      11  # Timothy O'Malley  not be used in advertising or publicity
      12  # pertaining to distribution of the software without specific, written
      13  # prior permission.
      14  #
      15  # Timothy O'Malley DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
      16  # SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
      17  # AND FITNESS, IN NO EVENT SHALL Timothy O'Malley BE LIABLE FOR
      18  # ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
      19  # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
      20  # WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
      21  # ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
      22  # PERFORMANCE OF THIS SOFTWARE.
      23  #
      24  ####
      25  #
      26  # Id: Cookie.py,v 2.29 2000/08/23 05:28:49 timo Exp
      27  #   by Timothy O'Malley <timo@alum.mit.edu>
      28  #
      29  #  Cookie.py is a Python module for the handling of HTTP
      30  #  cookies as a Python dictionary.  See RFC 2109 for more
      31  #  information on cookies.
      32  #
      33  #  The original idea to treat Cookies as a dictionary came from
      34  #  Dave Mitchell (davem@magnet.com) in 1995, when he released the
      35  #  first version of nscookie.py.
      36  #
      37  ####
      38  
      39  r"""
      40  Here's a sample session to show how to use this module.
      41  At the moment, this is the only documentation.
      42  
      43  The Basics
      44  ----------
      45  
      46  Importing is easy...
      47  
      48     >>> from http import cookies
      49  
      50  Most of the time you start by creating a cookie.
      51  
      52     >>> C = cookies.SimpleCookie()
      53  
      54  Once you've created your Cookie, you can add values just as if it were
      55  a dictionary.
      56  
      57     >>> C = cookies.SimpleCookie()
      58     >>> C["fig"] = "newton"
      59     >>> C["sugar"] = "wafer"
      60     >>> C.output()
      61     'Set-Cookie: fig=newton\r\nSet-Cookie: sugar=wafer'
      62  
      63  Notice that the printable representation of a Cookie is the
      64  appropriate format for a Set-Cookie: header.  This is the
      65  default behavior.  You can change the header and printed
      66  attributes by using the .output() function
      67  
      68     >>> C = cookies.SimpleCookie()
      69     >>> C["rocky"] = "road"
      70     >>> C["rocky"]["path"] = "/cookie"
      71     >>> print(C.output(header="Cookie:"))
      72     Cookie: rocky=road; Path=/cookie
      73     >>> print(C.output(attrs=[], header="Cookie:"))
      74     Cookie: rocky=road
      75  
      76  The load() method of a Cookie extracts cookies from a string.  In a
      77  CGI script, you would use this method to extract the cookies from the
      78  HTTP_COOKIE environment variable.
      79  
      80     >>> C = cookies.SimpleCookie()
      81     >>> C.load("chips=ahoy; vienna=finger")
      82     >>> C.output()
      83     'Set-Cookie: chips=ahoy\r\nSet-Cookie: vienna=finger'
      84  
      85  The load() method is darn-tootin smart about identifying cookies
      86  within a string.  Escaped quotation marks, nested semicolons, and other
      87  such trickeries do not confuse it.
      88  
      89     >>> C = cookies.SimpleCookie()
      90     >>> C.load('keebler="E=everybody; L=\\"Loves\\"; fudge=\\012;";')
      91     >>> print(C)
      92     Set-Cookie: keebler="E=everybody; L=\"Loves\"; fudge=\012;"
      93  
      94  Each element of the Cookie also supports all of the RFC 2109
      95  Cookie attributes.  Here's an example which sets the Path
      96  attribute.
      97  
      98     >>> C = cookies.SimpleCookie()
      99     >>> C["oreo"] = "doublestuff"
     100     >>> C["oreo"]["path"] = "/"
     101     >>> print(C)
     102     Set-Cookie: oreo=doublestuff; Path=/
     103  
     104  Each dictionary element has a 'value' attribute, which gives you
     105  back the value associated with the key.
     106  
     107     >>> C = cookies.SimpleCookie()
     108     >>> C["twix"] = "none for you"
     109     >>> C["twix"].value
     110     'none for you'
     111  
     112  The SimpleCookie expects that all values should be standard strings.
     113  Just to be sure, SimpleCookie invokes the str() builtin to convert
     114  the value to a string, when the values are set dictionary-style.
     115  
     116     >>> C = cookies.SimpleCookie()
     117     >>> C["number"] = 7
     118     >>> C["string"] = "seven"
     119     >>> C["number"].value
     120     '7'
     121     >>> C["string"].value
     122     'seven'
     123     >>> C.output()
     124     'Set-Cookie: number=7\r\nSet-Cookie: string=seven'
     125  
     126  Finis.
     127  """
     128  
     129  #
     130  # Import our required modules
     131  #
     132  import re
     133  import string
     134  import types
     135  
     136  __all__ = ["CookieError", "BaseCookie", "SimpleCookie"]
     137  
     138  _nulljoin = ''.join
     139  _semispacejoin = '; '.join
     140  _spacejoin = ' '.join
     141  
     142  #
     143  # Define an exception visible to External modules
     144  #
     145  class ESC[4;38;5;81mCookieError(ESC[4;38;5;149mException):
     146      pass
     147  
     148  
     149  # These quoting routines conform to the RFC2109 specification, which in
     150  # turn references the character definitions from RFC2068.  They provide
     151  # a two-way quoting algorithm.  Any non-text character is translated
     152  # into a 4 character sequence: a forward-slash followed by the
     153  # three-digit octal equivalent of the character.  Any '\' or '"' is
     154  # quoted with a preceding '\' slash.
     155  # Because of the way browsers really handle cookies (as opposed to what
     156  # the RFC says) we also encode "," and ";".
     157  #
     158  # These are taken from RFC2068 and RFC2109.
     159  #       _LegalChars       is the list of chars which don't require "'s
     160  #       _Translator       hash-table for fast quoting
     161  #
     162  _LegalChars = string.ascii_letters + string.digits + "!#$%&'*+-.^_`|~:"
     163  _UnescapedChars = _LegalChars + ' ()/<=>?@[]{}'
     164  
     165  _Translator = {n: '\\%03o' % n
     166                 for n in set(range(256)) - set(map(ord, _UnescapedChars))}
     167  _Translator.update({
     168      ord('"'): '\\"',
     169      ord('\\'): '\\\\',
     170  })
     171  
     172  _is_legal_key = re.compile('[%s]+' % re.escape(_LegalChars)).fullmatch
     173  
     174  def _quote(str):
     175      r"""Quote a string for use in a cookie header.
     176  
     177      If the string does not need to be double-quoted, then just return the
     178      string.  Otherwise, surround the string in doublequotes and quote
     179      (with a \) special characters.
     180      """
     181      if str is None or _is_legal_key(str):
     182          return str
     183      else:
     184          return '"' + str.translate(_Translator) + '"'
     185  
     186  
     187  _OctalPatt = re.compile(r"\\[0-3][0-7][0-7]")
     188  _QuotePatt = re.compile(r"[\\].")
     189  
     190  def _unquote(str):
     191      # If there aren't any doublequotes,
     192      # then there can't be any special characters.  See RFC 2109.
     193      if str is None or len(str) < 2:
     194          return str
     195      if str[0] != '"' or str[-1] != '"':
     196          return str
     197  
     198      # We have to assume that we must decode this string.
     199      # Down to work.
     200  
     201      # Remove the "s
     202      str = str[1:-1]
     203  
     204      # Check for special sequences.  Examples:
     205      #    \012 --> \n
     206      #    \"   --> "
     207      #
     208      i = 0
     209      n = len(str)
     210      res = []
     211      while 0 <= i < n:
     212          o_match = _OctalPatt.search(str, i)
     213          q_match = _QuotePatt.search(str, i)
     214          if not o_match and not q_match:              # Neither matched
     215              res.append(str[i:])
     216              break
     217          # else:
     218          j = k = -1
     219          if o_match:
     220              j = o_match.start(0)
     221          if q_match:
     222              k = q_match.start(0)
     223          if q_match and (not o_match or k < j):     # QuotePatt matched
     224              res.append(str[i:k])
     225              res.append(str[k+1])
     226              i = k + 2
     227          else:                                      # OctalPatt matched
     228              res.append(str[i:j])
     229              res.append(chr(int(str[j+1:j+4], 8)))
     230              i = j + 4
     231      return _nulljoin(res)
     232  
     233  # The _getdate() routine is used to set the expiration time in the cookie's HTTP
     234  # header.  By default, _getdate() returns the current time in the appropriate
     235  # "expires" format for a Set-Cookie header.  The one optional argument is an
     236  # offset from now, in seconds.  For example, an offset of -3600 means "one hour
     237  # ago".  The offset may be a floating point number.
     238  #
     239  
     240  _weekdayname = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
     241  
     242  _monthname = [None,
     243                'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
     244                'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
     245  
     246  def _getdate(future=0, weekdayname=_weekdayname, monthname=_monthname):
     247      from time import gmtime, time
     248      now = time()
     249      year, month, day, hh, mm, ss, wd, y, z = gmtime(now + future)
     250      return "%s, %02d %3s %4d %02d:%02d:%02d GMT" % \
     251             (weekdayname[wd], day, monthname[month], year, hh, mm, ss)
     252  
     253  
     254  class ESC[4;38;5;81mMorsel(ESC[4;38;5;149mdict):
     255      """A class to hold ONE (key, value) pair.
     256  
     257      In a cookie, each such pair may have several attributes, so this class is
     258      used to keep the attributes associated with the appropriate key,value pair.
     259      This class also includes a coded_value attribute, which is used to hold
     260      the network representation of the value.
     261      """
     262      # RFC 2109 lists these attributes as reserved:
     263      #   path       comment         domain
     264      #   max-age    secure      version
     265      #
     266      # For historical reasons, these attributes are also reserved:
     267      #   expires
     268      #
     269      # This is an extension from Microsoft:
     270      #   httponly
     271      #
     272      # This dictionary provides a mapping from the lowercase
     273      # variant on the left to the appropriate traditional
     274      # formatting on the right.
     275      _reserved = {
     276          "expires"  : "expires",
     277          "path"     : "Path",
     278          "comment"  : "Comment",
     279          "domain"   : "Domain",
     280          "max-age"  : "Max-Age",
     281          "secure"   : "Secure",
     282          "httponly" : "HttpOnly",
     283          "version"  : "Version",
     284          "samesite" : "SameSite",
     285      }
     286  
     287      _flags = {'secure', 'httponly'}
     288  
     289      def __init__(self):
     290          # Set defaults
     291          self._key = self._value = self._coded_value = None
     292  
     293          # Set default attributes
     294          for key in self._reserved:
     295              dict.__setitem__(self, key, "")
     296  
     297      @property
     298      def key(self):
     299          return self._key
     300  
     301      @property
     302      def value(self):
     303          return self._value
     304  
     305      @property
     306      def coded_value(self):
     307          return self._coded_value
     308  
     309      def __setitem__(self, K, V):
     310          K = K.lower()
     311          if not K in self._reserved:
     312              raise CookieError("Invalid attribute %r" % (K,))
     313          dict.__setitem__(self, K, V)
     314  
     315      def setdefault(self, key, val=None):
     316          key = key.lower()
     317          if key not in self._reserved:
     318              raise CookieError("Invalid attribute %r" % (key,))
     319          return dict.setdefault(self, key, val)
     320  
     321      def __eq__(self, morsel):
     322          if not isinstance(morsel, Morsel):
     323              return NotImplemented
     324          return (dict.__eq__(self, morsel) and
     325                  self._value == morsel._value and
     326                  self._key == morsel._key and
     327                  self._coded_value == morsel._coded_value)
     328  
     329      __ne__ = object.__ne__
     330  
     331      def copy(self):
     332          morsel = Morsel()
     333          dict.update(morsel, self)
     334          morsel.__dict__.update(self.__dict__)
     335          return morsel
     336  
     337      def update(self, values):
     338          data = {}
     339          for key, val in dict(values).items():
     340              key = key.lower()
     341              if key not in self._reserved:
     342                  raise CookieError("Invalid attribute %r" % (key,))
     343              data[key] = val
     344          dict.update(self, data)
     345  
     346      def isReservedKey(self, K):
     347          return K.lower() in self._reserved
     348  
     349      def set(self, key, val, coded_val):
     350          if key.lower() in self._reserved:
     351              raise CookieError('Attempt to set a reserved key %r' % (key,))
     352          if not _is_legal_key(key):
     353              raise CookieError('Illegal key %r' % (key,))
     354  
     355          # It's a good key, so save it.
     356          self._key = key
     357          self._value = val
     358          self._coded_value = coded_val
     359  
     360      def __getstate__(self):
     361          return {
     362              'key': self._key,
     363              'value': self._value,
     364              'coded_value': self._coded_value,
     365          }
     366  
     367      def __setstate__(self, state):
     368          self._key = state['key']
     369          self._value = state['value']
     370          self._coded_value = state['coded_value']
     371  
     372      def output(self, attrs=None, header="Set-Cookie:"):
     373          return "%s %s" % (header, self.OutputString(attrs))
     374  
     375      __str__ = output
     376  
     377      def __repr__(self):
     378          return '<%s: %s>' % (self.__class__.__name__, self.OutputString())
     379  
     380      def js_output(self, attrs=None):
     381          # Print javascript
     382          return """
     383          <script type="text/javascript">
     384          <!-- begin hiding
     385          document.cookie = \"%s\";
     386          // end hiding -->
     387          </script>
     388          """ % (self.OutputString(attrs).replace('"', r'\"'))
     389  
     390      def OutputString(self, attrs=None):
     391          # Build up our result
     392          #
     393          result = []
     394          append = result.append
     395  
     396          # First, the key=value pair
     397          append("%s=%s" % (self.key, self.coded_value))
     398  
     399          # Now add any defined attributes
     400          if attrs is None:
     401              attrs = self._reserved
     402          items = sorted(self.items())
     403          for key, value in items:
     404              if value == "":
     405                  continue
     406              if key not in attrs:
     407                  continue
     408              if key == "expires" and isinstance(value, int):
     409                  append("%s=%s" % (self._reserved[key], _getdate(value)))
     410              elif key == "max-age" and isinstance(value, int):
     411                  append("%s=%d" % (self._reserved[key], value))
     412              elif key == "comment" and isinstance(value, str):
     413                  append("%s=%s" % (self._reserved[key], _quote(value)))
     414              elif key in self._flags:
     415                  if value:
     416                      append(str(self._reserved[key]))
     417              else:
     418                  append("%s=%s" % (self._reserved[key], value))
     419  
     420          # Return the result
     421          return _semispacejoin(result)
     422  
     423      __class_getitem__ = classmethod(types.GenericAlias)
     424  
     425  
     426  #
     427  # Pattern for finding cookie
     428  #
     429  # This used to be strict parsing based on the RFC2109 and RFC2068
     430  # specifications.  I have since discovered that MSIE 3.0x doesn't
     431  # follow the character rules outlined in those specs.  As a
     432  # result, the parsing rules here are less strict.
     433  #
     434  
     435  _LegalKeyChars  = r"\w\d!#%&'~_`><@,:/\$\*\+\-\.\^\|\)\(\?\}\{\="
     436  _LegalValueChars = _LegalKeyChars + r'\[\]'
     437  _CookiePattern = re.compile(r"""
     438      \s*                            # Optional whitespace at start of cookie
     439      (?P<key>                       # Start of group 'key'
     440      [""" + _LegalKeyChars + r"""]+?   # Any word of at least one letter
     441      )                              # End of group 'key'
     442      (                              # Optional group: there may not be a value.
     443      \s*=\s*                          # Equal Sign
     444      (?P<val>                         # Start of group 'val'
     445      "(?:[^\\"]|\\.)*"                  # Any doublequoted string
     446      |                                  # or
     447      \w{3},\s[\w\d\s-]{9,11}\s[\d:]{8}\sGMT  # Special case for "expires" attr
     448      |                                  # or
     449      [""" + _LegalValueChars + r"""]*      # Any word or empty string
     450      )                                # End of group 'val'
     451      )?                             # End of optional value group
     452      \s*                            # Any number of spaces.
     453      (\s+|;|$)                      # Ending either at space, semicolon, or EOS.
     454      """, re.ASCII | re.VERBOSE)    # re.ASCII may be removed if safe.
     455  
     456  
     457  # At long last, here is the cookie class.  Using this class is almost just like
     458  # using a dictionary.  See this module's docstring for example usage.
     459  #
     460  class ESC[4;38;5;81mBaseCookie(ESC[4;38;5;149mdict):
     461      """A container class for a set of Morsels."""
     462  
     463      def value_decode(self, val):
     464          """real_value, coded_value = value_decode(STRING)
     465          Called prior to setting a cookie's value from the network
     466          representation.  The VALUE is the value read from HTTP
     467          header.
     468          Override this function to modify the behavior of cookies.
     469          """
     470          return val, val
     471  
     472      def value_encode(self, val):
     473          """real_value, coded_value = value_encode(VALUE)
     474          Called prior to setting a cookie's value from the dictionary
     475          representation.  The VALUE is the value being assigned.
     476          Override this function to modify the behavior of cookies.
     477          """
     478          strval = str(val)
     479          return strval, strval
     480  
     481      def __init__(self, input=None):
     482          if input:
     483              self.load(input)
     484  
     485      def __set(self, key, real_value, coded_value):
     486          """Private method for setting a cookie's value"""
     487          M = self.get(key, Morsel())
     488          M.set(key, real_value, coded_value)
     489          dict.__setitem__(self, key, M)
     490  
     491      def __setitem__(self, key, value):
     492          """Dictionary style assignment."""
     493          if isinstance(value, Morsel):
     494              # allow assignment of constructed Morsels (e.g. for pickling)
     495              dict.__setitem__(self, key, value)
     496          else:
     497              rval, cval = self.value_encode(value)
     498              self.__set(key, rval, cval)
     499  
     500      def output(self, attrs=None, header="Set-Cookie:", sep="\015\012"):
     501          """Return a string suitable for HTTP."""
     502          result = []
     503          items = sorted(self.items())
     504          for key, value in items:
     505              result.append(value.output(attrs, header))
     506          return sep.join(result)
     507  
     508      __str__ = output
     509  
     510      def __repr__(self):
     511          l = []
     512          items = sorted(self.items())
     513          for key, value in items:
     514              l.append('%s=%s' % (key, repr(value.value)))
     515          return '<%s: %s>' % (self.__class__.__name__, _spacejoin(l))
     516  
     517      def js_output(self, attrs=None):
     518          """Return a string suitable for JavaScript."""
     519          result = []
     520          items = sorted(self.items())
     521          for key, value in items:
     522              result.append(value.js_output(attrs))
     523          return _nulljoin(result)
     524  
     525      def load(self, rawdata):
     526          """Load cookies from a string (presumably HTTP_COOKIE) or
     527          from a dictionary.  Loading cookies from a dictionary 'd'
     528          is equivalent to calling:
     529              map(Cookie.__setitem__, d.keys(), d.values())
     530          """
     531          if isinstance(rawdata, str):
     532              self.__parse_string(rawdata)
     533          else:
     534              # self.update() wouldn't call our custom __setitem__
     535              for key, value in rawdata.items():
     536                  self[key] = value
     537          return
     538  
     539      def __parse_string(self, str, patt=_CookiePattern):
     540          i = 0                 # Our starting point
     541          n = len(str)          # Length of string
     542          parsed_items = []     # Parsed (type, key, value) triples
     543          morsel_seen = False   # A key=value pair was previously encountered
     544  
     545          TYPE_ATTRIBUTE = 1
     546          TYPE_KEYVALUE = 2
     547  
     548          # We first parse the whole cookie string and reject it if it's
     549          # syntactically invalid (this helps avoid some classes of injection
     550          # attacks).
     551          while 0 <= i < n:
     552              # Start looking for a cookie
     553              match = patt.match(str, i)
     554              if not match:
     555                  # No more cookies
     556                  break
     557  
     558              key, value = match.group("key"), match.group("val")
     559              i = match.end(0)
     560  
     561              if key[0] == "$":
     562                  if not morsel_seen:
     563                      # We ignore attributes which pertain to the cookie
     564                      # mechanism as a whole, such as "$Version".
     565                      # See RFC 2965. (Does anyone care?)
     566                      continue
     567                  parsed_items.append((TYPE_ATTRIBUTE, key[1:], value))
     568              elif key.lower() in Morsel._reserved:
     569                  if not morsel_seen:
     570                      # Invalid cookie string
     571                      return
     572                  if value is None:
     573                      if key.lower() in Morsel._flags:
     574                          parsed_items.append((TYPE_ATTRIBUTE, key, True))
     575                      else:
     576                          # Invalid cookie string
     577                          return
     578                  else:
     579                      parsed_items.append((TYPE_ATTRIBUTE, key, _unquote(value)))
     580              elif value is not None:
     581                  parsed_items.append((TYPE_KEYVALUE, key, self.value_decode(value)))
     582                  morsel_seen = True
     583              else:
     584                  # Invalid cookie string
     585                  return
     586  
     587          # The cookie string is valid, apply it.
     588          M = None         # current morsel
     589          for tp, key, value in parsed_items:
     590              if tp == TYPE_ATTRIBUTE:
     591                  assert M is not None
     592                  M[key] = value
     593              else:
     594                  assert tp == TYPE_KEYVALUE
     595                  rval, cval = value
     596                  self.__set(key, rval, cval)
     597                  M = self[key]
     598  
     599  
     600  class ESC[4;38;5;81mSimpleCookie(ESC[4;38;5;149mBaseCookie):
     601      """
     602      SimpleCookie supports strings as cookie values.  When setting
     603      the value using the dictionary assignment notation, SimpleCookie
     604      calls the builtin str() to convert the value to a string.  Values
     605      received from HTTP are kept as strings.
     606      """
     607      def value_decode(self, val):
     608          return _unquote(val), val
     609  
     610      def value_encode(self, val):
     611          strval = str(val)
     612          return strval, _quote(strval)