FazBrowse GitHub Viewer
|
Trending
|
URL:
|
Home
Tools:
[Download Repo ZIP]
[View Raw Code]
[Original HTTPS Page]
Source.Python/addons/source-python/Python3/email/utils.py at main · CookStar/Source.Python · GitHub
CookStar
Source.Python
Repository navigation
Code
Issues
Pull requests
Actions
Projects
Security and quality
Insights
Expand file tree
Breadcrumbs
Source.Python
/
addons
/
source-python
/
Python3
/
email
/
utils.py
Copy path
More file actions
More file actions
Latest commit
History
History
History
382 lines (325 loc) · 13.4 KB
Breadcrumbs
Source.Python
/
addons
/
source-python
/
Python3
/
email
/
utils.py
Copy path
File metadata and controls
382 lines (325 loc) · 13.4 KB
Raw
Copy raw file
Download raw file
Open symbols panel
Edit and raw actions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
# Copyright (C) 2001-2010 Python Software Foundation
# Author: Barry Warsaw
# Contact: email-sig@python.org
"""Miscellaneous utilities."""
__all__
=
[
'collapse_rfc2231_value'
,
'decode_params'
,
'decode_rfc2231'
,
'encode_rfc2231'
,
'formataddr'
,
'formatdate'
,
'format_datetime'
,
'getaddresses'
,
'make_msgid'
,
'mktime_tz'
,
'parseaddr'
,
'parsedate'
,
'parsedate_tz'
,
'parsedate_to_datetime'
,
'unquote'
,
]
import
os
import
re
import
time
import
random
import
socket
import
datetime
import
urllib
.
parse
from
email
.
_parseaddr
import
quote
from
email
.
_parseaddr
import
AddressList
as
_AddressList
from
email
.
_parseaddr
import
mktime_tz
from
email
.
_parseaddr
import
parsedate
,
parsedate_tz
,
_parsedate_tz
# Intrapackage imports
from
email
.
charset
import
Charset
COMMASPACE
=
', '
EMPTYSTRING
=
''
UEMPTYSTRING
=
''
CRLF
=
'
\r
\n
'
TICK
=
"'"
specialsre
=
re
.
compile
(
r'[][\\()<>@,:;".]'
)
escapesre
=
re
.
compile
(
r'[\\"]'
)
def
_has_surrogates
(
s
):
"""Return True if s contains surrogate-escaped binary data."""
# This check is based on the fact that unless there are surrogates, utf8
# (Python's default encoding) can encode any string. This is the fastest
# way to check for surrogates, see issue 11454 for timings.
try
:
s
.
encode
()
return
False
except
UnicodeEncodeError
:
return
True
# How to deal with a string containing bytes before handing it to the
# application through the 'normal' interface.
def
_sanitize
(
string
):
# Turn any escaped bytes into unicode 'unknown' char. If the escaped
# bytes happen to be utf-8 they will instead get decoded, even if they
# were invalid in the charset the source was supposed to be in. This
# seems like it is not a bad thing; a defect was still registered.
original_bytes
=
string
.
encode
(
'utf-8'
,
'surrogateescape'
)
return
original_bytes
.
decode
(
'utf-8'
,
'replace'
)
# Helpers
def
formataddr
(
pair
,
charset
=
'utf-8'
):
"""The inverse of parseaddr(), this takes a 2-tuple of the form
(realname, email_address) and returns the string value suitable
for an RFC 2822 From, To or Cc header.
If the first element of pair is false, then the second element is
returned unmodified.
Optional charset if given is the character set that is used to encode
realname in case realname is not ASCII safe. Can be an instance of str or
a Charset-like object which has a header_encode method. Default is
'utf-8'.
"""
name
,
address
=
pair
# The address MUST (per RFC) be ascii, so raise a UnicodeError if it isn't.
address
.
encode
(
'ascii'
)
if
name
:
try
:
name
.
encode
(
'ascii'
)
except
UnicodeEncodeError
:
if
isinstance
(
charset
,
str
):
charset
=
Charset
(
charset
)
encoded_name
=
charset
.
header_encode
(
name
)
return
"%s <%s>"
%
(
encoded_name
,
address
)
else
:
quotes
=
''
if
specialsre
.
search
(
name
):
quotes
=
'"'
name
=
escapesre
.
sub
(
r'\\\g<0>'
,
name
)
return
'%s%s%s <%s>'
%
(
quotes
,
name
,
quotes
,
address
)
return
address
def
getaddresses
(
fieldvalues
):
"""Return a list of (REALNAME, EMAIL) for each fieldvalue."""
all
=
COMMASPACE
.
join
(
fieldvalues
)
a
=
_AddressList
(
all
)
return
a
.
addresslist
ecre
=
re
.
compile
(
r'''
=\? # literal =?
(?P<charset>[^?]*?) # non-greedy up to the next ? is the charset
\? # literal ?
(?P<encoding>[qb]) # either a "q" or a "b", case insensitive
\? # literal ?
(?P<atom>.*?) # non-greedy up to the next ?= is the atom
\?= # literal ?=
'''
,
re
.
VERBOSE
|
re
.
IGNORECASE
)
def
_format_timetuple_and_zone
(
timetuple
,
zone
):
return
'%s, %02d %s %04d %02d:%02d:%02d %s'
%
(
[
'Mon'
,
'Tue'
,
'Wed'
,
'Thu'
,
'Fri'
,
'Sat'
,
'Sun'
][
timetuple
[
6
]],
timetuple
[
2
],
[
'Jan'
,
'Feb'
,
'Mar'
,
'Apr'
,
'May'
,
'Jun'
,
'Jul'
,
'Aug'
,
'Sep'
,
'Oct'
,
'Nov'
,
'Dec'
][
timetuple
[
1
]
-
1
],
timetuple
[
0
],
timetuple
[
3
],
timetuple
[
4
],
timetuple
[
5
],
zone
)
def
formatdate
(
timeval
=
None
,
localtime
=
False
,
usegmt
=
False
):
"""Returns a date string as specified by RFC 2822, e.g.:
Fri, 09 Nov 2001 01:08:47 -0000
Optional timeval if given is a floating point time value as accepted by
gmtime() and localtime(), otherwise the current time is used.
Optional localtime is a flag that when True, interprets timeval, and
returns a date relative to the local timezone instead of UTC, properly
taking daylight savings time into account.
Optional argument usegmt means that the timezone is written out as
an ascii string, not numeric one (so "GMT" instead of "+0000"). This
is needed for HTTP, and is only used when localtime==False.
"""
# Note: we cannot use strftime() because that honors the locale and RFC
# 2822 requires that day and month names be the English abbreviations.
if
timeval
is
None
:
timeval
=
time
.
time
()
if
localtime
or
usegmt
:
dt
=
datetime
.
datetime
.
fromtimestamp
(
timeval
,
datetime
.
timezone
.
utc
)
else
:
dt
=
datetime
.
datetime
.
utcfromtimestamp
(
timeval
)
if
localtime
:
dt
=
dt
.
astimezone
()
usegmt
=
False
return
format_datetime
(
dt
,
usegmt
)
def
format_datetime
(
dt
,
usegmt
=
False
):
"""Turn a datetime into a date string as specified in RFC 2822.
If usegmt is True, dt must be an aware datetime with an offset of zero. In
this case 'GMT' will be rendered instead of the normal +0000 required by
RFC2822. This is to support HTTP headers involving date stamps.
"""
now
=
dt
.
timetuple
()
if
usegmt
:
if
dt
.
tzinfo
is
None
or
dt
.
tzinfo
!=
datetime
.
timezone
.
utc
:
raise
ValueError
(
"usegmt option requires a UTC datetime"
)
zone
=
'GMT'
elif
dt
.
tzinfo
is
None
:
zone
=
'-0000'
else
:
zone
=
dt
.
strftime
(
"%z"
)
return
_format_timetuple_and_zone
(
now
,
zone
)
def
make_msgid
(
idstring
=
None
,
domain
=
None
):
"""Returns a string suitable for RFC 2822 compliant Message-ID, e.g:
<142480216486.20800.16526388040877946887@nightshade.la.mastaler.com>
Optional idstring if given is a string used to strengthen the
uniqueness of the message id. Optional domain if given provides the
portion of the message id after the '@'. It defaults to the locally
defined hostname.
"""
timeval
=
int
(
time
.
time
()
*
100
)
pid
=
os
.
getpid
()
randint
=
random
.
getrandbits
(
64
)
if
idstring
is
None
:
idstring
=
''
else
:
idstring
=
'.'
+
idstring
if
domain
is
None
:
domain
=
socket
.
getfqdn
()
msgid
=
'<%d.%d.%d%s@%s>'
%
(
timeval
,
pid
,
randint
,
idstring
,
domain
)
return
msgid
def
parsedate_to_datetime
(
data
):
*
dtuple
,
tz
=
_parsedate_tz
(
data
)
if
tz
is
None
:
return
datetime
.
datetime
(
*
dtuple
[:
6
])
return
datetime
.
datetime
(
*
dtuple
[:
6
],
tzinfo
=
datetime
.
timezone
(
datetime
.
timedelta
(
seconds
=
tz
)))
def
parseaddr
(
addr
):
addrs
=
_AddressList
(
addr
).
addresslist
if
not
addrs
:
return
''
,
''
return
addrs
[
0
]
# rfc822.unquote() doesn't properly de-backslash-ify in Python pre-2.3.
def
unquote
(
str
):
"""Remove quotes from a string."""
if
len
(
str
)
>
1
:
if
str
.
startswith
(
'"'
)
and
str
.
endswith
(
'"'
):
return
str
[
1
:
-
1
].
replace
(
'
\\
\\
'
,
'
\\
'
).
replace
(
'
\\
"'
,
'"'
)
if
str
.
startswith
(
'<'
)
and
str
.
endswith
(
'>'
):
return
str
[
1
:
-
1
]
return
str
# RFC2231-related functions - parameter encoding and decoding
def
decode_rfc2231
(
s
):
"""Decode string according to RFC 2231"""
parts
=
s
.
split
(
TICK
,
2
)
if
len
(
parts
)
<=
2
:
return
None
,
None
,
s
return
parts
def
encode_rfc2231
(
s
,
charset
=
None
,
language
=
None
):
"""Encode string according to RFC 2231.
If neither charset nor language is given, then s is returned as-is. If
charset is given but not language, the string is encoded using the empty
string for language.
"""
s
=
urllib
.
parse
.
quote
(
s
,
safe
=
''
,
encoding
=
charset
or
'ascii'
)
if
charset
is
None
and
language
is
None
:
return
s
if
language
is
None
:
language
=
''
return
"%s'%s'%s"
%
(
charset
,
language
,
s
)
rfc2231_continuation
=
re
.
compile
(
r'^(?P<name>\w+)\*((?P<num>[0-9]+)\*?)?$'
,
re
.
ASCII
)
def
decode_params
(
params
):
"""Decode parameters list according to RFC 2231.
params is a sequence of 2-tuples containing (param name, string value).
"""
# Copy params so we don't mess with the original
params
=
params
[:]
new_params
=
[]
# Map parameter's name to a list of continuations. The values are a
# 3-tuple of the continuation number, the string value, and a flag
# specifying whether a particular segment is %-encoded.
rfc2231_params
=
{}
name
,
value
=
params
.
pop
(
0
)
new_params
.
append
((
name
,
value
))
while
params
:
name
,
value
=
params
.
pop
(
0
)
if
name
.
endswith
(
'*'
):
encoded
=
True
else
:
encoded
=
False
value
=
unquote
(
value
)
mo
=
rfc2231_continuation
.
match
(
name
)
if
mo
:
name
,
num
=
mo
.
group
(
'name'
,
'num'
)
if
num
is
not
None
:
num
=
int
(
num
)
rfc2231_params
.
setdefault
(
name
, []).
append
((
num
,
value
,
encoded
))
else
:
new_params
.
append
((
name
,
'"%s"'
%
quote
(
value
)))
if
rfc2231_params
:
for
name
,
continuations
in
rfc2231_params
.
items
():
value
=
[]
extended
=
False
# Sort by number
continuations
.
sort
()
# And now append all values in numerical order, converting
# %-encodings for the encoded segments. If any of the
# continuation names ends in a *, then the entire string, after
# decoding segments and concatenating, must have the charset and
# language specifiers at the beginning of the string.
for
num
,
s
,
encoded
in
continuations
:
if
encoded
:
# Decode as "latin-1", so the characters in s directly
# represent the percent-encoded octet values.
# collapse_rfc2231_value treats this as an octet sequence.
s
=
urllib
.
parse
.
unquote
(
s
,
encoding
=
"latin-1"
)
extended
=
True
value
.
append
(
s
)
value
=
quote
(
EMPTYSTRING
.
join
(
value
))
if
extended
:
charset
,
language
,
value
=
decode_rfc2231
(
value
)
new_params
.
append
((
name
, (
charset
,
language
,
'"%s"'
%
value
)))
else
:
new_params
.
append
((
name
,
'"%s"'
%
value
))
return
new_params
def
collapse_rfc2231_value
(
value
,
errors
=
'replace'
,
fallback_charset
=
'us-ascii'
):
if
not
isinstance
(
value
,
tuple
)
or
len
(
value
)
!=
3
:
return
unquote
(
value
)
# While value comes to us as a unicode string, we need it to be a bytes
# object. We do not want bytes() normal utf-8 decoder, we want a straight
# interpretation of the string as character bytes.
charset
,
language
,
text
=
value
if
charset
is
None
:
# Issue 17369: if charset/lang is None, decode_rfc2231 couldn't parse
# the value, so use the fallback_charset.
charset
=
fallback_charset
rawbytes
=
bytes
(
text
,
'raw-unicode-escape'
)
try
:
return
str
(
rawbytes
,
charset
,
errors
)
except
LookupError
:
# charset is not a known codec.
return
unquote
(
text
)
#
# datetime doesn't provide a localtime function yet, so provide one. Code
# adapted from the patch in issue 9527. This may not be perfect, but it is
# better than not having it.
#
def
localtime
(
dt
=
None
,
isdst
=
-
1
):
"""Return local time as an aware datetime object.
If called without arguments, return current time. Otherwise *dt*
argument should be a datetime instance, and it is converted to the
local time zone according to the system time zone database. If *dt* is
naive (that is, dt.tzinfo is None), it is assumed to be in local time.
In this case, a positive or zero value for *isdst* causes localtime to
presume initially that summer time (for example, Daylight Saving Time)
is or is not (respectively) in effect for the specified time. A
negative value for *isdst* causes the localtime() function to attempt
to divine whether summer time is in effect for the specified time.
"""
if
dt
is
None
:
return
datetime
.
datetime
.
now
(
datetime
.
timezone
.
utc
).
astimezone
()
if
dt
.
tzinfo
is
not
None
:
return
dt
.
astimezone
()
# We have a naive datetime. Convert to a (localtime) timetuple and pass to
# system mktime together with the isdst hint. System mktime will return
# seconds since epoch.
tm
=
dt
.
timetuple
()[:
-
1
]
+
(
isdst
,)
seconds
=
time
.
mktime
(
tm
)
localtm
=
time
.
localtime
(
seconds
)
try
:
delta
=
datetime
.
timedelta
(
seconds
=
localtm
.
tm_gmtoff
)
tz
=
datetime
.
timezone
(
delta
,
localtm
.
tm_zone
)
except
AttributeError
:
# Compute UTC offset and compare with the value implied by tm_isdst.
# If the values match, use the zone name implied by tm_isdst.
delta
=
dt
-
datetime
.
datetime
(
*
time
.
gmtime
(
seconds
)[:
6
])
dst
=
time
.
daylight
and
localtm
.
tm_isdst
>
0
gmtoff
=
-
(
time
.
altzone
if
dst
else
time
.
timezone
)
if
delta
==
datetime
.
timedelta
(
seconds
=
gmtoff
):
tz
=
datetime
.
timezone
(
delta
,
time
.
tzname
[
dst
])
else
:
tz
=
datetime
.
timezone
(
delta
)
return
dt
.
replace
(
tzinfo
=
tz
)
Back
|
FazBrowse Home
|
New Git URL