134 lines
4.4 KiB
Python
134 lines
4.4 KiB
Python
"""URL encoding helper
|
|
|
|
Mostly taken from urllib.parse (which is sadly too large to be imported directly)
|
|
|
|
I've removed most of the comment to make it easier on micropython
|
|
"""
|
|
___license___ = "Python"
|
|
___dependencies___ = ["upip:collections"]
|
|
|
|
from collections.defaultdict import defaultdict
|
|
|
|
_ALWAYS_SAFE = frozenset(b'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
|
b'abcdefghijklmnopqrstuvwxyz'
|
|
b'0123456789'
|
|
b'_.-')
|
|
_ALWAYS_SAFE_BYTES = bytes(_ALWAYS_SAFE)
|
|
|
|
_safe_quoters = {}
|
|
|
|
class Quoter(defaultdict):
|
|
def __init__(self, safe):
|
|
"""safe: bytes object."""
|
|
self.safe = _ALWAYS_SAFE.union(safe)
|
|
|
|
def __repr__(self):
|
|
# Without this, will just display as a defaultdict
|
|
return "<Quoter %r>" % dict(self)
|
|
|
|
def __missing__(self, b):
|
|
# Handle a cache miss. Store quoted string in cache and return.
|
|
res = chr(b) if b in self.safe else '%{:02X}'.format(b)
|
|
self[b] = res
|
|
return res
|
|
|
|
def quote(string, safe='/', encoding=None, errors=None):
|
|
if isinstance(string, str):
|
|
if not string:
|
|
return string
|
|
if encoding is None:
|
|
encoding = 'utf-8'
|
|
if errors is None:
|
|
errors = 'strict'
|
|
string = string.encode(encoding, errors)
|
|
else:
|
|
if encoding is not None:
|
|
raise TypeError("quote() doesn't support 'encoding' for bytes")
|
|
if errors is not None:
|
|
raise TypeError("quote() doesn't support 'errors' for bytes")
|
|
return quote_from_bytes(string, safe)
|
|
|
|
def quote_plus(string, safe='', encoding=None, errors=None):
|
|
if ((isinstance(string, str) and ' ' not in string) or
|
|
(isinstance(string, bytes) and b' ' not in string)):
|
|
return quote(string, safe, encoding, errors)
|
|
if isinstance(safe, str):
|
|
space = ' '
|
|
else:
|
|
space = b' '
|
|
string = quote(string, safe + space, encoding, errors)
|
|
return string.replace(' ', '+')
|
|
|
|
def quote_from_bytes(bs, safe='/'):
|
|
if not isinstance(bs, (bytes, bytearray)):
|
|
raise TypeError("quote_from_bytes() expected bytes")
|
|
if not bs:
|
|
return ''
|
|
if isinstance(safe, str):
|
|
safe = safe.encode('ascii', 'ignore')
|
|
else:
|
|
safe = bytes([c for c in safe if c < 128])
|
|
if not bs.rstrip(_ALWAYS_SAFE_BYTES + safe):
|
|
return bs.decode()
|
|
try:
|
|
quoter = _safe_quoters[safe]
|
|
except KeyError:
|
|
_safe_quoters[safe] = quoter = Quoter(safe).__getitem__
|
|
return ''.join([quoter(char) for char in bs])
|
|
|
|
def urlencode(query, doseq=False, safe='', encoding=None, errors=None):
|
|
if hasattr(query, "items"):
|
|
query = query.items()
|
|
else:
|
|
try:
|
|
if len(query) and not isinstance(query[0], tuple):
|
|
raise TypeError
|
|
except TypeError:
|
|
raise TypeError("not a valid non-string sequence "
|
|
"or mapping object")#.with_traceback(tb)
|
|
|
|
l = []
|
|
if not doseq:
|
|
for k, v in query:
|
|
if isinstance(k, bytes):
|
|
k = quote_plus(k, safe)
|
|
else:
|
|
k = quote_plus(str(k), safe, encoding, errors)
|
|
|
|
if isinstance(v, bytes):
|
|
v = quote_plus(v, safe)
|
|
else:
|
|
v = quote_plus(str(v), safe, encoding, errors)
|
|
l.append(k + '=' + v)
|
|
else:
|
|
for k, v in query:
|
|
if isinstance(k, bytes):
|
|
k = quote_plus(k, safe)
|
|
else:
|
|
k = quote_plus(str(k), safe, encoding, errors)
|
|
|
|
if isinstance(v, bytes):
|
|
v = quote_plus(v, safe)
|
|
l.append(k + '=' + v)
|
|
elif isinstance(v, str):
|
|
v = quote_plus(v, safe, encoding, errors)
|
|
l.append(k + '=' + v)
|
|
else:
|
|
try:
|
|
# Is this a sufficient test for sequence-ness?
|
|
x = len(v)
|
|
except TypeError:
|
|
# not a sequence
|
|
v = quote_plus(str(v), safe, encoding, errors)
|
|
l.append(k + '=' + v)
|
|
else:
|
|
# loop over the sequence
|
|
for elt in v:
|
|
if isinstance(elt, bytes):
|
|
elt = quote_plus(elt, safe)
|
|
else:
|
|
elt = quote_plus(str(elt), safe, encoding, errors)
|
|
l.append(k + '=' + elt)
|
|
return '&'.join(l)
|
|
|