|
0
|
1 |
import re |
|
|
2 |
import urllib |
|
|
3 |
from email.Utils import formatdate |
|
|
4 |
|
|
|
5 |
from django.utils.encoding import smart_str, force_unicode |
|
|
6 |
from django.utils.functional import allow_lazy |
|
|
7 |
|
|
|
8 |
ETAG_MATCH = re.compile(r'(?:W/)?"((?:\\.|[^"])*)"') |
|
|
9 |
|
|
|
10 |
def urlquote(url, safe='/'): |
|
|
11 |
""" |
|
|
12 |
A version of Python's urllib.quote() function that can operate on unicode |
|
|
13 |
strings. The url is first UTF-8 encoded before quoting. The returned string |
|
|
14 |
can safely be used as part of an argument to a subsequent iri_to_uri() call |
|
|
15 |
without double-quoting occurring. |
|
|
16 |
""" |
|
|
17 |
return force_unicode(urllib.quote(smart_str(url), safe)) |
|
|
18 |
|
|
|
19 |
urlquote = allow_lazy(urlquote, unicode) |
|
|
20 |
|
|
|
21 |
def urlquote_plus(url, safe=''): |
|
|
22 |
""" |
|
|
23 |
A version of Python's urllib.quote_plus() function that can operate on |
|
|
24 |
unicode strings. The url is first UTF-8 encoded before quoting. The |
|
|
25 |
returned string can safely be used as part of an argument to a subsequent |
|
|
26 |
iri_to_uri() call without double-quoting occurring. |
|
|
27 |
""" |
|
|
28 |
return force_unicode(urllib.quote_plus(smart_str(url), safe)) |
|
|
29 |
urlquote_plus = allow_lazy(urlquote_plus, unicode) |
|
|
30 |
|
|
|
31 |
def urlencode(query, doseq=0): |
|
|
32 |
""" |
|
|
33 |
A version of Python's urllib.urlencode() function that can operate on |
|
|
34 |
unicode strings. The parameters are first case to UTF-8 encoded strings and |
|
|
35 |
then encoded as per normal. |
|
|
36 |
""" |
|
|
37 |
if hasattr(query, 'items'): |
|
|
38 |
query = query.items() |
|
|
39 |
return urllib.urlencode( |
|
|
40 |
[(smart_str(k), |
|
|
41 |
isinstance(v, (list,tuple)) and [smart_str(i) for i in v] or smart_str(v)) |
|
|
42 |
for k, v in query], |
|
|
43 |
doseq) |
|
|
44 |
|
|
|
45 |
def cookie_date(epoch_seconds=None): |
|
|
46 |
""" |
|
|
47 |
Formats the time to ensure compatibility with Netscape's cookie standard. |
|
|
48 |
|
|
|
49 |
Accepts a floating point number expressed in seconds since the epoch, in |
|
|
50 |
UTC - such as that outputted by time.time(). If set to None, defaults to |
|
|
51 |
the current time. |
|
|
52 |
|
|
|
53 |
Outputs a string in the format 'Wdy, DD-Mon-YYYY HH:MM:SS GMT'. |
|
|
54 |
""" |
|
|
55 |
rfcdate = formatdate(epoch_seconds) |
|
|
56 |
return '%s-%s-%s GMT' % (rfcdate[:7], rfcdate[8:11], rfcdate[12:25]) |
|
|
57 |
|
|
|
58 |
def http_date(epoch_seconds=None): |
|
|
59 |
""" |
|
|
60 |
Formats the time to match the RFC1123 date format as specified by HTTP |
|
|
61 |
RFC2616 section 3.3.1. |
|
|
62 |
|
|
|
63 |
Accepts a floating point number expressed in seconds since the epoch, in |
|
|
64 |
UTC - such as that outputted by time.time(). If set to None, defaults to |
|
|
65 |
the current time. |
|
|
66 |
|
|
|
67 |
Outputs a string in the format 'Wdy, DD Mon YYYY HH:MM:SS GMT'. |
|
|
68 |
""" |
|
|
69 |
rfcdate = formatdate(epoch_seconds) |
|
|
70 |
return '%s GMT' % rfcdate[:25] |
|
|
71 |
|
|
|
72 |
# Base 36 functions: useful for generating compact URLs |
|
|
73 |
|
|
|
74 |
def base36_to_int(s): |
|
|
75 |
""" |
|
|
76 |
Convertd a base 36 string to an integer |
|
|
77 |
""" |
|
|
78 |
return int(s, 36) |
|
|
79 |
|
|
|
80 |
def int_to_base36(i): |
|
|
81 |
""" |
|
|
82 |
Converts an integer to a base36 string |
|
|
83 |
""" |
|
|
84 |
digits = "0123456789abcdefghijklmnopqrstuvwxyz" |
|
|
85 |
factor = 0 |
|
|
86 |
# Find starting factor |
|
|
87 |
while True: |
|
|
88 |
factor += 1 |
|
|
89 |
if i < 36 ** factor: |
|
|
90 |
factor -= 1 |
|
|
91 |
break |
|
|
92 |
base36 = [] |
|
|
93 |
# Construct base36 representation |
|
|
94 |
while factor >= 0: |
|
|
95 |
j = 36 ** factor |
|
|
96 |
base36.append(digits[i / j]) |
|
|
97 |
i = i % j |
|
|
98 |
factor -= 1 |
|
|
99 |
return ''.join(base36) |
|
|
100 |
|
|
|
101 |
def parse_etags(etag_str): |
|
|
102 |
""" |
|
|
103 |
Parses a string with one or several etags passed in If-None-Match and |
|
|
104 |
If-Match headers by the rules in RFC 2616. Returns a list of etags |
|
|
105 |
without surrounding double quotes (") and unescaped from \<CHAR>. |
|
|
106 |
""" |
|
|
107 |
etags = ETAG_MATCH.findall(etag_str) |
|
|
108 |
if not etags: |
|
|
109 |
# etag_str has wrong format, treat it as an opaque string then |
|
|
110 |
return [etag_str] |
|
|
111 |
etags = [e.decode('string_escape') for e in etags] |
|
|
112 |
return etags |
|
|
113 |
|
|
|
114 |
def quote_etag(etag): |
|
|
115 |
""" |
|
|
116 |
Wraps a string in double quotes escaping contents as necesary. |
|
|
117 |
""" |
|
|
118 |
return '"%s"' % etag.replace('\\', '\\\\').replace('"', '\\"') |
|
|
119 |
|