Index: third_party/google-endpoints/future/backports/http/cookies.py |
diff --git a/third_party/google-endpoints/future/backports/http/cookies.py b/third_party/google-endpoints/future/backports/http/cookies.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..ae32ed7e713453956541d140a987007836aa787f |
--- /dev/null |
+++ b/third_party/google-endpoints/future/backports/http/cookies.py |
@@ -0,0 +1,597 @@ |
+#### |
+# Copyright 2000 by Timothy O'Malley <timo@alum.mit.edu> |
+# |
+# All Rights Reserved |
+# |
+# Permission to use, copy, modify, and distribute this software |
+# and its documentation for any purpose and without fee is hereby |
+# granted, provided that the above copyright notice appear in all |
+# copies and that both that copyright notice and this permission |
+# notice appear in supporting documentation, and that the name of |
+# Timothy O'Malley not be used in advertising or publicity |
+# pertaining to distribution of the software without specific, written |
+# prior permission. |
+# |
+# Timothy O'Malley DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS |
+# SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY |
+# AND FITNESS, IN NO EVENT SHALL Timothy O'Malley BE LIABLE FOR |
+# ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, |
+# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS |
+# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR |
+# PERFORMANCE OF THIS SOFTWARE. |
+# |
+#### |
+# |
+# Id: Cookie.py,v 2.29 2000/08/23 05:28:49 timo Exp |
+# by Timothy O'Malley <timo@alum.mit.edu> |
+# |
+# Cookie.py is a Python module for the handling of HTTP |
+# cookies as a Python dictionary. See RFC 2109 for more |
+# information on cookies. |
+# |
+# The original idea to treat Cookies as a dictionary came from |
+# Dave Mitchell (davem@magnet.com) in 1995, when he released the |
+# first version of nscookie.py. |
+# |
+#### |
+ |
+r""" |
+http.cookies module ported to python-future from Py3.3 |
+ |
+Here's a sample session to show how to use this module. |
+At the moment, this is the only documentation. |
+ |
+The Basics |
+---------- |
+ |
+Importing is easy... |
+ |
+ >>> from http import cookies |
+ |
+Most of the time you start by creating a cookie. |
+ |
+ >>> C = cookies.SimpleCookie() |
+ |
+Once you've created your Cookie, you can add values just as if it were |
+a dictionary. |
+ |
+ >>> C = cookies.SimpleCookie() |
+ >>> C["fig"] = "newton" |
+ >>> C["sugar"] = "wafer" |
+ >>> C.output() |
+ 'Set-Cookie: fig=newton\r\nSet-Cookie: sugar=wafer' |
+ |
+Notice that the printable representation of a Cookie is the |
+appropriate format for a Set-Cookie: header. This is the |
+default behavior. You can change the header and printed |
+attributes by using the .output() function |
+ |
+ >>> C = cookies.SimpleCookie() |
+ >>> C["rocky"] = "road" |
+ >>> C["rocky"]["path"] = "/cookie" |
+ >>> print(C.output(header="Cookie:")) |
+ Cookie: rocky=road; Path=/cookie |
+ >>> print(C.output(attrs=[], header="Cookie:")) |
+ Cookie: rocky=road |
+ |
+The load() method of a Cookie extracts cookies from a string. In a |
+CGI script, you would use this method to extract the cookies from the |
+HTTP_COOKIE environment variable. |
+ |
+ >>> C = cookies.SimpleCookie() |
+ >>> C.load("chips=ahoy; vienna=finger") |
+ >>> C.output() |
+ 'Set-Cookie: chips=ahoy\r\nSet-Cookie: vienna=finger' |
+ |
+The load() method is darn-tootin smart about identifying cookies |
+within a string. Escaped quotation marks, nested semicolons, and other |
+such trickeries do not confuse it. |
+ |
+ >>> C = cookies.SimpleCookie() |
+ >>> C.load('keebler="E=everybody; L=\\"Loves\\"; fudge=\\012;";') |
+ >>> print(C) |
+ Set-Cookie: keebler="E=everybody; L=\"Loves\"; fudge=\012;" |
+ |
+Each element of the Cookie also supports all of the RFC 2109 |
+Cookie attributes. Here's an example which sets the Path |
+attribute. |
+ |
+ >>> C = cookies.SimpleCookie() |
+ >>> C["oreo"] = "doublestuff" |
+ >>> C["oreo"]["path"] = "/" |
+ >>> print(C) |
+ Set-Cookie: oreo=doublestuff; Path=/ |
+ |
+Each dictionary element has a 'value' attribute, which gives you |
+back the value associated with the key. |
+ |
+ >>> C = cookies.SimpleCookie() |
+ >>> C["twix"] = "none for you" |
+ >>> C["twix"].value |
+ 'none for you' |
+ |
+The SimpleCookie expects that all values should be standard strings. |
+Just to be sure, SimpleCookie invokes the str() builtin to convert |
+the value to a string, when the values are set dictionary-style. |
+ |
+ >>> C = cookies.SimpleCookie() |
+ >>> C["number"] = 7 |
+ >>> C["string"] = "seven" |
+ >>> C["number"].value |
+ '7' |
+ >>> C["string"].value |
+ 'seven' |
+ >>> C.output() |
+ 'Set-Cookie: number=7\r\nSet-Cookie: string=seven' |
+ |
+Finis. |
+""" |
+from __future__ import unicode_literals |
+from __future__ import print_function |
+from __future__ import division |
+from __future__ import absolute_import |
+from future.builtins import chr, dict, int, str |
+from future.utils import PY2, as_native_str |
+ |
+# |
+# Import our required modules |
+# |
+import re |
+re.ASCII = 0 # for py2 compatibility |
+import string |
+ |
+__all__ = ["CookieError", "BaseCookie", "SimpleCookie"] |
+ |
+_nulljoin = ''.join |
+_semispacejoin = '; '.join |
+_spacejoin = ' '.join |
+ |
+# |
+# Define an exception visible to External modules |
+# |
+class CookieError(Exception): |
+ pass |
+ |
+ |
+# These quoting routines conform to the RFC2109 specification, which in |
+# turn references the character definitions from RFC2068. They provide |
+# a two-way quoting algorithm. Any non-text character is translated |
+# into a 4 character sequence: a forward-slash followed by the |
+# three-digit octal equivalent of the character. Any '\' or '"' is |
+# quoted with a preceeding '\' slash. |
+# |
+# These are taken from RFC2068 and RFC2109. |
+# _LegalChars is the list of chars which don't require "'s |
+# _Translator hash-table for fast quoting |
+# |
+_LegalChars = string.ascii_letters + string.digits + "!#$%&'*+-.^_`|~:" |
+_Translator = { |
+ '\000' : '\\000', '\001' : '\\001', '\002' : '\\002', |
+ '\003' : '\\003', '\004' : '\\004', '\005' : '\\005', |
+ '\006' : '\\006', '\007' : '\\007', '\010' : '\\010', |
+ '\011' : '\\011', '\012' : '\\012', '\013' : '\\013', |
+ '\014' : '\\014', '\015' : '\\015', '\016' : '\\016', |
+ '\017' : '\\017', '\020' : '\\020', '\021' : '\\021', |
+ '\022' : '\\022', '\023' : '\\023', '\024' : '\\024', |
+ '\025' : '\\025', '\026' : '\\026', '\027' : '\\027', |
+ '\030' : '\\030', '\031' : '\\031', '\032' : '\\032', |
+ '\033' : '\\033', '\034' : '\\034', '\035' : '\\035', |
+ '\036' : '\\036', '\037' : '\\037', |
+ |
+ # Because of the way browsers really handle cookies (as opposed |
+ # to what the RFC says) we also encode , and ; |
+ |
+ ',' : '\\054', ';' : '\\073', |
+ |
+ '"' : '\\"', '\\' : '\\\\', |
+ |
+ '\177' : '\\177', '\200' : '\\200', '\201' : '\\201', |
+ '\202' : '\\202', '\203' : '\\203', '\204' : '\\204', |
+ '\205' : '\\205', '\206' : '\\206', '\207' : '\\207', |
+ '\210' : '\\210', '\211' : '\\211', '\212' : '\\212', |
+ '\213' : '\\213', '\214' : '\\214', '\215' : '\\215', |
+ '\216' : '\\216', '\217' : '\\217', '\220' : '\\220', |
+ '\221' : '\\221', '\222' : '\\222', '\223' : '\\223', |
+ '\224' : '\\224', '\225' : '\\225', '\226' : '\\226', |
+ '\227' : '\\227', '\230' : '\\230', '\231' : '\\231', |
+ '\232' : '\\232', '\233' : '\\233', '\234' : '\\234', |
+ '\235' : '\\235', '\236' : '\\236', '\237' : '\\237', |
+ '\240' : '\\240', '\241' : '\\241', '\242' : '\\242', |
+ '\243' : '\\243', '\244' : '\\244', '\245' : '\\245', |
+ '\246' : '\\246', '\247' : '\\247', '\250' : '\\250', |
+ '\251' : '\\251', '\252' : '\\252', '\253' : '\\253', |
+ '\254' : '\\254', '\255' : '\\255', '\256' : '\\256', |
+ '\257' : '\\257', '\260' : '\\260', '\261' : '\\261', |
+ '\262' : '\\262', '\263' : '\\263', '\264' : '\\264', |
+ '\265' : '\\265', '\266' : '\\266', '\267' : '\\267', |
+ '\270' : '\\270', '\271' : '\\271', '\272' : '\\272', |
+ '\273' : '\\273', '\274' : '\\274', '\275' : '\\275', |
+ '\276' : '\\276', '\277' : '\\277', '\300' : '\\300', |
+ '\301' : '\\301', '\302' : '\\302', '\303' : '\\303', |
+ '\304' : '\\304', '\305' : '\\305', '\306' : '\\306', |
+ '\307' : '\\307', '\310' : '\\310', '\311' : '\\311', |
+ '\312' : '\\312', '\313' : '\\313', '\314' : '\\314', |
+ '\315' : '\\315', '\316' : '\\316', '\317' : '\\317', |
+ '\320' : '\\320', '\321' : '\\321', '\322' : '\\322', |
+ '\323' : '\\323', '\324' : '\\324', '\325' : '\\325', |
+ '\326' : '\\326', '\327' : '\\327', '\330' : '\\330', |
+ '\331' : '\\331', '\332' : '\\332', '\333' : '\\333', |
+ '\334' : '\\334', '\335' : '\\335', '\336' : '\\336', |
+ '\337' : '\\337', '\340' : '\\340', '\341' : '\\341', |
+ '\342' : '\\342', '\343' : '\\343', '\344' : '\\344', |
+ '\345' : '\\345', '\346' : '\\346', '\347' : '\\347', |
+ '\350' : '\\350', '\351' : '\\351', '\352' : '\\352', |
+ '\353' : '\\353', '\354' : '\\354', '\355' : '\\355', |
+ '\356' : '\\356', '\357' : '\\357', '\360' : '\\360', |
+ '\361' : '\\361', '\362' : '\\362', '\363' : '\\363', |
+ '\364' : '\\364', '\365' : '\\365', '\366' : '\\366', |
+ '\367' : '\\367', '\370' : '\\370', '\371' : '\\371', |
+ '\372' : '\\372', '\373' : '\\373', '\374' : '\\374', |
+ '\375' : '\\375', '\376' : '\\376', '\377' : '\\377' |
+ } |
+ |
+def _quote(str, LegalChars=_LegalChars): |
+ r"""Quote a string for use in a cookie header. |
+ |
+ If the string does not need to be double-quoted, then just return the |
+ string. Otherwise, surround the string in doublequotes and quote |
+ (with a \) special characters. |
+ """ |
+ if all(c in LegalChars for c in str): |
+ return str |
+ else: |
+ return '"' + _nulljoin(_Translator.get(s, s) for s in str) + '"' |
+ |
+ |
+_OctalPatt = re.compile(r"\\[0-3][0-7][0-7]") |
+_QuotePatt = re.compile(r"[\\].") |
+ |
+def _unquote(mystr): |
+ # If there aren't any doublequotes, |
+ # then there can't be any special characters. See RFC 2109. |
+ if len(mystr) < 2: |
+ return mystr |
+ if mystr[0] != '"' or mystr[-1] != '"': |
+ return mystr |
+ |
+ # We have to assume that we must decode this string. |
+ # Down to work. |
+ |
+ # Remove the "s |
+ mystr = mystr[1:-1] |
+ |
+ # Check for special sequences. Examples: |
+ # \012 --> \n |
+ # \" --> " |
+ # |
+ i = 0 |
+ n = len(mystr) |
+ res = [] |
+ while 0 <= i < n: |
+ o_match = _OctalPatt.search(mystr, i) |
+ q_match = _QuotePatt.search(mystr, i) |
+ if not o_match and not q_match: # Neither matched |
+ res.append(mystr[i:]) |
+ break |
+ # else: |
+ j = k = -1 |
+ if o_match: |
+ j = o_match.start(0) |
+ if q_match: |
+ k = q_match.start(0) |
+ if q_match and (not o_match or k < j): # QuotePatt matched |
+ res.append(mystr[i:k]) |
+ res.append(mystr[k+1]) |
+ i = k + 2 |
+ else: # OctalPatt matched |
+ res.append(mystr[i:j]) |
+ res.append(chr(int(mystr[j+1:j+4], 8))) |
+ i = j + 4 |
+ return _nulljoin(res) |
+ |
+# The _getdate() routine is used to set the expiration time in the cookie's HTTP |
+# header. By default, _getdate() returns the current time in the appropriate |
+# "expires" format for a Set-Cookie header. The one optional argument is an |
+# offset from now, in seconds. For example, an offset of -3600 means "one hour |
+# ago". The offset may be a floating point number. |
+# |
+ |
+_weekdayname = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] |
+ |
+_monthname = [None, |
+ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', |
+ 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] |
+ |
+def _getdate(future=0, weekdayname=_weekdayname, monthname=_monthname): |
+ from time import gmtime, time |
+ now = time() |
+ year, month, day, hh, mm, ss, wd, y, z = gmtime(now + future) |
+ return "%s, %02d %3s %4d %02d:%02d:%02d GMT" % \ |
+ (weekdayname[wd], day, monthname[month], year, hh, mm, ss) |
+ |
+ |
+class Morsel(dict): |
+ """A class to hold ONE (key, value) pair. |
+ |
+ In a cookie, each such pair may have several attributes, so this class is |
+ used to keep the attributes associated with the appropriate key,value pair. |
+ This class also includes a coded_value attribute, which is used to hold |
+ the network representation of the value. This is most useful when Python |
+ objects are pickled for network transit. |
+ """ |
+ # RFC 2109 lists these attributes as reserved: |
+ # path comment domain |
+ # max-age secure version |
+ # |
+ # For historical reasons, these attributes are also reserved: |
+ # expires |
+ # |
+ # This is an extension from Microsoft: |
+ # httponly |
+ # |
+ # This dictionary provides a mapping from the lowercase |
+ # variant on the left to the appropriate traditional |
+ # formatting on the right. |
+ _reserved = { |
+ "expires" : "expires", |
+ "path" : "Path", |
+ "comment" : "Comment", |
+ "domain" : "Domain", |
+ "max-age" : "Max-Age", |
+ "secure" : "secure", |
+ "httponly" : "httponly", |
+ "version" : "Version", |
+ } |
+ |
+ _flags = set(['secure', 'httponly']) |
+ |
+ def __init__(self): |
+ # Set defaults |
+ self.key = self.value = self.coded_value = None |
+ |
+ # Set default attributes |
+ for key in self._reserved: |
+ dict.__setitem__(self, key, "") |
+ |
+ def __setitem__(self, K, V): |
+ K = K.lower() |
+ if not K in self._reserved: |
+ raise CookieError("Invalid Attribute %s" % K) |
+ dict.__setitem__(self, K, V) |
+ |
+ def isReservedKey(self, K): |
+ return K.lower() in self._reserved |
+ |
+ def set(self, key, val, coded_val, LegalChars=_LegalChars): |
+ # First we verify that the key isn't a reserved word |
+ # Second we make sure it only contains legal characters |
+ if key.lower() in self._reserved: |
+ raise CookieError("Attempt to set a reserved key: %s" % key) |
+ if any(c not in LegalChars for c in key): |
+ raise CookieError("Illegal key value: %s" % key) |
+ |
+ # It's a good key, so save it. |
+ self.key = key |
+ self.value = val |
+ self.coded_value = coded_val |
+ |
+ def output(self, attrs=None, header="Set-Cookie:"): |
+ return "%s %s" % (header, self.OutputString(attrs)) |
+ |
+ __str__ = output |
+ |
+ @as_native_str() |
+ def __repr__(self): |
+ if PY2 and isinstance(self.value, unicode): |
+ val = str(self.value) # make it a newstr to remove the u prefix |
+ else: |
+ val = self.value |
+ return '<%s: %s=%s>' % (self.__class__.__name__, |
+ str(self.key), repr(val)) |
+ |
+ def js_output(self, attrs=None): |
+ # Print javascript |
+ return """ |
+ <script type="text/javascript"> |
+ <!-- begin hiding |
+ document.cookie = \"%s\"; |
+ // end hiding --> |
+ </script> |
+ """ % (self.OutputString(attrs).replace('"', r'\"')) |
+ |
+ def OutputString(self, attrs=None): |
+ # Build up our result |
+ # |
+ result = [] |
+ append = result.append |
+ |
+ # First, the key=value pair |
+ append("%s=%s" % (self.key, self.coded_value)) |
+ |
+ # Now add any defined attributes |
+ if attrs is None: |
+ attrs = self._reserved |
+ items = sorted(self.items()) |
+ for key, value in items: |
+ if value == "": |
+ continue |
+ if key not in attrs: |
+ continue |
+ if key == "expires" and isinstance(value, int): |
+ append("%s=%s" % (self._reserved[key], _getdate(value))) |
+ elif key == "max-age" and isinstance(value, int): |
+ append("%s=%d" % (self._reserved[key], value)) |
+ elif key == "secure": |
+ append(str(self._reserved[key])) |
+ elif key == "httponly": |
+ append(str(self._reserved[key])) |
+ else: |
+ append("%s=%s" % (self._reserved[key], value)) |
+ |
+ # Return the result |
+ return _semispacejoin(result) |
+ |
+ |
+# |
+# Pattern for finding cookie |
+# |
+# This used to be strict parsing based on the RFC2109 and RFC2068 |
+# specifications. I have since discovered that MSIE 3.0x doesn't |
+# follow the character rules outlined in those specs. As a |
+# result, the parsing rules here are less strict. |
+# |
+ |
+_LegalCharsPatt = r"[\w\d!#%&'~_`><@,:/\$\*\+\-\.\^\|\)\(\?\}\{\=]" |
+_CookiePattern = re.compile(r""" |
+ (?x) # This is a verbose pattern |
+ (?P<key> # Start of group 'key' |
+ """ + _LegalCharsPatt + r"""+? # Any word of at least one letter |
+ ) # End of group 'key' |
+ ( # Optional group: there may not be a value. |
+ \s*=\s* # Equal Sign |
+ (?P<val> # Start of group 'val' |
+ "(?:[^\\"]|\\.)*" # Any doublequoted string |
+ | # or |
+ \w{3},\s[\w\d\s-]{9,11}\s[\d:]{8}\sGMT # Special case for "expires" attr |
+ | # or |
+ """ + _LegalCharsPatt + r"""* # Any word or empty string |
+ ) # End of group 'val' |
+ )? # End of optional value group |
+ \s* # Any number of spaces. |
+ (\s+|;|$) # Ending either at space, semicolon, or EOS. |
+ """, re.ASCII) # May be removed if safe. |
+ |
+ |
+# At long last, here is the cookie class. Using this class is almost just like |
+# using a dictionary. See this module's docstring for example usage. |
+# |
+class BaseCookie(dict): |
+ """A container class for a set of Morsels.""" |
+ |
+ def value_decode(self, val): |
+ """real_value, coded_value = value_decode(STRING) |
+ Called prior to setting a cookie's value from the network |
+ representation. The VALUE is the value read from HTTP |
+ header. |
+ Override this function to modify the behavior of cookies. |
+ """ |
+ return val, val |
+ |
+ def value_encode(self, val): |
+ """real_value, coded_value = value_encode(VALUE) |
+ Called prior to setting a cookie's value from the dictionary |
+ representation. The VALUE is the value being assigned. |
+ Override this function to modify the behavior of cookies. |
+ """ |
+ strval = str(val) |
+ return strval, strval |
+ |
+ def __init__(self, input=None): |
+ if input: |
+ self.load(input) |
+ |
+ def __set(self, key, real_value, coded_value): |
+ """Private method for setting a cookie's value""" |
+ M = self.get(key, Morsel()) |
+ M.set(key, real_value, coded_value) |
+ dict.__setitem__(self, key, M) |
+ |
+ def __setitem__(self, key, value): |
+ """Dictionary style assignment.""" |
+ rval, cval = self.value_encode(value) |
+ self.__set(key, rval, cval) |
+ |
+ def output(self, attrs=None, header="Set-Cookie:", sep="\015\012"): |
+ """Return a string suitable for HTTP.""" |
+ result = [] |
+ items = sorted(self.items()) |
+ for key, value in items: |
+ result.append(value.output(attrs, header)) |
+ return sep.join(result) |
+ |
+ __str__ = output |
+ |
+ @as_native_str() |
+ def __repr__(self): |
+ l = [] |
+ items = sorted(self.items()) |
+ for key, value in items: |
+ if PY2 and isinstance(value.value, unicode): |
+ val = str(value.value) # make it a newstr to remove the u prefix |
+ else: |
+ val = value.value |
+ l.append('%s=%s' % (str(key), repr(val))) |
+ return '<%s: %s>' % (self.__class__.__name__, _spacejoin(l)) |
+ |
+ def js_output(self, attrs=None): |
+ """Return a string suitable for JavaScript.""" |
+ result = [] |
+ items = sorted(self.items()) |
+ for key, value in items: |
+ result.append(value.js_output(attrs)) |
+ return _nulljoin(result) |
+ |
+ def load(self, rawdata): |
+ """Load cookies from a string (presumably HTTP_COOKIE) or |
+ from a dictionary. Loading cookies from a dictionary 'd' |
+ is equivalent to calling: |
+ map(Cookie.__setitem__, d.keys(), d.values()) |
+ """ |
+ if isinstance(rawdata, str): |
+ self.__parse_string(rawdata) |
+ else: |
+ # self.update() wouldn't call our custom __setitem__ |
+ for key, value in rawdata.items(): |
+ self[key] = value |
+ return |
+ |
+ def __parse_string(self, mystr, patt=_CookiePattern): |
+ i = 0 # Our starting point |
+ n = len(mystr) # Length of string |
+ M = None # current morsel |
+ |
+ while 0 <= i < n: |
+ # Start looking for a cookie |
+ match = patt.search(mystr, i) |
+ if not match: |
+ # No more cookies |
+ break |
+ |
+ key, value = match.group("key"), match.group("val") |
+ |
+ i = match.end(0) |
+ |
+ # Parse the key, value in case it's metainfo |
+ if key[0] == "$": |
+ # We ignore attributes which pertain to the cookie |
+ # mechanism as a whole. See RFC 2109. |
+ # (Does anyone care?) |
+ if M: |
+ M[key[1:]] = value |
+ elif key.lower() in Morsel._reserved: |
+ if M: |
+ if value is None: |
+ if key.lower() in Morsel._flags: |
+ M[key] = True |
+ else: |
+ M[key] = _unquote(value) |
+ elif value is not None: |
+ rval, cval = self.value_decode(value) |
+ self.__set(key, rval, cval) |
+ M = self[key] |
+ |
+ |
+class SimpleCookie(BaseCookie): |
+ """ |
+ SimpleCookie supports strings as cookie values. When setting |
+ the value using the dictionary assignment notation, SimpleCookie |
+ calls the builtin str() to convert the value to a string. Values |
+ received from HTTP are kept as strings. |
+ """ |
+ def value_decode(self, val): |
+ return _unquote(val), val |
+ |
+ def value_encode(self, val): |
+ strval = str(val) |
+ return strval, _quote(strval) |