Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitattributes +2 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/curses/__init__.py +113 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/curses/ascii.py +99 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/curses/has_key.py +192 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/curses/panel.py +6 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/dbm/__init__.py +189 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/dbm/dumb.py +316 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/dbm/gnu.py +3 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/dbm/ndbm.py +3 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/email/__init__.py +62 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/email/_encoded_words.py +233 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/email/_header_value_parser.py +0 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/email/_parseaddr.py +551 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/email/_policybase.py +374 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/email/architecture.rst +216 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/email/base64mime.py +119 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/email/charset.py +404 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/email/contentmanager.py +250 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/email/encoders.py +69 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/email/errors.py +110 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/email/feedparser.py +536 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/email/generator.py +512 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/email/header.py +578 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/email/headerregistry.py +607 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/email/iterators.py +71 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/email/message.py +1173 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/email/parser.py +131 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/email/policy.py +224 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/email/quoprimime.py +299 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/email/utils.py +376 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/json/__init__.py +370 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/json/decoder.py +356 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/json/encoder.py +442 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/json/scanner.py +73 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/json/tool.py +58 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/sqlite3/dbapi2.py +89 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/test/__init__.py +1 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/test/test_script_helper.py +125 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/test/test_support.py +689 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/turtledemo/__init__.py +14 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/turtledemo/__main__.py +386 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/turtledemo/bytedesign.py +161 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/turtledemo/chaos.py +59 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/turtledemo/clock.py +132 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/turtledemo/colormixer.py +58 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/turtledemo/forest.py +108 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/turtledemo/lindenmayer.py +119 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/turtledemo/minimal_hanoi.py +79 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/turtledemo/paint.py +54 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/turtledemo/peace.py +61 -0
.gitattributes
CHANGED
|
@@ -170,3 +170,5 @@ my_container_sandbox/workspace/anaconda3/pkgs/libgomp-11.2.0-h1234567_1.conda fi
|
|
| 170 |
my_container_sandbox/workspace/anaconda3/pkgs/tzdata-2021a-h52ac0ba_0.conda filter=lfs diff=lfs merge=lfs -text
|
| 171 |
my_container_sandbox/workspace/anaconda3/pkgs/ca-certificates-2024.2.2-hbcca054_0.conda filter=lfs diff=lfs merge=lfs -text
|
| 172 |
my_container_sandbox/workspace/anaconda3/pkgs/ncurses-6.4-h6a678d5_0.conda filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
| 170 |
my_container_sandbox/workspace/anaconda3/pkgs/tzdata-2021a-h52ac0ba_0.conda filter=lfs diff=lfs merge=lfs -text
|
| 171 |
my_container_sandbox/workspace/anaconda3/pkgs/ca-certificates-2024.2.2-hbcca054_0.conda filter=lfs diff=lfs merge=lfs -text
|
| 172 |
my_container_sandbox/workspace/anaconda3/pkgs/ncurses-6.4-h6a678d5_0.conda filter=lfs diff=lfs merge=lfs -text
|
| 173 |
+
my_container_sandbox/workspace/anaconda3/pkgs/ca-certificates-2021.7.5-h06a4308_1.conda filter=lfs diff=lfs merge=lfs -text
|
| 174 |
+
my_container_sandbox/workspace/anaconda3/pkgs/conda-package-handling-2.2.0-pyh38be061_0.conda filter=lfs diff=lfs merge=lfs -text
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/curses/__init__.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""curses
|
| 2 |
+
|
| 3 |
+
The main package for curses support for Python. Normally used by importing
|
| 4 |
+
the package, and perhaps a particular module inside it.
|
| 5 |
+
|
| 6 |
+
import curses
|
| 7 |
+
from curses import textpad
|
| 8 |
+
curses.initscr()
|
| 9 |
+
...
|
| 10 |
+
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
from _curses import *
|
| 14 |
+
import os as _os
|
| 15 |
+
import sys as _sys
|
| 16 |
+
|
| 17 |
+
# Some constants, most notably the ACS_* ones, are only added to the C
|
| 18 |
+
# _curses module's dictionary after initscr() is called. (Some
|
| 19 |
+
# versions of SGI's curses don't define values for those constants
|
| 20 |
+
# until initscr() has been called.) This wrapper function calls the
|
| 21 |
+
# underlying C initscr(), and then copies the constants from the
|
| 22 |
+
# _curses module to the curses package's dictionary. Don't do 'from
|
| 23 |
+
# curses import *' if you'll be needing the ACS_* constants.
|
| 24 |
+
|
| 25 |
+
def initscr():
|
| 26 |
+
import _curses, curses
|
| 27 |
+
# we call setupterm() here because it raises an error
|
| 28 |
+
# instead of calling exit() in error cases.
|
| 29 |
+
setupterm(term=_os.environ.get("TERM", "unknown"),
|
| 30 |
+
fd=_sys.__stdout__.fileno())
|
| 31 |
+
stdscr = _curses.initscr()
|
| 32 |
+
for key, value in _curses.__dict__.items():
|
| 33 |
+
if key[0:4] == 'ACS_' or key in ('LINES', 'COLS'):
|
| 34 |
+
setattr(curses, key, value)
|
| 35 |
+
|
| 36 |
+
return stdscr
|
| 37 |
+
|
| 38 |
+
# This is a similar wrapper for start_color(), which adds the COLORS and
|
| 39 |
+
# COLOR_PAIRS variables which are only available after start_color() is
|
| 40 |
+
# called.
|
| 41 |
+
|
| 42 |
+
def start_color():
|
| 43 |
+
import _curses, curses
|
| 44 |
+
retval = _curses.start_color()
|
| 45 |
+
if hasattr(_curses, 'COLORS'):
|
| 46 |
+
curses.COLORS = _curses.COLORS
|
| 47 |
+
if hasattr(_curses, 'COLOR_PAIRS'):
|
| 48 |
+
curses.COLOR_PAIRS = _curses.COLOR_PAIRS
|
| 49 |
+
return retval
|
| 50 |
+
|
| 51 |
+
# Import Python has_key() implementation if _curses doesn't contain has_key()
|
| 52 |
+
|
| 53 |
+
try:
|
| 54 |
+
has_key
|
| 55 |
+
except NameError:
|
| 56 |
+
from .has_key import has_key
|
| 57 |
+
|
| 58 |
+
# Wrapper for the entire curses-based application. Runs a function which
|
| 59 |
+
# should be the rest of your curses-based application. If the application
|
| 60 |
+
# raises an exception, wrapper() will restore the terminal to a sane state so
|
| 61 |
+
# you can read the resulting traceback.
|
| 62 |
+
|
| 63 |
+
def wrapper(*args, **kwds):
|
| 64 |
+
"""Wrapper function that initializes curses and calls another function,
|
| 65 |
+
restoring normal keyboard/screen behavior on error.
|
| 66 |
+
The callable object 'func' is then passed the main window 'stdscr'
|
| 67 |
+
as its first argument, followed by any other arguments passed to
|
| 68 |
+
wrapper().
|
| 69 |
+
"""
|
| 70 |
+
|
| 71 |
+
if args:
|
| 72 |
+
func, *args = args
|
| 73 |
+
elif 'func' in kwds:
|
| 74 |
+
func = kwds.pop('func')
|
| 75 |
+
import warnings
|
| 76 |
+
warnings.warn("Passing 'func' as keyword argument is deprecated",
|
| 77 |
+
DeprecationWarning, stacklevel=2)
|
| 78 |
+
else:
|
| 79 |
+
raise TypeError('wrapper expected at least 1 positional argument, '
|
| 80 |
+
'got %d' % len(args))
|
| 81 |
+
|
| 82 |
+
try:
|
| 83 |
+
# Initialize curses
|
| 84 |
+
stdscr = initscr()
|
| 85 |
+
|
| 86 |
+
# Turn off echoing of keys, and enter cbreak mode,
|
| 87 |
+
# where no buffering is performed on keyboard input
|
| 88 |
+
noecho()
|
| 89 |
+
cbreak()
|
| 90 |
+
|
| 91 |
+
# In keypad mode, escape sequences for special keys
|
| 92 |
+
# (like the cursor keys) will be interpreted and
|
| 93 |
+
# a special value like curses.KEY_LEFT will be returned
|
| 94 |
+
stdscr.keypad(1)
|
| 95 |
+
|
| 96 |
+
# Start color, too. Harmless if the terminal doesn't have
|
| 97 |
+
# color; user can test with has_color() later on. The try/catch
|
| 98 |
+
# works around a minor bit of over-conscientiousness in the curses
|
| 99 |
+
# module -- the error return from C start_color() is ignorable.
|
| 100 |
+
try:
|
| 101 |
+
start_color()
|
| 102 |
+
except:
|
| 103 |
+
pass
|
| 104 |
+
|
| 105 |
+
return func(stdscr, *args, **kwds)
|
| 106 |
+
finally:
|
| 107 |
+
# Set everything back to normal
|
| 108 |
+
if 'stdscr' in locals():
|
| 109 |
+
stdscr.keypad(0)
|
| 110 |
+
echo()
|
| 111 |
+
nocbreak()
|
| 112 |
+
endwin()
|
| 113 |
+
wrapper.__text_signature__ = '(func, /, *args, **kwds)'
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/curses/ascii.py
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Constants and membership tests for ASCII characters"""
|
| 2 |
+
|
| 3 |
+
NUL = 0x00 # ^@
|
| 4 |
+
SOH = 0x01 # ^A
|
| 5 |
+
STX = 0x02 # ^B
|
| 6 |
+
ETX = 0x03 # ^C
|
| 7 |
+
EOT = 0x04 # ^D
|
| 8 |
+
ENQ = 0x05 # ^E
|
| 9 |
+
ACK = 0x06 # ^F
|
| 10 |
+
BEL = 0x07 # ^G
|
| 11 |
+
BS = 0x08 # ^H
|
| 12 |
+
TAB = 0x09 # ^I
|
| 13 |
+
HT = 0x09 # ^I
|
| 14 |
+
LF = 0x0a # ^J
|
| 15 |
+
NL = 0x0a # ^J
|
| 16 |
+
VT = 0x0b # ^K
|
| 17 |
+
FF = 0x0c # ^L
|
| 18 |
+
CR = 0x0d # ^M
|
| 19 |
+
SO = 0x0e # ^N
|
| 20 |
+
SI = 0x0f # ^O
|
| 21 |
+
DLE = 0x10 # ^P
|
| 22 |
+
DC1 = 0x11 # ^Q
|
| 23 |
+
DC2 = 0x12 # ^R
|
| 24 |
+
DC3 = 0x13 # ^S
|
| 25 |
+
DC4 = 0x14 # ^T
|
| 26 |
+
NAK = 0x15 # ^U
|
| 27 |
+
SYN = 0x16 # ^V
|
| 28 |
+
ETB = 0x17 # ^W
|
| 29 |
+
CAN = 0x18 # ^X
|
| 30 |
+
EM = 0x19 # ^Y
|
| 31 |
+
SUB = 0x1a # ^Z
|
| 32 |
+
ESC = 0x1b # ^[
|
| 33 |
+
FS = 0x1c # ^\
|
| 34 |
+
GS = 0x1d # ^]
|
| 35 |
+
RS = 0x1e # ^^
|
| 36 |
+
US = 0x1f # ^_
|
| 37 |
+
SP = 0x20 # space
|
| 38 |
+
DEL = 0x7f # delete
|
| 39 |
+
|
| 40 |
+
controlnames = [
|
| 41 |
+
"NUL", "SOH", "STX", "ETX", "EOT", "ENQ", "ACK", "BEL",
|
| 42 |
+
"BS", "HT", "LF", "VT", "FF", "CR", "SO", "SI",
|
| 43 |
+
"DLE", "DC1", "DC2", "DC3", "DC4", "NAK", "SYN", "ETB",
|
| 44 |
+
"CAN", "EM", "SUB", "ESC", "FS", "GS", "RS", "US",
|
| 45 |
+
"SP"
|
| 46 |
+
]
|
| 47 |
+
|
| 48 |
+
def _ctoi(c):
|
| 49 |
+
if type(c) == type(""):
|
| 50 |
+
return ord(c)
|
| 51 |
+
else:
|
| 52 |
+
return c
|
| 53 |
+
|
| 54 |
+
def isalnum(c): return isalpha(c) or isdigit(c)
|
| 55 |
+
def isalpha(c): return isupper(c) or islower(c)
|
| 56 |
+
def isascii(c): return 0 <= _ctoi(c) <= 127 # ?
|
| 57 |
+
def isblank(c): return _ctoi(c) in (9, 32)
|
| 58 |
+
def iscntrl(c): return 0 <= _ctoi(c) <= 31 or _ctoi(c) == 127
|
| 59 |
+
def isdigit(c): return 48 <= _ctoi(c) <= 57
|
| 60 |
+
def isgraph(c): return 33 <= _ctoi(c) <= 126
|
| 61 |
+
def islower(c): return 97 <= _ctoi(c) <= 122
|
| 62 |
+
def isprint(c): return 32 <= _ctoi(c) <= 126
|
| 63 |
+
def ispunct(c): return isgraph(c) and not isalnum(c)
|
| 64 |
+
def isspace(c): return _ctoi(c) in (9, 10, 11, 12, 13, 32)
|
| 65 |
+
def isupper(c): return 65 <= _ctoi(c) <= 90
|
| 66 |
+
def isxdigit(c): return isdigit(c) or \
|
| 67 |
+
(65 <= _ctoi(c) <= 70) or (97 <= _ctoi(c) <= 102)
|
| 68 |
+
def isctrl(c): return 0 <= _ctoi(c) < 32
|
| 69 |
+
def ismeta(c): return _ctoi(c) > 127
|
| 70 |
+
|
| 71 |
+
def ascii(c):
|
| 72 |
+
if type(c) == type(""):
|
| 73 |
+
return chr(_ctoi(c) & 0x7f)
|
| 74 |
+
else:
|
| 75 |
+
return _ctoi(c) & 0x7f
|
| 76 |
+
|
| 77 |
+
def ctrl(c):
|
| 78 |
+
if type(c) == type(""):
|
| 79 |
+
return chr(_ctoi(c) & 0x1f)
|
| 80 |
+
else:
|
| 81 |
+
return _ctoi(c) & 0x1f
|
| 82 |
+
|
| 83 |
+
def alt(c):
|
| 84 |
+
if type(c) == type(""):
|
| 85 |
+
return chr(_ctoi(c) | 0x80)
|
| 86 |
+
else:
|
| 87 |
+
return _ctoi(c) | 0x80
|
| 88 |
+
|
| 89 |
+
def unctrl(c):
|
| 90 |
+
bits = _ctoi(c)
|
| 91 |
+
if bits == 0x7f:
|
| 92 |
+
rep = "^?"
|
| 93 |
+
elif isprint(bits & 0x7f):
|
| 94 |
+
rep = chr(bits & 0x7f)
|
| 95 |
+
else:
|
| 96 |
+
rep = "^" + chr(((bits & 0x7f) | 0x20) + 0x20)
|
| 97 |
+
if bits & 0x80:
|
| 98 |
+
return "!" + rep
|
| 99 |
+
return rep
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/curses/has_key.py
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
#
|
| 3 |
+
# Emulation of has_key() function for platforms that don't use ncurses
|
| 4 |
+
#
|
| 5 |
+
|
| 6 |
+
import _curses
|
| 7 |
+
|
| 8 |
+
# Table mapping curses keys to the terminfo capability name
|
| 9 |
+
|
| 10 |
+
_capability_names = {
|
| 11 |
+
_curses.KEY_A1: 'ka1',
|
| 12 |
+
_curses.KEY_A3: 'ka3',
|
| 13 |
+
_curses.KEY_B2: 'kb2',
|
| 14 |
+
_curses.KEY_BACKSPACE: 'kbs',
|
| 15 |
+
_curses.KEY_BEG: 'kbeg',
|
| 16 |
+
_curses.KEY_BTAB: 'kcbt',
|
| 17 |
+
_curses.KEY_C1: 'kc1',
|
| 18 |
+
_curses.KEY_C3: 'kc3',
|
| 19 |
+
_curses.KEY_CANCEL: 'kcan',
|
| 20 |
+
_curses.KEY_CATAB: 'ktbc',
|
| 21 |
+
_curses.KEY_CLEAR: 'kclr',
|
| 22 |
+
_curses.KEY_CLOSE: 'kclo',
|
| 23 |
+
_curses.KEY_COMMAND: 'kcmd',
|
| 24 |
+
_curses.KEY_COPY: 'kcpy',
|
| 25 |
+
_curses.KEY_CREATE: 'kcrt',
|
| 26 |
+
_curses.KEY_CTAB: 'kctab',
|
| 27 |
+
_curses.KEY_DC: 'kdch1',
|
| 28 |
+
_curses.KEY_DL: 'kdl1',
|
| 29 |
+
_curses.KEY_DOWN: 'kcud1',
|
| 30 |
+
_curses.KEY_EIC: 'krmir',
|
| 31 |
+
_curses.KEY_END: 'kend',
|
| 32 |
+
_curses.KEY_ENTER: 'kent',
|
| 33 |
+
_curses.KEY_EOL: 'kel',
|
| 34 |
+
_curses.KEY_EOS: 'ked',
|
| 35 |
+
_curses.KEY_EXIT: 'kext',
|
| 36 |
+
_curses.KEY_F0: 'kf0',
|
| 37 |
+
_curses.KEY_F1: 'kf1',
|
| 38 |
+
_curses.KEY_F10: 'kf10',
|
| 39 |
+
_curses.KEY_F11: 'kf11',
|
| 40 |
+
_curses.KEY_F12: 'kf12',
|
| 41 |
+
_curses.KEY_F13: 'kf13',
|
| 42 |
+
_curses.KEY_F14: 'kf14',
|
| 43 |
+
_curses.KEY_F15: 'kf15',
|
| 44 |
+
_curses.KEY_F16: 'kf16',
|
| 45 |
+
_curses.KEY_F17: 'kf17',
|
| 46 |
+
_curses.KEY_F18: 'kf18',
|
| 47 |
+
_curses.KEY_F19: 'kf19',
|
| 48 |
+
_curses.KEY_F2: 'kf2',
|
| 49 |
+
_curses.KEY_F20: 'kf20',
|
| 50 |
+
_curses.KEY_F21: 'kf21',
|
| 51 |
+
_curses.KEY_F22: 'kf22',
|
| 52 |
+
_curses.KEY_F23: 'kf23',
|
| 53 |
+
_curses.KEY_F24: 'kf24',
|
| 54 |
+
_curses.KEY_F25: 'kf25',
|
| 55 |
+
_curses.KEY_F26: 'kf26',
|
| 56 |
+
_curses.KEY_F27: 'kf27',
|
| 57 |
+
_curses.KEY_F28: 'kf28',
|
| 58 |
+
_curses.KEY_F29: 'kf29',
|
| 59 |
+
_curses.KEY_F3: 'kf3',
|
| 60 |
+
_curses.KEY_F30: 'kf30',
|
| 61 |
+
_curses.KEY_F31: 'kf31',
|
| 62 |
+
_curses.KEY_F32: 'kf32',
|
| 63 |
+
_curses.KEY_F33: 'kf33',
|
| 64 |
+
_curses.KEY_F34: 'kf34',
|
| 65 |
+
_curses.KEY_F35: 'kf35',
|
| 66 |
+
_curses.KEY_F36: 'kf36',
|
| 67 |
+
_curses.KEY_F37: 'kf37',
|
| 68 |
+
_curses.KEY_F38: 'kf38',
|
| 69 |
+
_curses.KEY_F39: 'kf39',
|
| 70 |
+
_curses.KEY_F4: 'kf4',
|
| 71 |
+
_curses.KEY_F40: 'kf40',
|
| 72 |
+
_curses.KEY_F41: 'kf41',
|
| 73 |
+
_curses.KEY_F42: 'kf42',
|
| 74 |
+
_curses.KEY_F43: 'kf43',
|
| 75 |
+
_curses.KEY_F44: 'kf44',
|
| 76 |
+
_curses.KEY_F45: 'kf45',
|
| 77 |
+
_curses.KEY_F46: 'kf46',
|
| 78 |
+
_curses.KEY_F47: 'kf47',
|
| 79 |
+
_curses.KEY_F48: 'kf48',
|
| 80 |
+
_curses.KEY_F49: 'kf49',
|
| 81 |
+
_curses.KEY_F5: 'kf5',
|
| 82 |
+
_curses.KEY_F50: 'kf50',
|
| 83 |
+
_curses.KEY_F51: 'kf51',
|
| 84 |
+
_curses.KEY_F52: 'kf52',
|
| 85 |
+
_curses.KEY_F53: 'kf53',
|
| 86 |
+
_curses.KEY_F54: 'kf54',
|
| 87 |
+
_curses.KEY_F55: 'kf55',
|
| 88 |
+
_curses.KEY_F56: 'kf56',
|
| 89 |
+
_curses.KEY_F57: 'kf57',
|
| 90 |
+
_curses.KEY_F58: 'kf58',
|
| 91 |
+
_curses.KEY_F59: 'kf59',
|
| 92 |
+
_curses.KEY_F6: 'kf6',
|
| 93 |
+
_curses.KEY_F60: 'kf60',
|
| 94 |
+
_curses.KEY_F61: 'kf61',
|
| 95 |
+
_curses.KEY_F62: 'kf62',
|
| 96 |
+
_curses.KEY_F63: 'kf63',
|
| 97 |
+
_curses.KEY_F7: 'kf7',
|
| 98 |
+
_curses.KEY_F8: 'kf8',
|
| 99 |
+
_curses.KEY_F9: 'kf9',
|
| 100 |
+
_curses.KEY_FIND: 'kfnd',
|
| 101 |
+
_curses.KEY_HELP: 'khlp',
|
| 102 |
+
_curses.KEY_HOME: 'khome',
|
| 103 |
+
_curses.KEY_IC: 'kich1',
|
| 104 |
+
_curses.KEY_IL: 'kil1',
|
| 105 |
+
_curses.KEY_LEFT: 'kcub1',
|
| 106 |
+
_curses.KEY_LL: 'kll',
|
| 107 |
+
_curses.KEY_MARK: 'kmrk',
|
| 108 |
+
_curses.KEY_MESSAGE: 'kmsg',
|
| 109 |
+
_curses.KEY_MOVE: 'kmov',
|
| 110 |
+
_curses.KEY_NEXT: 'knxt',
|
| 111 |
+
_curses.KEY_NPAGE: 'knp',
|
| 112 |
+
_curses.KEY_OPEN: 'kopn',
|
| 113 |
+
_curses.KEY_OPTIONS: 'kopt',
|
| 114 |
+
_curses.KEY_PPAGE: 'kpp',
|
| 115 |
+
_curses.KEY_PREVIOUS: 'kprv',
|
| 116 |
+
_curses.KEY_PRINT: 'kprt',
|
| 117 |
+
_curses.KEY_REDO: 'krdo',
|
| 118 |
+
_curses.KEY_REFERENCE: 'kref',
|
| 119 |
+
_curses.KEY_REFRESH: 'krfr',
|
| 120 |
+
_curses.KEY_REPLACE: 'krpl',
|
| 121 |
+
_curses.KEY_RESTART: 'krst',
|
| 122 |
+
_curses.KEY_RESUME: 'kres',
|
| 123 |
+
_curses.KEY_RIGHT: 'kcuf1',
|
| 124 |
+
_curses.KEY_SAVE: 'ksav',
|
| 125 |
+
_curses.KEY_SBEG: 'kBEG',
|
| 126 |
+
_curses.KEY_SCANCEL: 'kCAN',
|
| 127 |
+
_curses.KEY_SCOMMAND: 'kCMD',
|
| 128 |
+
_curses.KEY_SCOPY: 'kCPY',
|
| 129 |
+
_curses.KEY_SCREATE: 'kCRT',
|
| 130 |
+
_curses.KEY_SDC: 'kDC',
|
| 131 |
+
_curses.KEY_SDL: 'kDL',
|
| 132 |
+
_curses.KEY_SELECT: 'kslt',
|
| 133 |
+
_curses.KEY_SEND: 'kEND',
|
| 134 |
+
_curses.KEY_SEOL: 'kEOL',
|
| 135 |
+
_curses.KEY_SEXIT: 'kEXT',
|
| 136 |
+
_curses.KEY_SF: 'kind',
|
| 137 |
+
_curses.KEY_SFIND: 'kFND',
|
| 138 |
+
_curses.KEY_SHELP: 'kHLP',
|
| 139 |
+
_curses.KEY_SHOME: 'kHOM',
|
| 140 |
+
_curses.KEY_SIC: 'kIC',
|
| 141 |
+
_curses.KEY_SLEFT: 'kLFT',
|
| 142 |
+
_curses.KEY_SMESSAGE: 'kMSG',
|
| 143 |
+
_curses.KEY_SMOVE: 'kMOV',
|
| 144 |
+
_curses.KEY_SNEXT: 'kNXT',
|
| 145 |
+
_curses.KEY_SOPTIONS: 'kOPT',
|
| 146 |
+
_curses.KEY_SPREVIOUS: 'kPRV',
|
| 147 |
+
_curses.KEY_SPRINT: 'kPRT',
|
| 148 |
+
_curses.KEY_SR: 'kri',
|
| 149 |
+
_curses.KEY_SREDO: 'kRDO',
|
| 150 |
+
_curses.KEY_SREPLACE: 'kRPL',
|
| 151 |
+
_curses.KEY_SRIGHT: 'kRIT',
|
| 152 |
+
_curses.KEY_SRSUME: 'kRES',
|
| 153 |
+
_curses.KEY_SSAVE: 'kSAV',
|
| 154 |
+
_curses.KEY_SSUSPEND: 'kSPD',
|
| 155 |
+
_curses.KEY_STAB: 'khts',
|
| 156 |
+
_curses.KEY_SUNDO: 'kUND',
|
| 157 |
+
_curses.KEY_SUSPEND: 'kspd',
|
| 158 |
+
_curses.KEY_UNDO: 'kund',
|
| 159 |
+
_curses.KEY_UP: 'kcuu1'
|
| 160 |
+
}
|
| 161 |
+
|
| 162 |
+
def has_key(ch):
|
| 163 |
+
if isinstance(ch, str):
|
| 164 |
+
ch = ord(ch)
|
| 165 |
+
|
| 166 |
+
# Figure out the correct capability name for the keycode.
|
| 167 |
+
capability_name = _capability_names.get(ch)
|
| 168 |
+
if capability_name is None:
|
| 169 |
+
return False
|
| 170 |
+
|
| 171 |
+
#Check the current terminal description for that capability;
|
| 172 |
+
#if present, return true, else return false.
|
| 173 |
+
if _curses.tigetstr( capability_name ):
|
| 174 |
+
return True
|
| 175 |
+
else:
|
| 176 |
+
return False
|
| 177 |
+
|
| 178 |
+
if __name__ == '__main__':
|
| 179 |
+
# Compare the output of this implementation and the ncurses has_key,
|
| 180 |
+
# on platforms where has_key is already available
|
| 181 |
+
try:
|
| 182 |
+
L = []
|
| 183 |
+
_curses.initscr()
|
| 184 |
+
for key in _capability_names.keys():
|
| 185 |
+
system = _curses.has_key(key)
|
| 186 |
+
python = has_key(key)
|
| 187 |
+
if system != python:
|
| 188 |
+
L.append( 'Mismatch for key %s, system=%i, Python=%i'
|
| 189 |
+
% (_curses.keyname( key ), system, python) )
|
| 190 |
+
finally:
|
| 191 |
+
_curses.endwin()
|
| 192 |
+
for i in L: print(i)
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/curses/panel.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""curses.panel
|
| 2 |
+
|
| 3 |
+
Module for using panels with curses.
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
from _curses_panel import *
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/dbm/__init__.py
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Generic interface to all dbm clones.
|
| 2 |
+
|
| 3 |
+
Use
|
| 4 |
+
|
| 5 |
+
import dbm
|
| 6 |
+
d = dbm.open(file, 'w', 0o666)
|
| 7 |
+
|
| 8 |
+
The returned object is a dbm.gnu, dbm.ndbm or dbm.dumb object, dependent on the
|
| 9 |
+
type of database being opened (determined by the whichdb function) in the case
|
| 10 |
+
of an existing dbm. If the dbm does not exist and the create or new flag ('c'
|
| 11 |
+
or 'n') was specified, the dbm type will be determined by the availability of
|
| 12 |
+
the modules (tested in the above order).
|
| 13 |
+
|
| 14 |
+
It has the following interface (key and data are strings):
|
| 15 |
+
|
| 16 |
+
d[key] = data # store data at key (may override data at
|
| 17 |
+
# existing key)
|
| 18 |
+
data = d[key] # retrieve data at key (raise KeyError if no
|
| 19 |
+
# such key)
|
| 20 |
+
del d[key] # delete data stored at key (raises KeyError
|
| 21 |
+
# if no such key)
|
| 22 |
+
flag = key in d # true if the key exists
|
| 23 |
+
list = d.keys() # return a list of all existing keys (slow!)
|
| 24 |
+
|
| 25 |
+
Future versions may change the order in which implementations are
|
| 26 |
+
tested for existence, and add interfaces to other dbm-like
|
| 27 |
+
implementations.
|
| 28 |
+
"""
|
| 29 |
+
|
| 30 |
+
__all__ = ['open', 'whichdb', 'error']
|
| 31 |
+
|
| 32 |
+
import io
|
| 33 |
+
import os
|
| 34 |
+
import struct
|
| 35 |
+
import sys
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
class error(Exception):
|
| 39 |
+
pass
|
| 40 |
+
|
| 41 |
+
_names = ['dbm.gnu', 'dbm.ndbm', 'dbm.dumb']
|
| 42 |
+
_defaultmod = None
|
| 43 |
+
_modules = {}
|
| 44 |
+
|
| 45 |
+
error = (error, OSError)
|
| 46 |
+
|
| 47 |
+
try:
|
| 48 |
+
from dbm import ndbm
|
| 49 |
+
except ImportError:
|
| 50 |
+
ndbm = None
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def open(file, flag='r', mode=0o666):
|
| 54 |
+
"""Open or create database at path given by *file*.
|
| 55 |
+
|
| 56 |
+
Optional argument *flag* can be 'r' (default) for read-only access, 'w'
|
| 57 |
+
for read-write access of an existing database, 'c' for read-write access
|
| 58 |
+
to a new or existing database, and 'n' for read-write access to a new
|
| 59 |
+
database.
|
| 60 |
+
|
| 61 |
+
Note: 'r' and 'w' fail if the database doesn't exist; 'c' creates it
|
| 62 |
+
only if it doesn't exist; and 'n' always creates a new database.
|
| 63 |
+
"""
|
| 64 |
+
global _defaultmod
|
| 65 |
+
if _defaultmod is None:
|
| 66 |
+
for name in _names:
|
| 67 |
+
try:
|
| 68 |
+
mod = __import__(name, fromlist=['open'])
|
| 69 |
+
except ImportError:
|
| 70 |
+
continue
|
| 71 |
+
if not _defaultmod:
|
| 72 |
+
_defaultmod = mod
|
| 73 |
+
_modules[name] = mod
|
| 74 |
+
if not _defaultmod:
|
| 75 |
+
raise ImportError("no dbm clone found; tried %s" % _names)
|
| 76 |
+
|
| 77 |
+
# guess the type of an existing database, if not creating a new one
|
| 78 |
+
result = whichdb(file) if 'n' not in flag else None
|
| 79 |
+
if result is None:
|
| 80 |
+
# db doesn't exist or 'n' flag was specified to create a new db
|
| 81 |
+
if 'c' in flag or 'n' in flag:
|
| 82 |
+
# file doesn't exist and the new flag was used so use default type
|
| 83 |
+
mod = _defaultmod
|
| 84 |
+
else:
|
| 85 |
+
raise error[0]("db file doesn't exist; "
|
| 86 |
+
"use 'c' or 'n' flag to create a new db")
|
| 87 |
+
elif result == "":
|
| 88 |
+
# db type cannot be determined
|
| 89 |
+
raise error[0]("db type could not be determined")
|
| 90 |
+
elif result not in _modules:
|
| 91 |
+
raise error[0]("db type is {0}, but the module is not "
|
| 92 |
+
"available".format(result))
|
| 93 |
+
else:
|
| 94 |
+
mod = _modules[result]
|
| 95 |
+
return mod.open(file, flag, mode)
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
def whichdb(filename):
|
| 99 |
+
"""Guess which db package to use to open a db file.
|
| 100 |
+
|
| 101 |
+
Return values:
|
| 102 |
+
|
| 103 |
+
- None if the database file can't be read;
|
| 104 |
+
- empty string if the file can be read but can't be recognized
|
| 105 |
+
- the name of the dbm submodule (e.g. "ndbm" or "gnu") if recognized.
|
| 106 |
+
|
| 107 |
+
Importing the given module may still fail, and opening the
|
| 108 |
+
database using that module may still fail.
|
| 109 |
+
"""
|
| 110 |
+
|
| 111 |
+
# Check for ndbm first -- this has a .pag and a .dir file
|
| 112 |
+
try:
|
| 113 |
+
f = io.open(filename + ".pag", "rb")
|
| 114 |
+
f.close()
|
| 115 |
+
f = io.open(filename + ".dir", "rb")
|
| 116 |
+
f.close()
|
| 117 |
+
return "dbm.ndbm"
|
| 118 |
+
except OSError:
|
| 119 |
+
# some dbm emulations based on Berkeley DB generate a .db file
|
| 120 |
+
# some do not, but they should be caught by the bsd checks
|
| 121 |
+
try:
|
| 122 |
+
f = io.open(filename + ".db", "rb")
|
| 123 |
+
f.close()
|
| 124 |
+
# guarantee we can actually open the file using dbm
|
| 125 |
+
# kind of overkill, but since we are dealing with emulations
|
| 126 |
+
# it seems like a prudent step
|
| 127 |
+
if ndbm is not None:
|
| 128 |
+
d = ndbm.open(filename)
|
| 129 |
+
d.close()
|
| 130 |
+
return "dbm.ndbm"
|
| 131 |
+
except OSError:
|
| 132 |
+
pass
|
| 133 |
+
|
| 134 |
+
# Check for dumbdbm next -- this has a .dir and a .dat file
|
| 135 |
+
try:
|
| 136 |
+
# First check for presence of files
|
| 137 |
+
os.stat(filename + ".dat")
|
| 138 |
+
size = os.stat(filename + ".dir").st_size
|
| 139 |
+
# dumbdbm files with no keys are empty
|
| 140 |
+
if size == 0:
|
| 141 |
+
return "dbm.dumb"
|
| 142 |
+
f = io.open(filename + ".dir", "rb")
|
| 143 |
+
try:
|
| 144 |
+
if f.read(1) in (b"'", b'"'):
|
| 145 |
+
return "dbm.dumb"
|
| 146 |
+
finally:
|
| 147 |
+
f.close()
|
| 148 |
+
except OSError:
|
| 149 |
+
pass
|
| 150 |
+
|
| 151 |
+
# See if the file exists, return None if not
|
| 152 |
+
try:
|
| 153 |
+
f = io.open(filename, "rb")
|
| 154 |
+
except OSError:
|
| 155 |
+
return None
|
| 156 |
+
|
| 157 |
+
with f:
|
| 158 |
+
# Read the start of the file -- the magic number
|
| 159 |
+
s16 = f.read(16)
|
| 160 |
+
s = s16[0:4]
|
| 161 |
+
|
| 162 |
+
# Return "" if not at least 4 bytes
|
| 163 |
+
if len(s) != 4:
|
| 164 |
+
return ""
|
| 165 |
+
|
| 166 |
+
# Convert to 4-byte int in native byte order -- return "" if impossible
|
| 167 |
+
try:
|
| 168 |
+
(magic,) = struct.unpack("=l", s)
|
| 169 |
+
except struct.error:
|
| 170 |
+
return ""
|
| 171 |
+
|
| 172 |
+
# Check for GNU dbm
|
| 173 |
+
if magic in (0x13579ace, 0x13579acd, 0x13579acf):
|
| 174 |
+
return "dbm.gnu"
|
| 175 |
+
|
| 176 |
+
# Later versions of Berkeley db hash file have a 12-byte pad in
|
| 177 |
+
# front of the file type
|
| 178 |
+
try:
|
| 179 |
+
(magic,) = struct.unpack("=l", s16[-4:])
|
| 180 |
+
except struct.error:
|
| 181 |
+
return ""
|
| 182 |
+
|
| 183 |
+
# Unknown
|
| 184 |
+
return ""
|
| 185 |
+
|
| 186 |
+
|
| 187 |
+
if __name__ == "__main__":
|
| 188 |
+
for filename in sys.argv[1:]:
|
| 189 |
+
print(whichdb(filename) or "UNKNOWN", filename)
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/dbm/dumb.py
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""A dumb and slow but simple dbm clone.
|
| 2 |
+
|
| 3 |
+
For database spam, spam.dir contains the index (a text file),
|
| 4 |
+
spam.bak *may* contain a backup of the index (also a text file),
|
| 5 |
+
while spam.dat contains the data (a binary file).
|
| 6 |
+
|
| 7 |
+
XXX TO DO:
|
| 8 |
+
|
| 9 |
+
- seems to contain a bug when updating...
|
| 10 |
+
|
| 11 |
+
- reclaim free space (currently, space once occupied by deleted or expanded
|
| 12 |
+
items is never reused)
|
| 13 |
+
|
| 14 |
+
- support concurrent access (currently, if two processes take turns making
|
| 15 |
+
updates, they can mess up the index)
|
| 16 |
+
|
| 17 |
+
- support efficient access to large databases (currently, the whole index
|
| 18 |
+
is read when the database is opened, and some updates rewrite the whole index)
|
| 19 |
+
|
| 20 |
+
- support opening for read-only (flag = 'm')
|
| 21 |
+
|
| 22 |
+
"""
|
| 23 |
+
|
| 24 |
+
import ast as _ast
|
| 25 |
+
import io as _io
|
| 26 |
+
import os as _os
|
| 27 |
+
import collections.abc
|
| 28 |
+
|
| 29 |
+
__all__ = ["error", "open"]
|
| 30 |
+
|
| 31 |
+
_BLOCKSIZE = 512
|
| 32 |
+
|
| 33 |
+
error = OSError
|
| 34 |
+
|
| 35 |
+
class _Database(collections.abc.MutableMapping):
|
| 36 |
+
|
| 37 |
+
# The on-disk directory and data files can remain in mutually
|
| 38 |
+
# inconsistent states for an arbitrarily long time (see comments
|
| 39 |
+
# at the end of __setitem__). This is only repaired when _commit()
|
| 40 |
+
# gets called. One place _commit() gets called is from __del__(),
|
| 41 |
+
# and if that occurs at program shutdown time, module globals may
|
| 42 |
+
# already have gotten rebound to None. Since it's crucial that
|
| 43 |
+
# _commit() finish successfully, we can't ignore shutdown races
|
| 44 |
+
# here, and _commit() must not reference any globals.
|
| 45 |
+
_os = _os # for _commit()
|
| 46 |
+
_io = _io # for _commit()
|
| 47 |
+
|
| 48 |
+
def __init__(self, filebasename, mode, flag='c'):
|
| 49 |
+
self._mode = mode
|
| 50 |
+
self._readonly = (flag == 'r')
|
| 51 |
+
|
| 52 |
+
# The directory file is a text file. Each line looks like
|
| 53 |
+
# "%r, (%d, %d)\n" % (key, pos, siz)
|
| 54 |
+
# where key is the string key, pos is the offset into the dat
|
| 55 |
+
# file of the associated value's first byte, and siz is the number
|
| 56 |
+
# of bytes in the associated value.
|
| 57 |
+
self._dirfile = filebasename + '.dir'
|
| 58 |
+
|
| 59 |
+
# The data file is a binary file pointed into by the directory
|
| 60 |
+
# file, and holds the values associated with keys. Each value
|
| 61 |
+
# begins at a _BLOCKSIZE-aligned byte offset, and is a raw
|
| 62 |
+
# binary 8-bit string value.
|
| 63 |
+
self._datfile = filebasename + '.dat'
|
| 64 |
+
self._bakfile = filebasename + '.bak'
|
| 65 |
+
|
| 66 |
+
# The index is an in-memory dict, mirroring the directory file.
|
| 67 |
+
self._index = None # maps keys to (pos, siz) pairs
|
| 68 |
+
|
| 69 |
+
# Handle the creation
|
| 70 |
+
self._create(flag)
|
| 71 |
+
self._update(flag)
|
| 72 |
+
|
| 73 |
+
def _create(self, flag):
|
| 74 |
+
if flag == 'n':
|
| 75 |
+
for filename in (self._datfile, self._bakfile, self._dirfile):
|
| 76 |
+
try:
|
| 77 |
+
_os.remove(filename)
|
| 78 |
+
except OSError:
|
| 79 |
+
pass
|
| 80 |
+
# Mod by Jack: create data file if needed
|
| 81 |
+
try:
|
| 82 |
+
f = _io.open(self._datfile, 'r', encoding="Latin-1")
|
| 83 |
+
except OSError:
|
| 84 |
+
if flag not in ('c', 'n'):
|
| 85 |
+
raise
|
| 86 |
+
with _io.open(self._datfile, 'w', encoding="Latin-1") as f:
|
| 87 |
+
self._chmod(self._datfile)
|
| 88 |
+
else:
|
| 89 |
+
f.close()
|
| 90 |
+
|
| 91 |
+
# Read directory file into the in-memory index dict.
|
| 92 |
+
def _update(self, flag):
|
| 93 |
+
self._modified = False
|
| 94 |
+
self._index = {}
|
| 95 |
+
try:
|
| 96 |
+
f = _io.open(self._dirfile, 'r', encoding="Latin-1")
|
| 97 |
+
except OSError:
|
| 98 |
+
if flag not in ('c', 'n'):
|
| 99 |
+
raise
|
| 100 |
+
self._modified = True
|
| 101 |
+
else:
|
| 102 |
+
with f:
|
| 103 |
+
for line in f:
|
| 104 |
+
line = line.rstrip()
|
| 105 |
+
key, pos_and_siz_pair = _ast.literal_eval(line)
|
| 106 |
+
key = key.encode('Latin-1')
|
| 107 |
+
self._index[key] = pos_and_siz_pair
|
| 108 |
+
|
| 109 |
+
# Write the index dict to the directory file. The original directory
|
| 110 |
+
# file (if any) is renamed with a .bak extension first. If a .bak
|
| 111 |
+
# file currently exists, it's deleted.
|
| 112 |
+
def _commit(self):
|
| 113 |
+
# CAUTION: It's vital that _commit() succeed, and _commit() can
|
| 114 |
+
# be called from __del__(). Therefore we must never reference a
|
| 115 |
+
# global in this routine.
|
| 116 |
+
if self._index is None or not self._modified:
|
| 117 |
+
return # nothing to do
|
| 118 |
+
|
| 119 |
+
try:
|
| 120 |
+
self._os.unlink(self._bakfile)
|
| 121 |
+
except OSError:
|
| 122 |
+
pass
|
| 123 |
+
|
| 124 |
+
try:
|
| 125 |
+
self._os.rename(self._dirfile, self._bakfile)
|
| 126 |
+
except OSError:
|
| 127 |
+
pass
|
| 128 |
+
|
| 129 |
+
with self._io.open(self._dirfile, 'w', encoding="Latin-1") as f:
|
| 130 |
+
self._chmod(self._dirfile)
|
| 131 |
+
for key, pos_and_siz_pair in self._index.items():
|
| 132 |
+
# Use Latin-1 since it has no qualms with any value in any
|
| 133 |
+
# position; UTF-8, though, does care sometimes.
|
| 134 |
+
entry = "%r, %r\n" % (key.decode('Latin-1'), pos_and_siz_pair)
|
| 135 |
+
f.write(entry)
|
| 136 |
+
|
| 137 |
+
sync = _commit
|
| 138 |
+
|
| 139 |
+
def _verify_open(self):
|
| 140 |
+
if self._index is None:
|
| 141 |
+
raise error('DBM object has already been closed')
|
| 142 |
+
|
| 143 |
+
def __getitem__(self, key):
|
| 144 |
+
if isinstance(key, str):
|
| 145 |
+
key = key.encode('utf-8')
|
| 146 |
+
self._verify_open()
|
| 147 |
+
pos, siz = self._index[key] # may raise KeyError
|
| 148 |
+
with _io.open(self._datfile, 'rb') as f:
|
| 149 |
+
f.seek(pos)
|
| 150 |
+
dat = f.read(siz)
|
| 151 |
+
return dat
|
| 152 |
+
|
| 153 |
+
# Append val to the data file, starting at a _BLOCKSIZE-aligned
|
| 154 |
+
# offset. The data file is first padded with NUL bytes (if needed)
|
| 155 |
+
# to get to an aligned offset. Return pair
|
| 156 |
+
# (starting offset of val, len(val))
|
| 157 |
+
def _addval(self, val):
|
| 158 |
+
with _io.open(self._datfile, 'rb+') as f:
|
| 159 |
+
f.seek(0, 2)
|
| 160 |
+
pos = int(f.tell())
|
| 161 |
+
npos = ((pos + _BLOCKSIZE - 1) // _BLOCKSIZE) * _BLOCKSIZE
|
| 162 |
+
f.write(b'\0'*(npos-pos))
|
| 163 |
+
pos = npos
|
| 164 |
+
f.write(val)
|
| 165 |
+
return (pos, len(val))
|
| 166 |
+
|
| 167 |
+
# Write val to the data file, starting at offset pos. The caller
|
| 168 |
+
# is responsible for ensuring that there's enough room starting at
|
| 169 |
+
# pos to hold val, without overwriting some other value. Return
|
| 170 |
+
# pair (pos, len(val)).
|
| 171 |
+
def _setval(self, pos, val):
|
| 172 |
+
with _io.open(self._datfile, 'rb+') as f:
|
| 173 |
+
f.seek(pos)
|
| 174 |
+
f.write(val)
|
| 175 |
+
return (pos, len(val))
|
| 176 |
+
|
| 177 |
+
# key is a new key whose associated value starts in the data file
|
| 178 |
+
# at offset pos and with length siz. Add an index record to
|
| 179 |
+
# the in-memory index dict, and append one to the directory file.
|
| 180 |
+
def _addkey(self, key, pos_and_siz_pair):
|
| 181 |
+
self._index[key] = pos_and_siz_pair
|
| 182 |
+
with _io.open(self._dirfile, 'a', encoding="Latin-1") as f:
|
| 183 |
+
self._chmod(self._dirfile)
|
| 184 |
+
f.write("%r, %r\n" % (key.decode("Latin-1"), pos_and_siz_pair))
|
| 185 |
+
|
| 186 |
+
def __setitem__(self, key, val):
|
| 187 |
+
if self._readonly:
|
| 188 |
+
raise error('The database is opened for reading only')
|
| 189 |
+
if isinstance(key, str):
|
| 190 |
+
key = key.encode('utf-8')
|
| 191 |
+
elif not isinstance(key, (bytes, bytearray)):
|
| 192 |
+
raise TypeError("keys must be bytes or strings")
|
| 193 |
+
if isinstance(val, str):
|
| 194 |
+
val = val.encode('utf-8')
|
| 195 |
+
elif not isinstance(val, (bytes, bytearray)):
|
| 196 |
+
raise TypeError("values must be bytes or strings")
|
| 197 |
+
self._verify_open()
|
| 198 |
+
self._modified = True
|
| 199 |
+
if key not in self._index:
|
| 200 |
+
self._addkey(key, self._addval(val))
|
| 201 |
+
else:
|
| 202 |
+
# See whether the new value is small enough to fit in the
|
| 203 |
+
# (padded) space currently occupied by the old value.
|
| 204 |
+
pos, siz = self._index[key]
|
| 205 |
+
oldblocks = (siz + _BLOCKSIZE - 1) // _BLOCKSIZE
|
| 206 |
+
newblocks = (len(val) + _BLOCKSIZE - 1) // _BLOCKSIZE
|
| 207 |
+
if newblocks <= oldblocks:
|
| 208 |
+
self._index[key] = self._setval(pos, val)
|
| 209 |
+
else:
|
| 210 |
+
# The new value doesn't fit in the (padded) space used
|
| 211 |
+
# by the old value. The blocks used by the old value are
|
| 212 |
+
# forever lost.
|
| 213 |
+
self._index[key] = self._addval(val)
|
| 214 |
+
|
| 215 |
+
# Note that _index may be out of synch with the directory
|
| 216 |
+
# file now: _setval() and _addval() don't update the directory
|
| 217 |
+
# file. This also means that the on-disk directory and data
|
| 218 |
+
# files are in a mutually inconsistent state, and they'll
|
| 219 |
+
# remain that way until _commit() is called. Note that this
|
| 220 |
+
# is a disaster (for the database) if the program crashes
|
| 221 |
+
# (so that _commit() never gets called).
|
| 222 |
+
|
| 223 |
+
def __delitem__(self, key):
|
| 224 |
+
if self._readonly:
|
| 225 |
+
raise error('The database is opened for reading only')
|
| 226 |
+
if isinstance(key, str):
|
| 227 |
+
key = key.encode('utf-8')
|
| 228 |
+
self._verify_open()
|
| 229 |
+
self._modified = True
|
| 230 |
+
# The blocks used by the associated value are lost.
|
| 231 |
+
del self._index[key]
|
| 232 |
+
# XXX It's unclear why we do a _commit() here (the code always
|
| 233 |
+
# XXX has, so I'm not changing it). __setitem__ doesn't try to
|
| 234 |
+
# XXX keep the directory file in synch. Why should we? Or
|
| 235 |
+
# XXX why shouldn't __setitem__?
|
| 236 |
+
self._commit()
|
| 237 |
+
|
| 238 |
+
def keys(self):
|
| 239 |
+
try:
|
| 240 |
+
return list(self._index)
|
| 241 |
+
except TypeError:
|
| 242 |
+
raise error('DBM object has already been closed') from None
|
| 243 |
+
|
| 244 |
+
def items(self):
|
| 245 |
+
self._verify_open()
|
| 246 |
+
return [(key, self[key]) for key in self._index.keys()]
|
| 247 |
+
|
| 248 |
+
def __contains__(self, key):
|
| 249 |
+
if isinstance(key, str):
|
| 250 |
+
key = key.encode('utf-8')
|
| 251 |
+
try:
|
| 252 |
+
return key in self._index
|
| 253 |
+
except TypeError:
|
| 254 |
+
if self._index is None:
|
| 255 |
+
raise error('DBM object has already been closed') from None
|
| 256 |
+
else:
|
| 257 |
+
raise
|
| 258 |
+
|
| 259 |
+
def iterkeys(self):
|
| 260 |
+
try:
|
| 261 |
+
return iter(self._index)
|
| 262 |
+
except TypeError:
|
| 263 |
+
raise error('DBM object has already been closed') from None
|
| 264 |
+
__iter__ = iterkeys
|
| 265 |
+
|
| 266 |
+
def __len__(self):
|
| 267 |
+
try:
|
| 268 |
+
return len(self._index)
|
| 269 |
+
except TypeError:
|
| 270 |
+
raise error('DBM object has already been closed') from None
|
| 271 |
+
|
| 272 |
+
def close(self):
|
| 273 |
+
try:
|
| 274 |
+
self._commit()
|
| 275 |
+
finally:
|
| 276 |
+
self._index = self._datfile = self._dirfile = self._bakfile = None
|
| 277 |
+
|
| 278 |
+
__del__ = close
|
| 279 |
+
|
| 280 |
+
def _chmod(self, file):
|
| 281 |
+
self._os.chmod(file, self._mode)
|
| 282 |
+
|
| 283 |
+
def __enter__(self):
|
| 284 |
+
return self
|
| 285 |
+
|
| 286 |
+
def __exit__(self, *args):
|
| 287 |
+
self.close()
|
| 288 |
+
|
| 289 |
+
|
| 290 |
+
def open(file, flag='c', mode=0o666):
|
| 291 |
+
"""Open the database file, filename, and return corresponding object.
|
| 292 |
+
|
| 293 |
+
The flag argument, used to control how the database is opened in the
|
| 294 |
+
other DBM implementations, supports only the semantics of 'c' and 'n'
|
| 295 |
+
values. Other values will default to the semantics of 'c' value:
|
| 296 |
+
the database will always opened for update and will be created if it
|
| 297 |
+
does not exist.
|
| 298 |
+
|
| 299 |
+
The optional mode argument is the UNIX mode of the file, used only when
|
| 300 |
+
the database has to be created. It defaults to octal code 0o666 (and
|
| 301 |
+
will be modified by the prevailing umask).
|
| 302 |
+
|
| 303 |
+
"""
|
| 304 |
+
|
| 305 |
+
# Modify mode depending on the umask
|
| 306 |
+
try:
|
| 307 |
+
um = _os.umask(0)
|
| 308 |
+
_os.umask(um)
|
| 309 |
+
except AttributeError:
|
| 310 |
+
pass
|
| 311 |
+
else:
|
| 312 |
+
# Turn off any bits that are set in the umask
|
| 313 |
+
mode = mode & (~um)
|
| 314 |
+
if flag not in ('r', 'w', 'c', 'n'):
|
| 315 |
+
raise ValueError("Flag must be one of 'r', 'w', 'c', or 'n'")
|
| 316 |
+
return _Database(file, mode, flag=flag)
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/dbm/gnu.py
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Provide the _gdbm module as a dbm submodule."""
|
| 2 |
+
|
| 3 |
+
from _gdbm import *
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/dbm/ndbm.py
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Provide the _dbm module as a dbm submodule."""
|
| 2 |
+
|
| 3 |
+
from _dbm import *
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/email/__init__.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (C) 2001-2007 Python Software Foundation
|
| 2 |
+
# Author: Barry Warsaw
|
| 3 |
+
# Contact: email-sig@python.org
|
| 4 |
+
|
| 5 |
+
"""A package for parsing, handling, and generating email messages."""
|
| 6 |
+
|
| 7 |
+
__all__ = [
|
| 8 |
+
'base64mime',
|
| 9 |
+
'charset',
|
| 10 |
+
'encoders',
|
| 11 |
+
'errors',
|
| 12 |
+
'feedparser',
|
| 13 |
+
'generator',
|
| 14 |
+
'header',
|
| 15 |
+
'iterators',
|
| 16 |
+
'message',
|
| 17 |
+
'message_from_file',
|
| 18 |
+
'message_from_binary_file',
|
| 19 |
+
'message_from_string',
|
| 20 |
+
'message_from_bytes',
|
| 21 |
+
'mime',
|
| 22 |
+
'parser',
|
| 23 |
+
'quoprimime',
|
| 24 |
+
'utils',
|
| 25 |
+
]
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
# Some convenience routines. Don't import Parser and Message as side-effects
|
| 30 |
+
# of importing email since those cascadingly import most of the rest of the
|
| 31 |
+
# email package.
|
| 32 |
+
def message_from_string(s, *args, **kws):
|
| 33 |
+
"""Parse a string into a Message object model.
|
| 34 |
+
|
| 35 |
+
Optional _class and strict are passed to the Parser constructor.
|
| 36 |
+
"""
|
| 37 |
+
from email.parser import Parser
|
| 38 |
+
return Parser(*args, **kws).parsestr(s)
|
| 39 |
+
|
| 40 |
+
def message_from_bytes(s, *args, **kws):
|
| 41 |
+
"""Parse a bytes string into a Message object model.
|
| 42 |
+
|
| 43 |
+
Optional _class and strict are passed to the Parser constructor.
|
| 44 |
+
"""
|
| 45 |
+
from email.parser import BytesParser
|
| 46 |
+
return BytesParser(*args, **kws).parsebytes(s)
|
| 47 |
+
|
| 48 |
+
def message_from_file(fp, *args, **kws):
|
| 49 |
+
"""Read a file and parse its contents into a Message object model.
|
| 50 |
+
|
| 51 |
+
Optional _class and strict are passed to the Parser constructor.
|
| 52 |
+
"""
|
| 53 |
+
from email.parser import Parser
|
| 54 |
+
return Parser(*args, **kws).parse(fp)
|
| 55 |
+
|
| 56 |
+
def message_from_binary_file(fp, *args, **kws):
|
| 57 |
+
"""Read a binary file and parse its contents into a Message object model.
|
| 58 |
+
|
| 59 |
+
Optional _class and strict are passed to the Parser constructor.
|
| 60 |
+
"""
|
| 61 |
+
from email.parser import BytesParser
|
| 62 |
+
return BytesParser(*args, **kws).parse(fp)
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/email/_encoded_words.py
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
""" Routines for manipulating RFC2047 encoded words.
|
| 2 |
+
|
| 3 |
+
This is currently a package-private API, but will be considered for promotion
|
| 4 |
+
to a public API if there is demand.
|
| 5 |
+
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
# An ecoded word looks like this:
|
| 9 |
+
#
|
| 10 |
+
# =?charset[*lang]?cte?encoded_string?=
|
| 11 |
+
#
|
| 12 |
+
# for more information about charset see the charset module. Here it is one
|
| 13 |
+
# of the preferred MIME charset names (hopefully; you never know when parsing).
|
| 14 |
+
# cte (Content Transfer Encoding) is either 'q' or 'b' (ignoring case). In
|
| 15 |
+
# theory other letters could be used for other encodings, but in practice this
|
| 16 |
+
# (almost?) never happens. There could be a public API for adding entries
|
| 17 |
+
# to the CTE tables, but YAGNI for now. 'q' is Quoted Printable, 'b' is
|
| 18 |
+
# Base64. The meaning of encoded_string should be obvious. 'lang' is optional
|
| 19 |
+
# as indicated by the brackets (they are not part of the syntax) but is almost
|
| 20 |
+
# never encountered in practice.
|
| 21 |
+
#
|
| 22 |
+
# The general interface for a CTE decoder is that it takes the encoded_string
|
| 23 |
+
# as its argument, and returns a tuple (cte_decoded_string, defects). The
|
| 24 |
+
# cte_decoded_string is the original binary that was encoded using the
|
| 25 |
+
# specified cte. 'defects' is a list of MessageDefect instances indicating any
|
| 26 |
+
# problems encountered during conversion. 'charset' and 'lang' are the
|
| 27 |
+
# corresponding strings extracted from the EW, case preserved.
|
| 28 |
+
#
|
| 29 |
+
# The general interface for a CTE encoder is that it takes a binary sequence
|
| 30 |
+
# as input and returns the cte_encoded_string, which is an ascii-only string.
|
| 31 |
+
#
|
| 32 |
+
# Each decoder must also supply a length function that takes the binary
|
| 33 |
+
# sequence as its argument and returns the length of the resulting encoded
|
| 34 |
+
# string.
|
| 35 |
+
#
|
| 36 |
+
# The main API functions for the module are decode, which calls the decoder
|
| 37 |
+
# referenced by the cte specifier, and encode, which adds the appropriate
|
| 38 |
+
# RFC 2047 "chrome" to the encoded string, and can optionally automatically
|
| 39 |
+
# select the shortest possible encoding. See their docstrings below for
|
| 40 |
+
# details.
|
| 41 |
+
|
| 42 |
+
import re
|
| 43 |
+
import base64
|
| 44 |
+
import binascii
|
| 45 |
+
import functools
|
| 46 |
+
from string import ascii_letters, digits
|
| 47 |
+
from email import errors
|
| 48 |
+
|
| 49 |
+
__all__ = ['decode_q',
|
| 50 |
+
'encode_q',
|
| 51 |
+
'decode_b',
|
| 52 |
+
'encode_b',
|
| 53 |
+
'len_q',
|
| 54 |
+
'len_b',
|
| 55 |
+
'decode',
|
| 56 |
+
'encode',
|
| 57 |
+
]
|
| 58 |
+
|
| 59 |
+
#
|
| 60 |
+
# Quoted Printable
|
| 61 |
+
#
|
| 62 |
+
|
| 63 |
+
# regex based decoder.
|
| 64 |
+
_q_byte_subber = functools.partial(re.compile(br'=([a-fA-F0-9]{2})').sub,
|
| 65 |
+
lambda m: bytes.fromhex(m.group(1).decode()))
|
| 66 |
+
|
| 67 |
+
def decode_q(encoded):
|
| 68 |
+
encoded = encoded.replace(b'_', b' ')
|
| 69 |
+
return _q_byte_subber(encoded), []
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
# dict mapping bytes to their encoded form
|
| 73 |
+
class _QByteMap(dict):
|
| 74 |
+
|
| 75 |
+
safe = b'-!*+/' + ascii_letters.encode('ascii') + digits.encode('ascii')
|
| 76 |
+
|
| 77 |
+
def __missing__(self, key):
|
| 78 |
+
if key in self.safe:
|
| 79 |
+
self[key] = chr(key)
|
| 80 |
+
else:
|
| 81 |
+
self[key] = "={:02X}".format(key)
|
| 82 |
+
return self[key]
|
| 83 |
+
|
| 84 |
+
_q_byte_map = _QByteMap()
|
| 85 |
+
|
| 86 |
+
# In headers spaces are mapped to '_'.
|
| 87 |
+
_q_byte_map[ord(' ')] = '_'
|
| 88 |
+
|
| 89 |
+
def encode_q(bstring):
|
| 90 |
+
return ''.join(_q_byte_map[x] for x in bstring)
|
| 91 |
+
|
| 92 |
+
def len_q(bstring):
|
| 93 |
+
return sum(len(_q_byte_map[x]) for x in bstring)
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
#
|
| 97 |
+
# Base64
|
| 98 |
+
#
|
| 99 |
+
|
| 100 |
+
def decode_b(encoded):
|
| 101 |
+
# First try encoding with validate=True, fixing the padding if needed.
|
| 102 |
+
# This will succeed only if encoded includes no invalid characters.
|
| 103 |
+
pad_err = len(encoded) % 4
|
| 104 |
+
missing_padding = b'==='[:4-pad_err] if pad_err else b''
|
| 105 |
+
try:
|
| 106 |
+
return (
|
| 107 |
+
base64.b64decode(encoded + missing_padding, validate=True),
|
| 108 |
+
[errors.InvalidBase64PaddingDefect()] if pad_err else [],
|
| 109 |
+
)
|
| 110 |
+
except binascii.Error:
|
| 111 |
+
# Since we had correct padding, this is likely an invalid char error.
|
| 112 |
+
#
|
| 113 |
+
# The non-alphabet characters are ignored as far as padding
|
| 114 |
+
# goes, but we don't know how many there are. So try without adding
|
| 115 |
+
# padding to see if it works.
|
| 116 |
+
try:
|
| 117 |
+
return (
|
| 118 |
+
base64.b64decode(encoded, validate=False),
|
| 119 |
+
[errors.InvalidBase64CharactersDefect()],
|
| 120 |
+
)
|
| 121 |
+
except binascii.Error:
|
| 122 |
+
# Add as much padding as could possibly be necessary (extra padding
|
| 123 |
+
# is ignored).
|
| 124 |
+
try:
|
| 125 |
+
return (
|
| 126 |
+
base64.b64decode(encoded + b'==', validate=False),
|
| 127 |
+
[errors.InvalidBase64CharactersDefect(),
|
| 128 |
+
errors.InvalidBase64PaddingDefect()],
|
| 129 |
+
)
|
| 130 |
+
except binascii.Error:
|
| 131 |
+
# This only happens when the encoded string's length is 1 more
|
| 132 |
+
# than a multiple of 4, which is invalid.
|
| 133 |
+
#
|
| 134 |
+
# bpo-27397: Just return the encoded string since there's no
|
| 135 |
+
# way to decode.
|
| 136 |
+
return encoded, [errors.InvalidBase64LengthDefect()]
|
| 137 |
+
|
| 138 |
+
def encode_b(bstring):
|
| 139 |
+
return base64.b64encode(bstring).decode('ascii')
|
| 140 |
+
|
| 141 |
+
def len_b(bstring):
|
| 142 |
+
groups_of_3, leftover = divmod(len(bstring), 3)
|
| 143 |
+
# 4 bytes out for each 3 bytes (or nonzero fraction thereof) in.
|
| 144 |
+
return groups_of_3 * 4 + (4 if leftover else 0)
|
| 145 |
+
|
| 146 |
+
|
| 147 |
+
_cte_decoders = {
|
| 148 |
+
'q': decode_q,
|
| 149 |
+
'b': decode_b,
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
def decode(ew):
|
| 153 |
+
"""Decode encoded word and return (string, charset, lang, defects) tuple.
|
| 154 |
+
|
| 155 |
+
An RFC 2047/2243 encoded word has the form:
|
| 156 |
+
|
| 157 |
+
=?charset*lang?cte?encoded_string?=
|
| 158 |
+
|
| 159 |
+
where '*lang' may be omitted but the other parts may not be.
|
| 160 |
+
|
| 161 |
+
This function expects exactly such a string (that is, it does not check the
|
| 162 |
+
syntax and may raise errors if the string is not well formed), and returns
|
| 163 |
+
the encoded_string decoded first from its Content Transfer Encoding and
|
| 164 |
+
then from the resulting bytes into unicode using the specified charset. If
|
| 165 |
+
the cte-decoded string does not successfully decode using the specified
|
| 166 |
+
character set, a defect is added to the defects list and the unknown octets
|
| 167 |
+
are replaced by the unicode 'unknown' character \\uFDFF.
|
| 168 |
+
|
| 169 |
+
The specified charset and language are returned. The default for language,
|
| 170 |
+
which is rarely if ever encountered, is the empty string.
|
| 171 |
+
|
| 172 |
+
"""
|
| 173 |
+
_, charset, cte, cte_string, _ = ew.split('?')
|
| 174 |
+
charset, _, lang = charset.partition('*')
|
| 175 |
+
cte = cte.lower()
|
| 176 |
+
# Recover the original bytes and do CTE decoding.
|
| 177 |
+
bstring = cte_string.encode('ascii', 'surrogateescape')
|
| 178 |
+
bstring, defects = _cte_decoders[cte](bstring)
|
| 179 |
+
# Turn the CTE decoded bytes into unicode.
|
| 180 |
+
try:
|
| 181 |
+
string = bstring.decode(charset)
|
| 182 |
+
except UnicodeError:
|
| 183 |
+
defects.append(errors.UndecodableBytesDefect("Encoded word "
|
| 184 |
+
"contains bytes not decodable using {} charset".format(charset)))
|
| 185 |
+
string = bstring.decode(charset, 'surrogateescape')
|
| 186 |
+
except LookupError:
|
| 187 |
+
string = bstring.decode('ascii', 'surrogateescape')
|
| 188 |
+
if charset.lower() != 'unknown-8bit':
|
| 189 |
+
defects.append(errors.CharsetError("Unknown charset {} "
|
| 190 |
+
"in encoded word; decoded as unknown bytes".format(charset)))
|
| 191 |
+
return string, charset, lang, defects
|
| 192 |
+
|
| 193 |
+
|
| 194 |
+
_cte_encoders = {
|
| 195 |
+
'q': encode_q,
|
| 196 |
+
'b': encode_b,
|
| 197 |
+
}
|
| 198 |
+
|
| 199 |
+
_cte_encode_length = {
|
| 200 |
+
'q': len_q,
|
| 201 |
+
'b': len_b,
|
| 202 |
+
}
|
| 203 |
+
|
| 204 |
+
def encode(string, charset='utf-8', encoding=None, lang=''):
|
| 205 |
+
"""Encode string using the CTE encoding that produces the shorter result.
|
| 206 |
+
|
| 207 |
+
Produces an RFC 2047/2243 encoded word of the form:
|
| 208 |
+
|
| 209 |
+
=?charset*lang?cte?encoded_string?=
|
| 210 |
+
|
| 211 |
+
where '*lang' is omitted unless the 'lang' parameter is given a value.
|
| 212 |
+
Optional argument charset (defaults to utf-8) specifies the charset to use
|
| 213 |
+
to encode the string to binary before CTE encoding it. Optional argument
|
| 214 |
+
'encoding' is the cte specifier for the encoding that should be used ('q'
|
| 215 |
+
or 'b'); if it is None (the default) the encoding which produces the
|
| 216 |
+
shortest encoded sequence is used, except that 'q' is preferred if it is up
|
| 217 |
+
to five characters longer. Optional argument 'lang' (default '') gives the
|
| 218 |
+
RFC 2243 language string to specify in the encoded word.
|
| 219 |
+
|
| 220 |
+
"""
|
| 221 |
+
if charset == 'unknown-8bit':
|
| 222 |
+
bstring = string.encode('ascii', 'surrogateescape')
|
| 223 |
+
else:
|
| 224 |
+
bstring = string.encode(charset)
|
| 225 |
+
if encoding is None:
|
| 226 |
+
qlen = _cte_encode_length['q'](bstring)
|
| 227 |
+
blen = _cte_encode_length['b'](bstring)
|
| 228 |
+
# Bias toward q. 5 is arbitrary.
|
| 229 |
+
encoding = 'q' if qlen - blen < 5 else 'b'
|
| 230 |
+
encoded = _cte_encoders[encoding](bstring)
|
| 231 |
+
if lang:
|
| 232 |
+
lang = '*' + lang
|
| 233 |
+
return "=?{}{}?{}?{}?=".format(charset, lang, encoding, encoded)
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/email/_header_value_parser.py
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/email/_parseaddr.py
ADDED
|
@@ -0,0 +1,551 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (C) 2002-2007 Python Software Foundation
|
| 2 |
+
# Contact: email-sig@python.org
|
| 3 |
+
|
| 4 |
+
"""Email address parsing code.
|
| 5 |
+
|
| 6 |
+
Lifted directly from rfc822.py. This should eventually be rewritten.
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
__all__ = [
|
| 10 |
+
'mktime_tz',
|
| 11 |
+
'parsedate',
|
| 12 |
+
'parsedate_tz',
|
| 13 |
+
'quote',
|
| 14 |
+
]
|
| 15 |
+
|
| 16 |
+
import time, calendar
|
| 17 |
+
|
| 18 |
+
SPACE = ' '
|
| 19 |
+
EMPTYSTRING = ''
|
| 20 |
+
COMMASPACE = ', '
|
| 21 |
+
|
| 22 |
+
# Parse a date field
|
| 23 |
+
_monthnames = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul',
|
| 24 |
+
'aug', 'sep', 'oct', 'nov', 'dec',
|
| 25 |
+
'january', 'february', 'march', 'april', 'may', 'june', 'july',
|
| 26 |
+
'august', 'september', 'october', 'november', 'december']
|
| 27 |
+
|
| 28 |
+
_daynames = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']
|
| 29 |
+
|
| 30 |
+
# The timezone table does not include the military time zones defined
|
| 31 |
+
# in RFC822, other than Z. According to RFC1123, the description in
|
| 32 |
+
# RFC822 gets the signs wrong, so we can't rely on any such time
|
| 33 |
+
# zones. RFC1123 recommends that numeric timezone indicators be used
|
| 34 |
+
# instead of timezone names.
|
| 35 |
+
|
| 36 |
+
_timezones = {'UT':0, 'UTC':0, 'GMT':0, 'Z':0,
|
| 37 |
+
'AST': -400, 'ADT': -300, # Atlantic (used in Canada)
|
| 38 |
+
'EST': -500, 'EDT': -400, # Eastern
|
| 39 |
+
'CST': -600, 'CDT': -500, # Central
|
| 40 |
+
'MST': -700, 'MDT': -600, # Mountain
|
| 41 |
+
'PST': -800, 'PDT': -700 # Pacific
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def parsedate_tz(data):
|
| 46 |
+
"""Convert a date string to a time tuple.
|
| 47 |
+
|
| 48 |
+
Accounts for military timezones.
|
| 49 |
+
"""
|
| 50 |
+
res = _parsedate_tz(data)
|
| 51 |
+
if not res:
|
| 52 |
+
return
|
| 53 |
+
if res[9] is None:
|
| 54 |
+
res[9] = 0
|
| 55 |
+
return tuple(res)
|
| 56 |
+
|
| 57 |
+
def _parsedate_tz(data):
|
| 58 |
+
"""Convert date to extended time tuple.
|
| 59 |
+
|
| 60 |
+
The last (additional) element is the time zone offset in seconds, except if
|
| 61 |
+
the timezone was specified as -0000. In that case the last element is
|
| 62 |
+
None. This indicates a UTC timestamp that explicitly declaims knowledge of
|
| 63 |
+
the source timezone, as opposed to a +0000 timestamp that indicates the
|
| 64 |
+
source timezone really was UTC.
|
| 65 |
+
|
| 66 |
+
"""
|
| 67 |
+
if not data:
|
| 68 |
+
return
|
| 69 |
+
data = data.split()
|
| 70 |
+
if not data: # This happens for whitespace-only input.
|
| 71 |
+
return None
|
| 72 |
+
# The FWS after the comma after the day-of-week is optional, so search and
|
| 73 |
+
# adjust for this.
|
| 74 |
+
if data[0].endswith(',') or data[0].lower() in _daynames:
|
| 75 |
+
# There's a dayname here. Skip it
|
| 76 |
+
del data[0]
|
| 77 |
+
else:
|
| 78 |
+
i = data[0].rfind(',')
|
| 79 |
+
if i >= 0:
|
| 80 |
+
data[0] = data[0][i+1:]
|
| 81 |
+
if len(data) == 3: # RFC 850 date, deprecated
|
| 82 |
+
stuff = data[0].split('-')
|
| 83 |
+
if len(stuff) == 3:
|
| 84 |
+
data = stuff + data[1:]
|
| 85 |
+
if len(data) == 4:
|
| 86 |
+
s = data[3]
|
| 87 |
+
i = s.find('+')
|
| 88 |
+
if i == -1:
|
| 89 |
+
i = s.find('-')
|
| 90 |
+
if i > 0:
|
| 91 |
+
data[3:] = [s[:i], s[i:]]
|
| 92 |
+
else:
|
| 93 |
+
data.append('') # Dummy tz
|
| 94 |
+
if len(data) < 5:
|
| 95 |
+
return None
|
| 96 |
+
data = data[:5]
|
| 97 |
+
[dd, mm, yy, tm, tz] = data
|
| 98 |
+
mm = mm.lower()
|
| 99 |
+
if mm not in _monthnames:
|
| 100 |
+
dd, mm = mm, dd.lower()
|
| 101 |
+
if mm not in _monthnames:
|
| 102 |
+
return None
|
| 103 |
+
mm = _monthnames.index(mm) + 1
|
| 104 |
+
if mm > 12:
|
| 105 |
+
mm -= 12
|
| 106 |
+
if dd[-1] == ',':
|
| 107 |
+
dd = dd[:-1]
|
| 108 |
+
i = yy.find(':')
|
| 109 |
+
if i > 0:
|
| 110 |
+
yy, tm = tm, yy
|
| 111 |
+
if yy[-1] == ',':
|
| 112 |
+
yy = yy[:-1]
|
| 113 |
+
if not yy[0].isdigit():
|
| 114 |
+
yy, tz = tz, yy
|
| 115 |
+
if tm[-1] == ',':
|
| 116 |
+
tm = tm[:-1]
|
| 117 |
+
tm = tm.split(':')
|
| 118 |
+
if len(tm) == 2:
|
| 119 |
+
[thh, tmm] = tm
|
| 120 |
+
tss = '0'
|
| 121 |
+
elif len(tm) == 3:
|
| 122 |
+
[thh, tmm, tss] = tm
|
| 123 |
+
elif len(tm) == 1 and '.' in tm[0]:
|
| 124 |
+
# Some non-compliant MUAs use '.' to separate time elements.
|
| 125 |
+
tm = tm[0].split('.')
|
| 126 |
+
if len(tm) == 2:
|
| 127 |
+
[thh, tmm] = tm
|
| 128 |
+
tss = 0
|
| 129 |
+
elif len(tm) == 3:
|
| 130 |
+
[thh, tmm, tss] = tm
|
| 131 |
+
else:
|
| 132 |
+
return None
|
| 133 |
+
try:
|
| 134 |
+
yy = int(yy)
|
| 135 |
+
dd = int(dd)
|
| 136 |
+
thh = int(thh)
|
| 137 |
+
tmm = int(tmm)
|
| 138 |
+
tss = int(tss)
|
| 139 |
+
except ValueError:
|
| 140 |
+
return None
|
| 141 |
+
# Check for a yy specified in two-digit format, then convert it to the
|
| 142 |
+
# appropriate four-digit format, according to the POSIX standard. RFC 822
|
| 143 |
+
# calls for a two-digit yy, but RFC 2822 (which obsoletes RFC 822)
|
| 144 |
+
# mandates a 4-digit yy. For more information, see the documentation for
|
| 145 |
+
# the time module.
|
| 146 |
+
if yy < 100:
|
| 147 |
+
# The year is between 1969 and 1999 (inclusive).
|
| 148 |
+
if yy > 68:
|
| 149 |
+
yy += 1900
|
| 150 |
+
# The year is between 2000 and 2068 (inclusive).
|
| 151 |
+
else:
|
| 152 |
+
yy += 2000
|
| 153 |
+
tzoffset = None
|
| 154 |
+
tz = tz.upper()
|
| 155 |
+
if tz in _timezones:
|
| 156 |
+
tzoffset = _timezones[tz]
|
| 157 |
+
else:
|
| 158 |
+
try:
|
| 159 |
+
tzoffset = int(tz)
|
| 160 |
+
except ValueError:
|
| 161 |
+
pass
|
| 162 |
+
if tzoffset==0 and tz.startswith('-'):
|
| 163 |
+
tzoffset = None
|
| 164 |
+
# Convert a timezone offset into seconds ; -0500 -> -18000
|
| 165 |
+
if tzoffset:
|
| 166 |
+
if tzoffset < 0:
|
| 167 |
+
tzsign = -1
|
| 168 |
+
tzoffset = -tzoffset
|
| 169 |
+
else:
|
| 170 |
+
tzsign = 1
|
| 171 |
+
tzoffset = tzsign * ( (tzoffset//100)*3600 + (tzoffset % 100)*60)
|
| 172 |
+
# Daylight Saving Time flag is set to -1, since DST is unknown.
|
| 173 |
+
return [yy, mm, dd, thh, tmm, tss, 0, 1, -1, tzoffset]
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
def parsedate(data):
|
| 177 |
+
"""Convert a time string to a time tuple."""
|
| 178 |
+
t = parsedate_tz(data)
|
| 179 |
+
if isinstance(t, tuple):
|
| 180 |
+
return t[:9]
|
| 181 |
+
else:
|
| 182 |
+
return t
|
| 183 |
+
|
| 184 |
+
|
| 185 |
+
def mktime_tz(data):
|
| 186 |
+
"""Turn a 10-tuple as returned by parsedate_tz() into a POSIX timestamp."""
|
| 187 |
+
if data[9] is None:
|
| 188 |
+
# No zone info, so localtime is better assumption than GMT
|
| 189 |
+
return time.mktime(data[:8] + (-1,))
|
| 190 |
+
else:
|
| 191 |
+
t = calendar.timegm(data)
|
| 192 |
+
return t - data[9]
|
| 193 |
+
|
| 194 |
+
|
| 195 |
+
def quote(str):
|
| 196 |
+
"""Prepare string to be used in a quoted string.
|
| 197 |
+
|
| 198 |
+
Turns backslash and double quote characters into quoted pairs. These
|
| 199 |
+
are the only characters that need to be quoted inside a quoted string.
|
| 200 |
+
Does not add the surrounding double quotes.
|
| 201 |
+
"""
|
| 202 |
+
return str.replace('\\', '\\\\').replace('"', '\\"')
|
| 203 |
+
|
| 204 |
+
|
| 205 |
+
class AddrlistClass:
|
| 206 |
+
"""Address parser class by Ben Escoto.
|
| 207 |
+
|
| 208 |
+
To understand what this class does, it helps to have a copy of RFC 2822 in
|
| 209 |
+
front of you.
|
| 210 |
+
|
| 211 |
+
Note: this class interface is deprecated and may be removed in the future.
|
| 212 |
+
Use email.utils.AddressList instead.
|
| 213 |
+
"""
|
| 214 |
+
|
| 215 |
+
def __init__(self, field):
|
| 216 |
+
"""Initialize a new instance.
|
| 217 |
+
|
| 218 |
+
`field' is an unparsed address header field, containing
|
| 219 |
+
one or more addresses.
|
| 220 |
+
"""
|
| 221 |
+
self.specials = '()<>@,:;.\"[]'
|
| 222 |
+
self.pos = 0
|
| 223 |
+
self.LWS = ' \t'
|
| 224 |
+
self.CR = '\r\n'
|
| 225 |
+
self.FWS = self.LWS + self.CR
|
| 226 |
+
self.atomends = self.specials + self.LWS + self.CR
|
| 227 |
+
# Note that RFC 2822 now specifies `.' as obs-phrase, meaning that it
|
| 228 |
+
# is obsolete syntax. RFC 2822 requires that we recognize obsolete
|
| 229 |
+
# syntax, so allow dots in phrases.
|
| 230 |
+
self.phraseends = self.atomends.replace('.', '')
|
| 231 |
+
self.field = field
|
| 232 |
+
self.commentlist = []
|
| 233 |
+
|
| 234 |
+
def gotonext(self):
|
| 235 |
+
"""Skip white space and extract comments."""
|
| 236 |
+
wslist = []
|
| 237 |
+
while self.pos < len(self.field):
|
| 238 |
+
if self.field[self.pos] in self.LWS + '\n\r':
|
| 239 |
+
if self.field[self.pos] not in '\n\r':
|
| 240 |
+
wslist.append(self.field[self.pos])
|
| 241 |
+
self.pos += 1
|
| 242 |
+
elif self.field[self.pos] == '(':
|
| 243 |
+
self.commentlist.append(self.getcomment())
|
| 244 |
+
else:
|
| 245 |
+
break
|
| 246 |
+
return EMPTYSTRING.join(wslist)
|
| 247 |
+
|
| 248 |
+
def getaddrlist(self):
|
| 249 |
+
"""Parse all addresses.
|
| 250 |
+
|
| 251 |
+
Returns a list containing all of the addresses.
|
| 252 |
+
"""
|
| 253 |
+
result = []
|
| 254 |
+
while self.pos < len(self.field):
|
| 255 |
+
ad = self.getaddress()
|
| 256 |
+
if ad:
|
| 257 |
+
result += ad
|
| 258 |
+
else:
|
| 259 |
+
result.append(('', ''))
|
| 260 |
+
return result
|
| 261 |
+
|
| 262 |
+
def getaddress(self):
|
| 263 |
+
"""Parse the next address."""
|
| 264 |
+
self.commentlist = []
|
| 265 |
+
self.gotonext()
|
| 266 |
+
|
| 267 |
+
oldpos = self.pos
|
| 268 |
+
oldcl = self.commentlist
|
| 269 |
+
plist = self.getphraselist()
|
| 270 |
+
|
| 271 |
+
self.gotonext()
|
| 272 |
+
returnlist = []
|
| 273 |
+
|
| 274 |
+
if self.pos >= len(self.field):
|
| 275 |
+
# Bad email address technically, no domain.
|
| 276 |
+
if plist:
|
| 277 |
+
returnlist = [(SPACE.join(self.commentlist), plist[0])]
|
| 278 |
+
|
| 279 |
+
elif self.field[self.pos] in '.@':
|
| 280 |
+
# email address is just an addrspec
|
| 281 |
+
# this isn't very efficient since we start over
|
| 282 |
+
self.pos = oldpos
|
| 283 |
+
self.commentlist = oldcl
|
| 284 |
+
addrspec = self.getaddrspec()
|
| 285 |
+
returnlist = [(SPACE.join(self.commentlist), addrspec)]
|
| 286 |
+
|
| 287 |
+
elif self.field[self.pos] == ':':
|
| 288 |
+
# address is a group
|
| 289 |
+
returnlist = []
|
| 290 |
+
|
| 291 |
+
fieldlen = len(self.field)
|
| 292 |
+
self.pos += 1
|
| 293 |
+
while self.pos < len(self.field):
|
| 294 |
+
self.gotonext()
|
| 295 |
+
if self.pos < fieldlen and self.field[self.pos] == ';':
|
| 296 |
+
self.pos += 1
|
| 297 |
+
break
|
| 298 |
+
returnlist = returnlist + self.getaddress()
|
| 299 |
+
|
| 300 |
+
elif self.field[self.pos] == '<':
|
| 301 |
+
# Address is a phrase then a route addr
|
| 302 |
+
routeaddr = self.getrouteaddr()
|
| 303 |
+
|
| 304 |
+
if self.commentlist:
|
| 305 |
+
returnlist = [(SPACE.join(plist) + ' (' +
|
| 306 |
+
' '.join(self.commentlist) + ')', routeaddr)]
|
| 307 |
+
else:
|
| 308 |
+
returnlist = [(SPACE.join(plist), routeaddr)]
|
| 309 |
+
|
| 310 |
+
else:
|
| 311 |
+
if plist:
|
| 312 |
+
returnlist = [(SPACE.join(self.commentlist), plist[0])]
|
| 313 |
+
elif self.field[self.pos] in self.specials:
|
| 314 |
+
self.pos += 1
|
| 315 |
+
|
| 316 |
+
self.gotonext()
|
| 317 |
+
if self.pos < len(self.field) and self.field[self.pos] == ',':
|
| 318 |
+
self.pos += 1
|
| 319 |
+
return returnlist
|
| 320 |
+
|
| 321 |
+
def getrouteaddr(self):
|
| 322 |
+
"""Parse a route address (Return-path value).
|
| 323 |
+
|
| 324 |
+
This method just skips all the route stuff and returns the addrspec.
|
| 325 |
+
"""
|
| 326 |
+
if self.field[self.pos] != '<':
|
| 327 |
+
return
|
| 328 |
+
|
| 329 |
+
expectroute = False
|
| 330 |
+
self.pos += 1
|
| 331 |
+
self.gotonext()
|
| 332 |
+
adlist = ''
|
| 333 |
+
while self.pos < len(self.field):
|
| 334 |
+
if expectroute:
|
| 335 |
+
self.getdomain()
|
| 336 |
+
expectroute = False
|
| 337 |
+
elif self.field[self.pos] == '>':
|
| 338 |
+
self.pos += 1
|
| 339 |
+
break
|
| 340 |
+
elif self.field[self.pos] == '@':
|
| 341 |
+
self.pos += 1
|
| 342 |
+
expectroute = True
|
| 343 |
+
elif self.field[self.pos] == ':':
|
| 344 |
+
self.pos += 1
|
| 345 |
+
else:
|
| 346 |
+
adlist = self.getaddrspec()
|
| 347 |
+
self.pos += 1
|
| 348 |
+
break
|
| 349 |
+
self.gotonext()
|
| 350 |
+
|
| 351 |
+
return adlist
|
| 352 |
+
|
| 353 |
+
def getaddrspec(self):
|
| 354 |
+
"""Parse an RFC 2822 addr-spec."""
|
| 355 |
+
aslist = []
|
| 356 |
+
|
| 357 |
+
self.gotonext()
|
| 358 |
+
while self.pos < len(self.field):
|
| 359 |
+
preserve_ws = True
|
| 360 |
+
if self.field[self.pos] == '.':
|
| 361 |
+
if aslist and not aslist[-1].strip():
|
| 362 |
+
aslist.pop()
|
| 363 |
+
aslist.append('.')
|
| 364 |
+
self.pos += 1
|
| 365 |
+
preserve_ws = False
|
| 366 |
+
elif self.field[self.pos] == '"':
|
| 367 |
+
aslist.append('"%s"' % quote(self.getquote()))
|
| 368 |
+
elif self.field[self.pos] in self.atomends:
|
| 369 |
+
if aslist and not aslist[-1].strip():
|
| 370 |
+
aslist.pop()
|
| 371 |
+
break
|
| 372 |
+
else:
|
| 373 |
+
aslist.append(self.getatom())
|
| 374 |
+
ws = self.gotonext()
|
| 375 |
+
if preserve_ws and ws:
|
| 376 |
+
aslist.append(ws)
|
| 377 |
+
|
| 378 |
+
if self.pos >= len(self.field) or self.field[self.pos] != '@':
|
| 379 |
+
return EMPTYSTRING.join(aslist)
|
| 380 |
+
|
| 381 |
+
aslist.append('@')
|
| 382 |
+
self.pos += 1
|
| 383 |
+
self.gotonext()
|
| 384 |
+
domain = self.getdomain()
|
| 385 |
+
if not domain:
|
| 386 |
+
# Invalid domain, return an empty address instead of returning a
|
| 387 |
+
# local part to denote failed parsing.
|
| 388 |
+
return EMPTYSTRING
|
| 389 |
+
return EMPTYSTRING.join(aslist) + domain
|
| 390 |
+
|
| 391 |
+
def getdomain(self):
|
| 392 |
+
"""Get the complete domain name from an address."""
|
| 393 |
+
sdlist = []
|
| 394 |
+
while self.pos < len(self.field):
|
| 395 |
+
if self.field[self.pos] in self.LWS:
|
| 396 |
+
self.pos += 1
|
| 397 |
+
elif self.field[self.pos] == '(':
|
| 398 |
+
self.commentlist.append(self.getcomment())
|
| 399 |
+
elif self.field[self.pos] == '[':
|
| 400 |
+
sdlist.append(self.getdomainliteral())
|
| 401 |
+
elif self.field[self.pos] == '.':
|
| 402 |
+
self.pos += 1
|
| 403 |
+
sdlist.append('.')
|
| 404 |
+
elif self.field[self.pos] == '@':
|
| 405 |
+
# bpo-34155: Don't parse domains with two `@` like
|
| 406 |
+
# `a@malicious.org@important.com`.
|
| 407 |
+
return EMPTYSTRING
|
| 408 |
+
elif self.field[self.pos] in self.atomends:
|
| 409 |
+
break
|
| 410 |
+
else:
|
| 411 |
+
sdlist.append(self.getatom())
|
| 412 |
+
return EMPTYSTRING.join(sdlist)
|
| 413 |
+
|
| 414 |
+
def getdelimited(self, beginchar, endchars, allowcomments=True):
|
| 415 |
+
"""Parse a header fragment delimited by special characters.
|
| 416 |
+
|
| 417 |
+
`beginchar' is the start character for the fragment.
|
| 418 |
+
If self is not looking at an instance of `beginchar' then
|
| 419 |
+
getdelimited returns the empty string.
|
| 420 |
+
|
| 421 |
+
`endchars' is a sequence of allowable end-delimiting characters.
|
| 422 |
+
Parsing stops when one of these is encountered.
|
| 423 |
+
|
| 424 |
+
If `allowcomments' is non-zero, embedded RFC 2822 comments are allowed
|
| 425 |
+
within the parsed fragment.
|
| 426 |
+
"""
|
| 427 |
+
if self.field[self.pos] != beginchar:
|
| 428 |
+
return ''
|
| 429 |
+
|
| 430 |
+
slist = ['']
|
| 431 |
+
quote = False
|
| 432 |
+
self.pos += 1
|
| 433 |
+
while self.pos < len(self.field):
|
| 434 |
+
if quote:
|
| 435 |
+
slist.append(self.field[self.pos])
|
| 436 |
+
quote = False
|
| 437 |
+
elif self.field[self.pos] in endchars:
|
| 438 |
+
self.pos += 1
|
| 439 |
+
break
|
| 440 |
+
elif allowcomments and self.field[self.pos] == '(':
|
| 441 |
+
slist.append(self.getcomment())
|
| 442 |
+
continue # have already advanced pos from getcomment
|
| 443 |
+
elif self.field[self.pos] == '\\':
|
| 444 |
+
quote = True
|
| 445 |
+
else:
|
| 446 |
+
slist.append(self.field[self.pos])
|
| 447 |
+
self.pos += 1
|
| 448 |
+
|
| 449 |
+
return EMPTYSTRING.join(slist)
|
| 450 |
+
|
| 451 |
+
def getquote(self):
|
| 452 |
+
"""Get a quote-delimited fragment from self's field."""
|
| 453 |
+
return self.getdelimited('"', '"\r', False)
|
| 454 |
+
|
| 455 |
+
def getcomment(self):
|
| 456 |
+
"""Get a parenthesis-delimited fragment from self's field."""
|
| 457 |
+
return self.getdelimited('(', ')\r', True)
|
| 458 |
+
|
| 459 |
+
def getdomainliteral(self):
|
| 460 |
+
"""Parse an RFC 2822 domain-literal."""
|
| 461 |
+
return '[%s]' % self.getdelimited('[', ']\r', False)
|
| 462 |
+
|
| 463 |
+
def getatom(self, atomends=None):
|
| 464 |
+
"""Parse an RFC 2822 atom.
|
| 465 |
+
|
| 466 |
+
Optional atomends specifies a different set of end token delimiters
|
| 467 |
+
(the default is to use self.atomends). This is used e.g. in
|
| 468 |
+
getphraselist() since phrase endings must not include the `.' (which
|
| 469 |
+
is legal in phrases)."""
|
| 470 |
+
atomlist = ['']
|
| 471 |
+
if atomends is None:
|
| 472 |
+
atomends = self.atomends
|
| 473 |
+
|
| 474 |
+
while self.pos < len(self.field):
|
| 475 |
+
if self.field[self.pos] in atomends:
|
| 476 |
+
break
|
| 477 |
+
else:
|
| 478 |
+
atomlist.append(self.field[self.pos])
|
| 479 |
+
self.pos += 1
|
| 480 |
+
|
| 481 |
+
return EMPTYSTRING.join(atomlist)
|
| 482 |
+
|
| 483 |
+
def getphraselist(self):
|
| 484 |
+
"""Parse a sequence of RFC 2822 phrases.
|
| 485 |
+
|
| 486 |
+
A phrase is a sequence of words, which are in turn either RFC 2822
|
| 487 |
+
atoms or quoted-strings. Phrases are canonicalized by squeezing all
|
| 488 |
+
runs of continuous whitespace into one space.
|
| 489 |
+
"""
|
| 490 |
+
plist = []
|
| 491 |
+
|
| 492 |
+
while self.pos < len(self.field):
|
| 493 |
+
if self.field[self.pos] in self.FWS:
|
| 494 |
+
self.pos += 1
|
| 495 |
+
elif self.field[self.pos] == '"':
|
| 496 |
+
plist.append(self.getquote())
|
| 497 |
+
elif self.field[self.pos] == '(':
|
| 498 |
+
self.commentlist.append(self.getcomment())
|
| 499 |
+
elif self.field[self.pos] in self.phraseends:
|
| 500 |
+
break
|
| 501 |
+
else:
|
| 502 |
+
plist.append(self.getatom(self.phraseends))
|
| 503 |
+
|
| 504 |
+
return plist
|
| 505 |
+
|
| 506 |
+
class AddressList(AddrlistClass):
|
| 507 |
+
"""An AddressList encapsulates a list of parsed RFC 2822 addresses."""
|
| 508 |
+
def __init__(self, field):
|
| 509 |
+
AddrlistClass.__init__(self, field)
|
| 510 |
+
if field:
|
| 511 |
+
self.addresslist = self.getaddrlist()
|
| 512 |
+
else:
|
| 513 |
+
self.addresslist = []
|
| 514 |
+
|
| 515 |
+
def __len__(self):
|
| 516 |
+
return len(self.addresslist)
|
| 517 |
+
|
| 518 |
+
def __add__(self, other):
|
| 519 |
+
# Set union
|
| 520 |
+
newaddr = AddressList(None)
|
| 521 |
+
newaddr.addresslist = self.addresslist[:]
|
| 522 |
+
for x in other.addresslist:
|
| 523 |
+
if not x in self.addresslist:
|
| 524 |
+
newaddr.addresslist.append(x)
|
| 525 |
+
return newaddr
|
| 526 |
+
|
| 527 |
+
def __iadd__(self, other):
|
| 528 |
+
# Set union, in-place
|
| 529 |
+
for x in other.addresslist:
|
| 530 |
+
if not x in self.addresslist:
|
| 531 |
+
self.addresslist.append(x)
|
| 532 |
+
return self
|
| 533 |
+
|
| 534 |
+
def __sub__(self, other):
|
| 535 |
+
# Set difference
|
| 536 |
+
newaddr = AddressList(None)
|
| 537 |
+
for x in self.addresslist:
|
| 538 |
+
if not x in other.addresslist:
|
| 539 |
+
newaddr.addresslist.append(x)
|
| 540 |
+
return newaddr
|
| 541 |
+
|
| 542 |
+
def __isub__(self, other):
|
| 543 |
+
# Set difference, in-place
|
| 544 |
+
for x in other.addresslist:
|
| 545 |
+
if x in self.addresslist:
|
| 546 |
+
self.addresslist.remove(x)
|
| 547 |
+
return self
|
| 548 |
+
|
| 549 |
+
def __getitem__(self, index):
|
| 550 |
+
# Make indexing, slices, and 'in' work
|
| 551 |
+
return self.addresslist[index]
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/email/_policybase.py
ADDED
|
@@ -0,0 +1,374 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Policy framework for the email package.
|
| 2 |
+
|
| 3 |
+
Allows fine grained feature control of how the package parses and emits data.
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import abc
|
| 7 |
+
from email import header
|
| 8 |
+
from email import charset as _charset
|
| 9 |
+
from email.utils import _has_surrogates
|
| 10 |
+
|
| 11 |
+
__all__ = [
|
| 12 |
+
'Policy',
|
| 13 |
+
'Compat32',
|
| 14 |
+
'compat32',
|
| 15 |
+
]
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
class _PolicyBase:
|
| 19 |
+
|
| 20 |
+
"""Policy Object basic framework.
|
| 21 |
+
|
| 22 |
+
This class is useless unless subclassed. A subclass should define
|
| 23 |
+
class attributes with defaults for any values that are to be
|
| 24 |
+
managed by the Policy object. The constructor will then allow
|
| 25 |
+
non-default values to be set for these attributes at instance
|
| 26 |
+
creation time. The instance will be callable, taking these same
|
| 27 |
+
attributes keyword arguments, and returning a new instance
|
| 28 |
+
identical to the called instance except for those values changed
|
| 29 |
+
by the keyword arguments. Instances may be added, yielding new
|
| 30 |
+
instances with any non-default values from the right hand
|
| 31 |
+
operand overriding those in the left hand operand. That is,
|
| 32 |
+
|
| 33 |
+
A + B == A(<non-default values of B>)
|
| 34 |
+
|
| 35 |
+
The repr of an instance can be used to reconstruct the object
|
| 36 |
+
if and only if the repr of the values can be used to reconstruct
|
| 37 |
+
those values.
|
| 38 |
+
|
| 39 |
+
"""
|
| 40 |
+
|
| 41 |
+
def __init__(self, **kw):
|
| 42 |
+
"""Create new Policy, possibly overriding some defaults.
|
| 43 |
+
|
| 44 |
+
See class docstring for a list of overridable attributes.
|
| 45 |
+
|
| 46 |
+
"""
|
| 47 |
+
for name, value in kw.items():
|
| 48 |
+
if hasattr(self, name):
|
| 49 |
+
super(_PolicyBase,self).__setattr__(name, value)
|
| 50 |
+
else:
|
| 51 |
+
raise TypeError(
|
| 52 |
+
"{!r} is an invalid keyword argument for {}".format(
|
| 53 |
+
name, self.__class__.__name__))
|
| 54 |
+
|
| 55 |
+
def __repr__(self):
|
| 56 |
+
args = [ "{}={!r}".format(name, value)
|
| 57 |
+
for name, value in self.__dict__.items() ]
|
| 58 |
+
return "{}({})".format(self.__class__.__name__, ', '.join(args))
|
| 59 |
+
|
| 60 |
+
def clone(self, **kw):
|
| 61 |
+
"""Return a new instance with specified attributes changed.
|
| 62 |
+
|
| 63 |
+
The new instance has the same attribute values as the current object,
|
| 64 |
+
except for the changes passed in as keyword arguments.
|
| 65 |
+
|
| 66 |
+
"""
|
| 67 |
+
newpolicy = self.__class__.__new__(self.__class__)
|
| 68 |
+
for attr, value in self.__dict__.items():
|
| 69 |
+
object.__setattr__(newpolicy, attr, value)
|
| 70 |
+
for attr, value in kw.items():
|
| 71 |
+
if not hasattr(self, attr):
|
| 72 |
+
raise TypeError(
|
| 73 |
+
"{!r} is an invalid keyword argument for {}".format(
|
| 74 |
+
attr, self.__class__.__name__))
|
| 75 |
+
object.__setattr__(newpolicy, attr, value)
|
| 76 |
+
return newpolicy
|
| 77 |
+
|
| 78 |
+
def __setattr__(self, name, value):
|
| 79 |
+
if hasattr(self, name):
|
| 80 |
+
msg = "{!r} object attribute {!r} is read-only"
|
| 81 |
+
else:
|
| 82 |
+
msg = "{!r} object has no attribute {!r}"
|
| 83 |
+
raise AttributeError(msg.format(self.__class__.__name__, name))
|
| 84 |
+
|
| 85 |
+
def __add__(self, other):
|
| 86 |
+
"""Non-default values from right operand override those from left.
|
| 87 |
+
|
| 88 |
+
The object returned is a new instance of the subclass.
|
| 89 |
+
|
| 90 |
+
"""
|
| 91 |
+
return self.clone(**other.__dict__)
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
def _append_doc(doc, added_doc):
|
| 95 |
+
doc = doc.rsplit('\n', 1)[0]
|
| 96 |
+
added_doc = added_doc.split('\n', 1)[1]
|
| 97 |
+
return doc + '\n' + added_doc
|
| 98 |
+
|
| 99 |
+
def _extend_docstrings(cls):
|
| 100 |
+
if cls.__doc__ and cls.__doc__.startswith('+'):
|
| 101 |
+
cls.__doc__ = _append_doc(cls.__bases__[0].__doc__, cls.__doc__)
|
| 102 |
+
for name, attr in cls.__dict__.items():
|
| 103 |
+
if attr.__doc__ and attr.__doc__.startswith('+'):
|
| 104 |
+
for c in (c for base in cls.__bases__ for c in base.mro()):
|
| 105 |
+
doc = getattr(getattr(c, name), '__doc__')
|
| 106 |
+
if doc:
|
| 107 |
+
attr.__doc__ = _append_doc(doc, attr.__doc__)
|
| 108 |
+
break
|
| 109 |
+
return cls
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
class Policy(_PolicyBase, metaclass=abc.ABCMeta):
|
| 113 |
+
|
| 114 |
+
r"""Controls for how messages are interpreted and formatted.
|
| 115 |
+
|
| 116 |
+
Most of the classes and many of the methods in the email package accept
|
| 117 |
+
Policy objects as parameters. A Policy object contains a set of values and
|
| 118 |
+
functions that control how input is interpreted and how output is rendered.
|
| 119 |
+
For example, the parameter 'raise_on_defect' controls whether or not an RFC
|
| 120 |
+
violation results in an error being raised or not, while 'max_line_length'
|
| 121 |
+
controls the maximum length of output lines when a Message is serialized.
|
| 122 |
+
|
| 123 |
+
Any valid attribute may be overridden when a Policy is created by passing
|
| 124 |
+
it as a keyword argument to the constructor. Policy objects are immutable,
|
| 125 |
+
but a new Policy object can be created with only certain values changed by
|
| 126 |
+
calling the Policy instance with keyword arguments. Policy objects can
|
| 127 |
+
also be added, producing a new Policy object in which the non-default
|
| 128 |
+
attributes set in the right hand operand overwrite those specified in the
|
| 129 |
+
left operand.
|
| 130 |
+
|
| 131 |
+
Settable attributes:
|
| 132 |
+
|
| 133 |
+
raise_on_defect -- If true, then defects should be raised as errors.
|
| 134 |
+
Default: False.
|
| 135 |
+
|
| 136 |
+
linesep -- string containing the value to use as separation
|
| 137 |
+
between output lines. Default '\n'.
|
| 138 |
+
|
| 139 |
+
cte_type -- Type of allowed content transfer encodings
|
| 140 |
+
|
| 141 |
+
7bit -- ASCII only
|
| 142 |
+
8bit -- Content-Transfer-Encoding: 8bit is allowed
|
| 143 |
+
|
| 144 |
+
Default: 8bit. Also controls the disposition of
|
| 145 |
+
(RFC invalid) binary data in headers; see the
|
| 146 |
+
documentation of the binary_fold method.
|
| 147 |
+
|
| 148 |
+
max_line_length -- maximum length of lines, excluding 'linesep',
|
| 149 |
+
during serialization. None or 0 means no line
|
| 150 |
+
wrapping is done. Default is 78.
|
| 151 |
+
|
| 152 |
+
mangle_from_ -- a flag that, when True escapes From_ lines in the
|
| 153 |
+
body of the message by putting a `>' in front of
|
| 154 |
+
them. This is used when the message is being
|
| 155 |
+
serialized by a generator. Default: True.
|
| 156 |
+
|
| 157 |
+
message_factory -- the class to use to create new message objects.
|
| 158 |
+
If the value is None, the default is Message.
|
| 159 |
+
|
| 160 |
+
"""
|
| 161 |
+
|
| 162 |
+
raise_on_defect = False
|
| 163 |
+
linesep = '\n'
|
| 164 |
+
cte_type = '8bit'
|
| 165 |
+
max_line_length = 78
|
| 166 |
+
mangle_from_ = False
|
| 167 |
+
message_factory = None
|
| 168 |
+
|
| 169 |
+
def handle_defect(self, obj, defect):
|
| 170 |
+
"""Based on policy, either raise defect or call register_defect.
|
| 171 |
+
|
| 172 |
+
handle_defect(obj, defect)
|
| 173 |
+
|
| 174 |
+
defect should be a Defect subclass, but in any case must be an
|
| 175 |
+
Exception subclass. obj is the object on which the defect should be
|
| 176 |
+
registered if it is not raised. If the raise_on_defect is True, the
|
| 177 |
+
defect is raised as an error, otherwise the object and the defect are
|
| 178 |
+
passed to register_defect.
|
| 179 |
+
|
| 180 |
+
This method is intended to be called by parsers that discover defects.
|
| 181 |
+
The email package parsers always call it with Defect instances.
|
| 182 |
+
|
| 183 |
+
"""
|
| 184 |
+
if self.raise_on_defect:
|
| 185 |
+
raise defect
|
| 186 |
+
self.register_defect(obj, defect)
|
| 187 |
+
|
| 188 |
+
def register_defect(self, obj, defect):
|
| 189 |
+
"""Record 'defect' on 'obj'.
|
| 190 |
+
|
| 191 |
+
Called by handle_defect if raise_on_defect is False. This method is
|
| 192 |
+
part of the Policy API so that Policy subclasses can implement custom
|
| 193 |
+
defect handling. The default implementation calls the append method of
|
| 194 |
+
the defects attribute of obj. The objects used by the email package by
|
| 195 |
+
default that get passed to this method will always have a defects
|
| 196 |
+
attribute with an append method.
|
| 197 |
+
|
| 198 |
+
"""
|
| 199 |
+
obj.defects.append(defect)
|
| 200 |
+
|
| 201 |
+
def header_max_count(self, name):
|
| 202 |
+
"""Return the maximum allowed number of headers named 'name'.
|
| 203 |
+
|
| 204 |
+
Called when a header is added to a Message object. If the returned
|
| 205 |
+
value is not 0 or None, and there are already a number of headers with
|
| 206 |
+
the name 'name' equal to the value returned, a ValueError is raised.
|
| 207 |
+
|
| 208 |
+
Because the default behavior of Message's __setitem__ is to append the
|
| 209 |
+
value to the list of headers, it is easy to create duplicate headers
|
| 210 |
+
without realizing it. This method allows certain headers to be limited
|
| 211 |
+
in the number of instances of that header that may be added to a
|
| 212 |
+
Message programmatically. (The limit is not observed by the parser,
|
| 213 |
+
which will faithfully produce as many headers as exist in the message
|
| 214 |
+
being parsed.)
|
| 215 |
+
|
| 216 |
+
The default implementation returns None for all header names.
|
| 217 |
+
"""
|
| 218 |
+
return None
|
| 219 |
+
|
| 220 |
+
@abc.abstractmethod
|
| 221 |
+
def header_source_parse(self, sourcelines):
|
| 222 |
+
"""Given a list of linesep terminated strings constituting the lines of
|
| 223 |
+
a single header, return the (name, value) tuple that should be stored
|
| 224 |
+
in the model. The input lines should retain their terminating linesep
|
| 225 |
+
characters. The lines passed in by the email package may contain
|
| 226 |
+
surrogateescaped binary data.
|
| 227 |
+
"""
|
| 228 |
+
raise NotImplementedError
|
| 229 |
+
|
| 230 |
+
@abc.abstractmethod
|
| 231 |
+
def header_store_parse(self, name, value):
|
| 232 |
+
"""Given the header name and the value provided by the application
|
| 233 |
+
program, return the (name, value) that should be stored in the model.
|
| 234 |
+
"""
|
| 235 |
+
raise NotImplementedError
|
| 236 |
+
|
| 237 |
+
@abc.abstractmethod
|
| 238 |
+
def header_fetch_parse(self, name, value):
|
| 239 |
+
"""Given the header name and the value from the model, return the value
|
| 240 |
+
to be returned to the application program that is requesting that
|
| 241 |
+
header. The value passed in by the email package may contain
|
| 242 |
+
surrogateescaped binary data if the lines were parsed by a BytesParser.
|
| 243 |
+
The returned value should not contain any surrogateescaped data.
|
| 244 |
+
|
| 245 |
+
"""
|
| 246 |
+
raise NotImplementedError
|
| 247 |
+
|
| 248 |
+
@abc.abstractmethod
|
| 249 |
+
def fold(self, name, value):
|
| 250 |
+
"""Given the header name and the value from the model, return a string
|
| 251 |
+
containing linesep characters that implement the folding of the header
|
| 252 |
+
according to the policy controls. The value passed in by the email
|
| 253 |
+
package may contain surrogateescaped binary data if the lines were
|
| 254 |
+
parsed by a BytesParser. The returned value should not contain any
|
| 255 |
+
surrogateescaped data.
|
| 256 |
+
|
| 257 |
+
"""
|
| 258 |
+
raise NotImplementedError
|
| 259 |
+
|
| 260 |
+
@abc.abstractmethod
|
| 261 |
+
def fold_binary(self, name, value):
|
| 262 |
+
"""Given the header name and the value from the model, return binary
|
| 263 |
+
data containing linesep characters that implement the folding of the
|
| 264 |
+
header according to the policy controls. The value passed in by the
|
| 265 |
+
email package may contain surrogateescaped binary data.
|
| 266 |
+
|
| 267 |
+
"""
|
| 268 |
+
raise NotImplementedError
|
| 269 |
+
|
| 270 |
+
|
| 271 |
+
@_extend_docstrings
|
| 272 |
+
class Compat32(Policy):
|
| 273 |
+
|
| 274 |
+
"""+
|
| 275 |
+
This particular policy is the backward compatibility Policy. It
|
| 276 |
+
replicates the behavior of the email package version 5.1.
|
| 277 |
+
"""
|
| 278 |
+
|
| 279 |
+
mangle_from_ = True
|
| 280 |
+
|
| 281 |
+
def _sanitize_header(self, name, value):
|
| 282 |
+
# If the header value contains surrogates, return a Header using
|
| 283 |
+
# the unknown-8bit charset to encode the bytes as encoded words.
|
| 284 |
+
if not isinstance(value, str):
|
| 285 |
+
# Assume it is already a header object
|
| 286 |
+
return value
|
| 287 |
+
if _has_surrogates(value):
|
| 288 |
+
return header.Header(value, charset=_charset.UNKNOWN8BIT,
|
| 289 |
+
header_name=name)
|
| 290 |
+
else:
|
| 291 |
+
return value
|
| 292 |
+
|
| 293 |
+
def header_source_parse(self, sourcelines):
|
| 294 |
+
"""+
|
| 295 |
+
The name is parsed as everything up to the ':' and returned unmodified.
|
| 296 |
+
The value is determined by stripping leading whitespace off the
|
| 297 |
+
remainder of the first line, joining all subsequent lines together, and
|
| 298 |
+
stripping any trailing carriage return or linefeed characters.
|
| 299 |
+
|
| 300 |
+
"""
|
| 301 |
+
name, value = sourcelines[0].split(':', 1)
|
| 302 |
+
value = value.lstrip(' \t') + ''.join(sourcelines[1:])
|
| 303 |
+
return (name, value.rstrip('\r\n'))
|
| 304 |
+
|
| 305 |
+
def header_store_parse(self, name, value):
|
| 306 |
+
"""+
|
| 307 |
+
The name and value are returned unmodified.
|
| 308 |
+
"""
|
| 309 |
+
return (name, value)
|
| 310 |
+
|
| 311 |
+
def header_fetch_parse(self, name, value):
|
| 312 |
+
"""+
|
| 313 |
+
If the value contains binary data, it is converted into a Header object
|
| 314 |
+
using the unknown-8bit charset. Otherwise it is returned unmodified.
|
| 315 |
+
"""
|
| 316 |
+
return self._sanitize_header(name, value)
|
| 317 |
+
|
| 318 |
+
def fold(self, name, value):
|
| 319 |
+
"""+
|
| 320 |
+
Headers are folded using the Header folding algorithm, which preserves
|
| 321 |
+
existing line breaks in the value, and wraps each resulting line to the
|
| 322 |
+
max_line_length. Non-ASCII binary data are CTE encoded using the
|
| 323 |
+
unknown-8bit charset.
|
| 324 |
+
|
| 325 |
+
"""
|
| 326 |
+
return self._fold(name, value, sanitize=True)
|
| 327 |
+
|
| 328 |
+
def fold_binary(self, name, value):
|
| 329 |
+
"""+
|
| 330 |
+
Headers are folded using the Header folding algorithm, which preserves
|
| 331 |
+
existing line breaks in the value, and wraps each resulting line to the
|
| 332 |
+
max_line_length. If cte_type is 7bit, non-ascii binary data is CTE
|
| 333 |
+
encoded using the unknown-8bit charset. Otherwise the original source
|
| 334 |
+
header is used, with its existing line breaks and/or binary data.
|
| 335 |
+
|
| 336 |
+
"""
|
| 337 |
+
folded = self._fold(name, value, sanitize=self.cte_type=='7bit')
|
| 338 |
+
return folded.encode('ascii', 'surrogateescape')
|
| 339 |
+
|
| 340 |
+
def _fold(self, name, value, sanitize):
|
| 341 |
+
parts = []
|
| 342 |
+
parts.append('%s: ' % name)
|
| 343 |
+
if isinstance(value, str):
|
| 344 |
+
if _has_surrogates(value):
|
| 345 |
+
if sanitize:
|
| 346 |
+
h = header.Header(value,
|
| 347 |
+
charset=_charset.UNKNOWN8BIT,
|
| 348 |
+
header_name=name)
|
| 349 |
+
else:
|
| 350 |
+
# If we have raw 8bit data in a byte string, we have no idea
|
| 351 |
+
# what the encoding is. There is no safe way to split this
|
| 352 |
+
# string. If it's ascii-subset, then we could do a normal
|
| 353 |
+
# ascii split, but if it's multibyte then we could break the
|
| 354 |
+
# string. There's no way to know so the least harm seems to
|
| 355 |
+
# be to not split the string and risk it being too long.
|
| 356 |
+
parts.append(value)
|
| 357 |
+
h = None
|
| 358 |
+
else:
|
| 359 |
+
h = header.Header(value, header_name=name)
|
| 360 |
+
else:
|
| 361 |
+
# Assume it is a Header-like object.
|
| 362 |
+
h = value
|
| 363 |
+
if h is not None:
|
| 364 |
+
# The Header class interprets a value of None for maxlinelen as the
|
| 365 |
+
# default value of 78, as recommended by RFC 2822.
|
| 366 |
+
maxlinelen = 0
|
| 367 |
+
if self.max_line_length is not None:
|
| 368 |
+
maxlinelen = self.max_line_length
|
| 369 |
+
parts.append(h.encode(linesep=self.linesep, maxlinelen=maxlinelen))
|
| 370 |
+
parts.append(self.linesep)
|
| 371 |
+
return ''.join(parts)
|
| 372 |
+
|
| 373 |
+
|
| 374 |
+
compat32 = Compat32()
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/email/architecture.rst
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
:mod:`email` Package Architecture
|
| 2 |
+
=================================
|
| 3 |
+
|
| 4 |
+
Overview
|
| 5 |
+
--------
|
| 6 |
+
|
| 7 |
+
The email package consists of three major components:
|
| 8 |
+
|
| 9 |
+
Model
|
| 10 |
+
An object structure that represents an email message, and provides an
|
| 11 |
+
API for creating, querying, and modifying a message.
|
| 12 |
+
|
| 13 |
+
Parser
|
| 14 |
+
Takes a sequence of characters or bytes and produces a model of the
|
| 15 |
+
email message represented by those characters or bytes.
|
| 16 |
+
|
| 17 |
+
Generator
|
| 18 |
+
Takes a model and turns it into a sequence of characters or bytes. The
|
| 19 |
+
sequence can either be intended for human consumption (a printable
|
| 20 |
+
unicode string) or bytes suitable for transmission over the wire. In
|
| 21 |
+
the latter case all data is properly encoded using the content transfer
|
| 22 |
+
encodings specified by the relevant RFCs.
|
| 23 |
+
|
| 24 |
+
Conceptually the package is organized around the model. The model provides both
|
| 25 |
+
"external" APIs intended for use by application programs using the library,
|
| 26 |
+
and "internal" APIs intended for use by the Parser and Generator components.
|
| 27 |
+
This division is intentionally a bit fuzzy; the API described by this
|
| 28 |
+
documentation is all a public, stable API. This allows for an application
|
| 29 |
+
with special needs to implement its own parser and/or generator.
|
| 30 |
+
|
| 31 |
+
In addition to the three major functional components, there is a third key
|
| 32 |
+
component to the architecture:
|
| 33 |
+
|
| 34 |
+
Policy
|
| 35 |
+
An object that specifies various behavioral settings and carries
|
| 36 |
+
implementations of various behavior-controlling methods.
|
| 37 |
+
|
| 38 |
+
The Policy framework provides a simple and convenient way to control the
|
| 39 |
+
behavior of the library, making it possible for the library to be used in a
|
| 40 |
+
very flexible fashion while leveraging the common code required to parse,
|
| 41 |
+
represent, and generate message-like objects. For example, in addition to the
|
| 42 |
+
default :rfc:`5322` email message policy, we also have a policy that manages
|
| 43 |
+
HTTP headers in a fashion compliant with :rfc:`2616`. Individual policy
|
| 44 |
+
controls, such as the maximum line length produced by the generator, can also
|
| 45 |
+
be controlled individually to meet specialized application requirements.
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
The Model
|
| 49 |
+
---------
|
| 50 |
+
|
| 51 |
+
The message model is implemented by the :class:`~email.message.Message` class.
|
| 52 |
+
The model divides a message into the two fundamental parts discussed by the
|
| 53 |
+
RFC: the header section and the body. The `Message` object acts as a
|
| 54 |
+
pseudo-dictionary of named headers. Its dictionary interface provides
|
| 55 |
+
convenient access to individual headers by name. However, all headers are kept
|
| 56 |
+
internally in an ordered list, so that the information about the order of the
|
| 57 |
+
headers in the original message is preserved.
|
| 58 |
+
|
| 59 |
+
The `Message` object also has a `payload` that holds the body. A `payload` can
|
| 60 |
+
be one of two things: data, or a list of `Message` objects. The latter is used
|
| 61 |
+
to represent a multipart MIME message. Lists can be nested arbitrarily deeply
|
| 62 |
+
in order to represent the message, with all terminal leaves having non-list
|
| 63 |
+
data payloads.
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
Message Lifecycle
|
| 67 |
+
-----------------
|
| 68 |
+
|
| 69 |
+
The general lifecycle of a message is:
|
| 70 |
+
|
| 71 |
+
Creation
|
| 72 |
+
A `Message` object can be created by a Parser, or it can be
|
| 73 |
+
instantiated as an empty message by an application.
|
| 74 |
+
|
| 75 |
+
Manipulation
|
| 76 |
+
The application may examine one or more headers, and/or the
|
| 77 |
+
payload, and it may modify one or more headers and/or
|
| 78 |
+
the payload. This may be done on the top level `Message`
|
| 79 |
+
object, or on any sub-object.
|
| 80 |
+
|
| 81 |
+
Finalization
|
| 82 |
+
The Model is converted into a unicode or binary stream,
|
| 83 |
+
or the model is discarded.
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
Header Policy Control During Lifecycle
|
| 88 |
+
--------------------------------------
|
| 89 |
+
|
| 90 |
+
One of the major controls exerted by the Policy is the management of headers
|
| 91 |
+
during the `Message` lifecycle. Most applications don't need to be aware of
|
| 92 |
+
this.
|
| 93 |
+
|
| 94 |
+
A header enters the model in one of two ways: via a Parser, or by being set to
|
| 95 |
+
a specific value by an application program after the Model already exists.
|
| 96 |
+
Similarly, a header exits the model in one of two ways: by being serialized by
|
| 97 |
+
a Generator, or by being retrieved from a Model by an application program. The
|
| 98 |
+
Policy object provides hooks for all four of these pathways.
|
| 99 |
+
|
| 100 |
+
The model storage for headers is a list of (name, value) tuples.
|
| 101 |
+
|
| 102 |
+
The Parser identifies headers during parsing, and passes them to the
|
| 103 |
+
:meth:`~email.policy.Policy.header_source_parse` method of the Policy. The
|
| 104 |
+
result of that method is the (name, value) tuple to be stored in the model.
|
| 105 |
+
|
| 106 |
+
When an application program supplies a header value (for example, through the
|
| 107 |
+
`Message` object `__setitem__` interface), the name and the value are passed to
|
| 108 |
+
the :meth:`~email.policy.Policy.header_store_parse` method of the Policy, which
|
| 109 |
+
returns the (name, value) tuple to be stored in the model.
|
| 110 |
+
|
| 111 |
+
When an application program retrieves a header (through any of the dict or list
|
| 112 |
+
interfaces of `Message`), the name and value are passed to the
|
| 113 |
+
:meth:`~email.policy.Policy.header_fetch_parse` method of the Policy to
|
| 114 |
+
obtain the value returned to the application.
|
| 115 |
+
|
| 116 |
+
When a Generator requests a header during serialization, the name and value are
|
| 117 |
+
passed to the :meth:`~email.policy.Policy.fold` method of the Policy, which
|
| 118 |
+
returns a string containing line breaks in the appropriate places. The
|
| 119 |
+
:meth:`~email.policy.Policy.cte_type` Policy control determines whether or
|
| 120 |
+
not Content Transfer Encoding is performed on the data in the header. There is
|
| 121 |
+
also a :meth:`~email.policy.Policy.binary_fold` method for use by generators
|
| 122 |
+
that produce binary output, which returns the folded header as binary data,
|
| 123 |
+
possibly folded at different places than the corresponding string would be.
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
Handling Binary Data
|
| 127 |
+
--------------------
|
| 128 |
+
|
| 129 |
+
In an ideal world all message data would conform to the RFCs, meaning that the
|
| 130 |
+
parser could decode the message into the idealized unicode message that the
|
| 131 |
+
sender originally wrote. In the real world, the email package must also be
|
| 132 |
+
able to deal with badly formatted messages, including messages containing
|
| 133 |
+
non-ASCII characters that either have no indicated character set or are not
|
| 134 |
+
valid characters in the indicated character set.
|
| 135 |
+
|
| 136 |
+
Since email messages are *primarily* text data, and operations on message data
|
| 137 |
+
are primarily text operations (except for binary payloads of course), the model
|
| 138 |
+
stores all text data as unicode strings. Un-decodable binary inside text
|
| 139 |
+
data is handled by using the `surrogateescape` error handler of the ASCII
|
| 140 |
+
codec. As with the binary filenames the error handler was introduced to
|
| 141 |
+
handle, this allows the email package to "carry" the binary data received
|
| 142 |
+
during parsing along until the output stage, at which time it is regenerated
|
| 143 |
+
in its original form.
|
| 144 |
+
|
| 145 |
+
This carried binary data is almost entirely an implementation detail. The one
|
| 146 |
+
place where it is visible in the API is in the "internal" API. A Parser must
|
| 147 |
+
do the `surrogateescape` encoding of binary input data, and pass that data to
|
| 148 |
+
the appropriate Policy method. The "internal" interface used by the Generator
|
| 149 |
+
to access header values preserves the `surrogateescaped` bytes. All other
|
| 150 |
+
interfaces convert the binary data either back into bytes or into a safe form
|
| 151 |
+
(losing information in some cases).
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
Backward Compatibility
|
| 155 |
+
----------------------
|
| 156 |
+
|
| 157 |
+
The :class:`~email.policy.Policy.Compat32` Policy provides backward
|
| 158 |
+
compatibility with version 5.1 of the email package. It does this via the
|
| 159 |
+
following implementation of the four+1 Policy methods described above:
|
| 160 |
+
|
| 161 |
+
header_source_parse
|
| 162 |
+
Splits the first line on the colon to obtain the name, discards any spaces
|
| 163 |
+
after the colon, and joins the remainder of the line with all of the
|
| 164 |
+
remaining lines, preserving the linesep characters to obtain the value.
|
| 165 |
+
Trailing carriage return and/or linefeed characters are stripped from the
|
| 166 |
+
resulting value string.
|
| 167 |
+
|
| 168 |
+
header_store_parse
|
| 169 |
+
Returns the name and value exactly as received from the application.
|
| 170 |
+
|
| 171 |
+
header_fetch_parse
|
| 172 |
+
If the value contains any `surrogateescaped` binary data, return the value
|
| 173 |
+
as a :class:`~email.header.Header` object, using the character set
|
| 174 |
+
`unknown-8bit`. Otherwise just returns the value.
|
| 175 |
+
|
| 176 |
+
fold
|
| 177 |
+
Uses :class:`~email.header.Header`'s folding to fold headers in the
|
| 178 |
+
same way the email5.1 generator did.
|
| 179 |
+
|
| 180 |
+
binary_fold
|
| 181 |
+
Same as fold, but encodes to 'ascii'.
|
| 182 |
+
|
| 183 |
+
|
| 184 |
+
New Algorithm
|
| 185 |
+
-------------
|
| 186 |
+
|
| 187 |
+
header_source_parse
|
| 188 |
+
Same as legacy behavior.
|
| 189 |
+
|
| 190 |
+
header_store_parse
|
| 191 |
+
Same as legacy behavior.
|
| 192 |
+
|
| 193 |
+
header_fetch_parse
|
| 194 |
+
If the value is already a header object, returns it. Otherwise, parses the
|
| 195 |
+
value using the new parser, and returns the resulting object as the value.
|
| 196 |
+
`surrogateescaped` bytes get turned into unicode unknown character code
|
| 197 |
+
points.
|
| 198 |
+
|
| 199 |
+
fold
|
| 200 |
+
Uses the new header folding algorithm, respecting the policy settings.
|
| 201 |
+
surrogateescaped bytes are encoded using the ``unknown-8bit`` charset for
|
| 202 |
+
``cte_type=7bit`` or ``8bit``. Returns a string.
|
| 203 |
+
|
| 204 |
+
At some point there will also be a ``cte_type=unicode``, and for that
|
| 205 |
+
policy fold will serialize the idealized unicode message with RFC-like
|
| 206 |
+
folding, converting any surrogateescaped bytes into the unicode
|
| 207 |
+
unknown character glyph.
|
| 208 |
+
|
| 209 |
+
binary_fold
|
| 210 |
+
Uses the new header folding algorithm, respecting the policy settings.
|
| 211 |
+
surrogateescaped bytes are encoded using the `unknown-8bit` charset for
|
| 212 |
+
``cte_type=7bit``, and get turned back into bytes for ``cte_type=8bit``.
|
| 213 |
+
Returns bytes.
|
| 214 |
+
|
| 215 |
+
At some point there will also be a ``cte_type=unicode``, and for that
|
| 216 |
+
policy binary_fold will serialize the message according to :rfc:``5335``.
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/email/base64mime.py
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (C) 2002-2007 Python Software Foundation
|
| 2 |
+
# Author: Ben Gertzfield
|
| 3 |
+
# Contact: email-sig@python.org
|
| 4 |
+
|
| 5 |
+
"""Base64 content transfer encoding per RFCs 2045-2047.
|
| 6 |
+
|
| 7 |
+
This module handles the content transfer encoding method defined in RFC 2045
|
| 8 |
+
to encode arbitrary 8-bit data using the three 8-bit bytes in four 7-bit
|
| 9 |
+
characters encoding known as Base64.
|
| 10 |
+
|
| 11 |
+
It is used in the MIME standards for email to attach images, audio, and text
|
| 12 |
+
using some 8-bit character sets to messages.
|
| 13 |
+
|
| 14 |
+
This module provides an interface to encode and decode both headers and bodies
|
| 15 |
+
with Base64 encoding.
|
| 16 |
+
|
| 17 |
+
RFC 2045 defines a method for including character set information in an
|
| 18 |
+
`encoded-word' in a header. This method is commonly used for 8-bit real names
|
| 19 |
+
in To:, From:, Cc:, etc. fields, as well as Subject: lines.
|
| 20 |
+
|
| 21 |
+
This module does not do the line wrapping or end-of-line character conversion
|
| 22 |
+
necessary for proper internationalized headers; it only does dumb encoding and
|
| 23 |
+
decoding. To deal with the various line wrapping issues, use the email.header
|
| 24 |
+
module.
|
| 25 |
+
"""
|
| 26 |
+
|
| 27 |
+
__all__ = [
|
| 28 |
+
'body_decode',
|
| 29 |
+
'body_encode',
|
| 30 |
+
'decode',
|
| 31 |
+
'decodestring',
|
| 32 |
+
'header_encode',
|
| 33 |
+
'header_length',
|
| 34 |
+
]
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
from base64 import b64encode
|
| 38 |
+
from binascii import b2a_base64, a2b_base64
|
| 39 |
+
|
| 40 |
+
CRLF = '\r\n'
|
| 41 |
+
NL = '\n'
|
| 42 |
+
EMPTYSTRING = ''
|
| 43 |
+
|
| 44 |
+
# See also Charset.py
|
| 45 |
+
MISC_LEN = 7
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
# Helpers
|
| 50 |
+
def header_length(bytearray):
|
| 51 |
+
"""Return the length of s when it is encoded with base64."""
|
| 52 |
+
groups_of_3, leftover = divmod(len(bytearray), 3)
|
| 53 |
+
# 4 bytes out for each 3 bytes (or nonzero fraction thereof) in.
|
| 54 |
+
n = groups_of_3 * 4
|
| 55 |
+
if leftover:
|
| 56 |
+
n += 4
|
| 57 |
+
return n
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def header_encode(header_bytes, charset='iso-8859-1'):
|
| 62 |
+
"""Encode a single header line with Base64 encoding in a given charset.
|
| 63 |
+
|
| 64 |
+
charset names the character set to use to encode the header. It defaults
|
| 65 |
+
to iso-8859-1. Base64 encoding is defined in RFC 2045.
|
| 66 |
+
"""
|
| 67 |
+
if not header_bytes:
|
| 68 |
+
return ""
|
| 69 |
+
if isinstance(header_bytes, str):
|
| 70 |
+
header_bytes = header_bytes.encode(charset)
|
| 71 |
+
encoded = b64encode(header_bytes).decode("ascii")
|
| 72 |
+
return '=?%s?b?%s?=' % (charset, encoded)
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
def body_encode(s, maxlinelen=76, eol=NL):
|
| 77 |
+
r"""Encode a string with base64.
|
| 78 |
+
|
| 79 |
+
Each line will be wrapped at, at most, maxlinelen characters (defaults to
|
| 80 |
+
76 characters).
|
| 81 |
+
|
| 82 |
+
Each line of encoded text will end with eol, which defaults to "\n". Set
|
| 83 |
+
this to "\r\n" if you will be using the result of this function directly
|
| 84 |
+
in an email.
|
| 85 |
+
"""
|
| 86 |
+
if not s:
|
| 87 |
+
return s
|
| 88 |
+
|
| 89 |
+
encvec = []
|
| 90 |
+
max_unencoded = maxlinelen * 3 // 4
|
| 91 |
+
for i in range(0, len(s), max_unencoded):
|
| 92 |
+
# BAW: should encode() inherit b2a_base64()'s dubious behavior in
|
| 93 |
+
# adding a newline to the encoded string?
|
| 94 |
+
enc = b2a_base64(s[i:i + max_unencoded]).decode("ascii")
|
| 95 |
+
if enc.endswith(NL) and eol != NL:
|
| 96 |
+
enc = enc[:-1] + eol
|
| 97 |
+
encvec.append(enc)
|
| 98 |
+
return EMPTYSTRING.join(encvec)
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
def decode(string):
|
| 103 |
+
"""Decode a raw base64 string, returning a bytes object.
|
| 104 |
+
|
| 105 |
+
This function does not parse a full MIME header value encoded with
|
| 106 |
+
base64 (like =?iso-8859-1?b?bmloISBuaWgh?=) -- please use the high
|
| 107 |
+
level email.header class for that functionality.
|
| 108 |
+
"""
|
| 109 |
+
if not string:
|
| 110 |
+
return bytes()
|
| 111 |
+
elif isinstance(string, str):
|
| 112 |
+
return a2b_base64(string.encode('raw-unicode-escape'))
|
| 113 |
+
else:
|
| 114 |
+
return a2b_base64(string)
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
# For convenience and backwards compatibility w/ standard base64 module
|
| 118 |
+
body_decode = decode
|
| 119 |
+
decodestring = decode
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/email/charset.py
ADDED
|
@@ -0,0 +1,404 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (C) 2001-2007 Python Software Foundation
|
| 2 |
+
# Author: Ben Gertzfield, Barry Warsaw
|
| 3 |
+
# Contact: email-sig@python.org
|
| 4 |
+
|
| 5 |
+
__all__ = [
|
| 6 |
+
'Charset',
|
| 7 |
+
'add_alias',
|
| 8 |
+
'add_charset',
|
| 9 |
+
'add_codec',
|
| 10 |
+
]
|
| 11 |
+
|
| 12 |
+
from functools import partial
|
| 13 |
+
|
| 14 |
+
import email.base64mime
|
| 15 |
+
import email.quoprimime
|
| 16 |
+
|
| 17 |
+
from email import errors
|
| 18 |
+
from email.encoders import encode_7or8bit
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
# Flags for types of header encodings
|
| 23 |
+
QP = 1 # Quoted-Printable
|
| 24 |
+
BASE64 = 2 # Base64
|
| 25 |
+
SHORTEST = 3 # the shorter of QP and base64, but only for headers
|
| 26 |
+
|
| 27 |
+
# In "=?charset?q?hello_world?=", the =?, ?q?, and ?= add up to 7
|
| 28 |
+
RFC2047_CHROME_LEN = 7
|
| 29 |
+
|
| 30 |
+
DEFAULT_CHARSET = 'us-ascii'
|
| 31 |
+
UNKNOWN8BIT = 'unknown-8bit'
|
| 32 |
+
EMPTYSTRING = ''
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
# Defaults
|
| 37 |
+
CHARSETS = {
|
| 38 |
+
# input header enc body enc output conv
|
| 39 |
+
'iso-8859-1': (QP, QP, None),
|
| 40 |
+
'iso-8859-2': (QP, QP, None),
|
| 41 |
+
'iso-8859-3': (QP, QP, None),
|
| 42 |
+
'iso-8859-4': (QP, QP, None),
|
| 43 |
+
# iso-8859-5 is Cyrillic, and not especially used
|
| 44 |
+
# iso-8859-6 is Arabic, also not particularly used
|
| 45 |
+
# iso-8859-7 is Greek, QP will not make it readable
|
| 46 |
+
# iso-8859-8 is Hebrew, QP will not make it readable
|
| 47 |
+
'iso-8859-9': (QP, QP, None),
|
| 48 |
+
'iso-8859-10': (QP, QP, None),
|
| 49 |
+
# iso-8859-11 is Thai, QP will not make it readable
|
| 50 |
+
'iso-8859-13': (QP, QP, None),
|
| 51 |
+
'iso-8859-14': (QP, QP, None),
|
| 52 |
+
'iso-8859-15': (QP, QP, None),
|
| 53 |
+
'iso-8859-16': (QP, QP, None),
|
| 54 |
+
'windows-1252':(QP, QP, None),
|
| 55 |
+
'viscii': (QP, QP, None),
|
| 56 |
+
'us-ascii': (None, None, None),
|
| 57 |
+
'big5': (BASE64, BASE64, None),
|
| 58 |
+
'gb2312': (BASE64, BASE64, None),
|
| 59 |
+
'euc-jp': (BASE64, None, 'iso-2022-jp'),
|
| 60 |
+
'shift_jis': (BASE64, None, 'iso-2022-jp'),
|
| 61 |
+
'iso-2022-jp': (BASE64, None, None),
|
| 62 |
+
'koi8-r': (BASE64, BASE64, None),
|
| 63 |
+
'utf-8': (SHORTEST, BASE64, 'utf-8'),
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
# Aliases for other commonly-used names for character sets. Map
|
| 67 |
+
# them to the real ones used in email.
|
| 68 |
+
ALIASES = {
|
| 69 |
+
'latin_1': 'iso-8859-1',
|
| 70 |
+
'latin-1': 'iso-8859-1',
|
| 71 |
+
'latin_2': 'iso-8859-2',
|
| 72 |
+
'latin-2': 'iso-8859-2',
|
| 73 |
+
'latin_3': 'iso-8859-3',
|
| 74 |
+
'latin-3': 'iso-8859-3',
|
| 75 |
+
'latin_4': 'iso-8859-4',
|
| 76 |
+
'latin-4': 'iso-8859-4',
|
| 77 |
+
'latin_5': 'iso-8859-9',
|
| 78 |
+
'latin-5': 'iso-8859-9',
|
| 79 |
+
'latin_6': 'iso-8859-10',
|
| 80 |
+
'latin-6': 'iso-8859-10',
|
| 81 |
+
'latin_7': 'iso-8859-13',
|
| 82 |
+
'latin-7': 'iso-8859-13',
|
| 83 |
+
'latin_8': 'iso-8859-14',
|
| 84 |
+
'latin-8': 'iso-8859-14',
|
| 85 |
+
'latin_9': 'iso-8859-15',
|
| 86 |
+
'latin-9': 'iso-8859-15',
|
| 87 |
+
'latin_10':'iso-8859-16',
|
| 88 |
+
'latin-10':'iso-8859-16',
|
| 89 |
+
'cp949': 'ks_c_5601-1987',
|
| 90 |
+
'euc_jp': 'euc-jp',
|
| 91 |
+
'euc_kr': 'euc-kr',
|
| 92 |
+
'ascii': 'us-ascii',
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
# Map charsets to their Unicode codec strings.
|
| 97 |
+
CODEC_MAP = {
|
| 98 |
+
'gb2312': 'eucgb2312_cn',
|
| 99 |
+
'big5': 'big5_tw',
|
| 100 |
+
# Hack: We don't want *any* conversion for stuff marked us-ascii, as all
|
| 101 |
+
# sorts of garbage might be sent to us in the guise of 7-bit us-ascii.
|
| 102 |
+
# Let that stuff pass through without conversion to/from Unicode.
|
| 103 |
+
'us-ascii': None,
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
# Convenience functions for extending the above mappings
|
| 109 |
+
def add_charset(charset, header_enc=None, body_enc=None, output_charset=None):
|
| 110 |
+
"""Add character set properties to the global registry.
|
| 111 |
+
|
| 112 |
+
charset is the input character set, and must be the canonical name of a
|
| 113 |
+
character set.
|
| 114 |
+
|
| 115 |
+
Optional header_enc and body_enc is either Charset.QP for
|
| 116 |
+
quoted-printable, Charset.BASE64 for base64 encoding, Charset.SHORTEST for
|
| 117 |
+
the shortest of qp or base64 encoding, or None for no encoding. SHORTEST
|
| 118 |
+
is only valid for header_enc. It describes how message headers and
|
| 119 |
+
message bodies in the input charset are to be encoded. Default is no
|
| 120 |
+
encoding.
|
| 121 |
+
|
| 122 |
+
Optional output_charset is the character set that the output should be
|
| 123 |
+
in. Conversions will proceed from input charset, to Unicode, to the
|
| 124 |
+
output charset when the method Charset.convert() is called. The default
|
| 125 |
+
is to output in the same character set as the input.
|
| 126 |
+
|
| 127 |
+
Both input_charset and output_charset must have Unicode codec entries in
|
| 128 |
+
the module's charset-to-codec mapping; use add_codec(charset, codecname)
|
| 129 |
+
to add codecs the module does not know about. See the codecs module's
|
| 130 |
+
documentation for more information.
|
| 131 |
+
"""
|
| 132 |
+
if body_enc == SHORTEST:
|
| 133 |
+
raise ValueError('SHORTEST not allowed for body_enc')
|
| 134 |
+
CHARSETS[charset] = (header_enc, body_enc, output_charset)
|
| 135 |
+
|
| 136 |
+
|
| 137 |
+
def add_alias(alias, canonical):
|
| 138 |
+
"""Add a character set alias.
|
| 139 |
+
|
| 140 |
+
alias is the alias name, e.g. latin-1
|
| 141 |
+
canonical is the character set's canonical name, e.g. iso-8859-1
|
| 142 |
+
"""
|
| 143 |
+
ALIASES[alias] = canonical
|
| 144 |
+
|
| 145 |
+
|
| 146 |
+
def add_codec(charset, codecname):
|
| 147 |
+
"""Add a codec that map characters in the given charset to/from Unicode.
|
| 148 |
+
|
| 149 |
+
charset is the canonical name of a character set. codecname is the name
|
| 150 |
+
of a Python codec, as appropriate for the second argument to the unicode()
|
| 151 |
+
built-in, or to the encode() method of a Unicode string.
|
| 152 |
+
"""
|
| 153 |
+
CODEC_MAP[charset] = codecname
|
| 154 |
+
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
# Convenience function for encoding strings, taking into account
|
| 158 |
+
# that they might be unknown-8bit (ie: have surrogate-escaped bytes)
|
| 159 |
+
def _encode(string, codec):
|
| 160 |
+
if codec == UNKNOWN8BIT:
|
| 161 |
+
return string.encode('ascii', 'surrogateescape')
|
| 162 |
+
else:
|
| 163 |
+
return string.encode(codec)
|
| 164 |
+
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
class Charset:
|
| 168 |
+
"""Map character sets to their email properties.
|
| 169 |
+
|
| 170 |
+
This class provides information about the requirements imposed on email
|
| 171 |
+
for a specific character set. It also provides convenience routines for
|
| 172 |
+
converting between character sets, given the availability of the
|
| 173 |
+
applicable codecs. Given a character set, it will do its best to provide
|
| 174 |
+
information on how to use that character set in an email in an
|
| 175 |
+
RFC-compliant way.
|
| 176 |
+
|
| 177 |
+
Certain character sets must be encoded with quoted-printable or base64
|
| 178 |
+
when used in email headers or bodies. Certain character sets must be
|
| 179 |
+
converted outright, and are not allowed in email. Instances of this
|
| 180 |
+
module expose the following information about a character set:
|
| 181 |
+
|
| 182 |
+
input_charset: The initial character set specified. Common aliases
|
| 183 |
+
are converted to their `official' email names (e.g. latin_1
|
| 184 |
+
is converted to iso-8859-1). Defaults to 7-bit us-ascii.
|
| 185 |
+
|
| 186 |
+
header_encoding: If the character set must be encoded before it can be
|
| 187 |
+
used in an email header, this attribute will be set to
|
| 188 |
+
Charset.QP (for quoted-printable), Charset.BASE64 (for
|
| 189 |
+
base64 encoding), or Charset.SHORTEST for the shortest of
|
| 190 |
+
QP or BASE64 encoding. Otherwise, it will be None.
|
| 191 |
+
|
| 192 |
+
body_encoding: Same as header_encoding, but describes the encoding for the
|
| 193 |
+
mail message's body, which indeed may be different than the
|
| 194 |
+
header encoding. Charset.SHORTEST is not allowed for
|
| 195 |
+
body_encoding.
|
| 196 |
+
|
| 197 |
+
output_charset: Some character sets must be converted before they can be
|
| 198 |
+
used in email headers or bodies. If the input_charset is
|
| 199 |
+
one of them, this attribute will contain the name of the
|
| 200 |
+
charset output will be converted to. Otherwise, it will
|
| 201 |
+
be None.
|
| 202 |
+
|
| 203 |
+
input_codec: The name of the Python codec used to convert the
|
| 204 |
+
input_charset to Unicode. If no conversion codec is
|
| 205 |
+
necessary, this attribute will be None.
|
| 206 |
+
|
| 207 |
+
output_codec: The name of the Python codec used to convert Unicode
|
| 208 |
+
to the output_charset. If no conversion codec is necessary,
|
| 209 |
+
this attribute will have the same value as the input_codec.
|
| 210 |
+
"""
|
| 211 |
+
def __init__(self, input_charset=DEFAULT_CHARSET):
|
| 212 |
+
# RFC 2046, $4.1.2 says charsets are not case sensitive. We coerce to
|
| 213 |
+
# unicode because its .lower() is locale insensitive. If the argument
|
| 214 |
+
# is already a unicode, we leave it at that, but ensure that the
|
| 215 |
+
# charset is ASCII, as the standard (RFC XXX) requires.
|
| 216 |
+
try:
|
| 217 |
+
if isinstance(input_charset, str):
|
| 218 |
+
input_charset.encode('ascii')
|
| 219 |
+
else:
|
| 220 |
+
input_charset = str(input_charset, 'ascii')
|
| 221 |
+
except UnicodeError:
|
| 222 |
+
raise errors.CharsetError(input_charset)
|
| 223 |
+
input_charset = input_charset.lower()
|
| 224 |
+
# Set the input charset after filtering through the aliases
|
| 225 |
+
self.input_charset = ALIASES.get(input_charset, input_charset)
|
| 226 |
+
# We can try to guess which encoding and conversion to use by the
|
| 227 |
+
# charset_map dictionary. Try that first, but let the user override
|
| 228 |
+
# it.
|
| 229 |
+
henc, benc, conv = CHARSETS.get(self.input_charset,
|
| 230 |
+
(SHORTEST, BASE64, None))
|
| 231 |
+
if not conv:
|
| 232 |
+
conv = self.input_charset
|
| 233 |
+
# Set the attributes, allowing the arguments to override the default.
|
| 234 |
+
self.header_encoding = henc
|
| 235 |
+
self.body_encoding = benc
|
| 236 |
+
self.output_charset = ALIASES.get(conv, conv)
|
| 237 |
+
# Now set the codecs. If one isn't defined for input_charset,
|
| 238 |
+
# guess and try a Unicode codec with the same name as input_codec.
|
| 239 |
+
self.input_codec = CODEC_MAP.get(self.input_charset,
|
| 240 |
+
self.input_charset)
|
| 241 |
+
self.output_codec = CODEC_MAP.get(self.output_charset,
|
| 242 |
+
self.output_charset)
|
| 243 |
+
|
| 244 |
+
def __repr__(self):
|
| 245 |
+
return self.input_charset.lower()
|
| 246 |
+
|
| 247 |
+
def __eq__(self, other):
|
| 248 |
+
return str(self) == str(other).lower()
|
| 249 |
+
|
| 250 |
+
def get_body_encoding(self):
|
| 251 |
+
"""Return the content-transfer-encoding used for body encoding.
|
| 252 |
+
|
| 253 |
+
This is either the string `quoted-printable' or `base64' depending on
|
| 254 |
+
the encoding used, or it is a function in which case you should call
|
| 255 |
+
the function with a single argument, the Message object being
|
| 256 |
+
encoded. The function should then set the Content-Transfer-Encoding
|
| 257 |
+
header itself to whatever is appropriate.
|
| 258 |
+
|
| 259 |
+
Returns "quoted-printable" if self.body_encoding is QP.
|
| 260 |
+
Returns "base64" if self.body_encoding is BASE64.
|
| 261 |
+
Returns conversion function otherwise.
|
| 262 |
+
"""
|
| 263 |
+
assert self.body_encoding != SHORTEST
|
| 264 |
+
if self.body_encoding == QP:
|
| 265 |
+
return 'quoted-printable'
|
| 266 |
+
elif self.body_encoding == BASE64:
|
| 267 |
+
return 'base64'
|
| 268 |
+
else:
|
| 269 |
+
return encode_7or8bit
|
| 270 |
+
|
| 271 |
+
def get_output_charset(self):
|
| 272 |
+
"""Return the output character set.
|
| 273 |
+
|
| 274 |
+
This is self.output_charset if that is not None, otherwise it is
|
| 275 |
+
self.input_charset.
|
| 276 |
+
"""
|
| 277 |
+
return self.output_charset or self.input_charset
|
| 278 |
+
|
| 279 |
+
def header_encode(self, string):
|
| 280 |
+
"""Header-encode a string by converting it first to bytes.
|
| 281 |
+
|
| 282 |
+
The type of encoding (base64 or quoted-printable) will be based on
|
| 283 |
+
this charset's `header_encoding`.
|
| 284 |
+
|
| 285 |
+
:param string: A unicode string for the header. It must be possible
|
| 286 |
+
to encode this string to bytes using the character set's
|
| 287 |
+
output codec.
|
| 288 |
+
:return: The encoded string, with RFC 2047 chrome.
|
| 289 |
+
"""
|
| 290 |
+
codec = self.output_codec or 'us-ascii'
|
| 291 |
+
header_bytes = _encode(string, codec)
|
| 292 |
+
# 7bit/8bit encodings return the string unchanged (modulo conversions)
|
| 293 |
+
encoder_module = self._get_encoder(header_bytes)
|
| 294 |
+
if encoder_module is None:
|
| 295 |
+
return string
|
| 296 |
+
return encoder_module.header_encode(header_bytes, codec)
|
| 297 |
+
|
| 298 |
+
def header_encode_lines(self, string, maxlengths):
|
| 299 |
+
"""Header-encode a string by converting it first to bytes.
|
| 300 |
+
|
| 301 |
+
This is similar to `header_encode()` except that the string is fit
|
| 302 |
+
into maximum line lengths as given by the argument.
|
| 303 |
+
|
| 304 |
+
:param string: A unicode string for the header. It must be possible
|
| 305 |
+
to encode this string to bytes using the character set's
|
| 306 |
+
output codec.
|
| 307 |
+
:param maxlengths: Maximum line length iterator. Each element
|
| 308 |
+
returned from this iterator will provide the next maximum line
|
| 309 |
+
length. This parameter is used as an argument to built-in next()
|
| 310 |
+
and should never be exhausted. The maximum line lengths should
|
| 311 |
+
not count the RFC 2047 chrome. These line lengths are only a
|
| 312 |
+
hint; the splitter does the best it can.
|
| 313 |
+
:return: Lines of encoded strings, each with RFC 2047 chrome.
|
| 314 |
+
"""
|
| 315 |
+
# See which encoding we should use.
|
| 316 |
+
codec = self.output_codec or 'us-ascii'
|
| 317 |
+
header_bytes = _encode(string, codec)
|
| 318 |
+
encoder_module = self._get_encoder(header_bytes)
|
| 319 |
+
encoder = partial(encoder_module.header_encode, charset=codec)
|
| 320 |
+
# Calculate the number of characters that the RFC 2047 chrome will
|
| 321 |
+
# contribute to each line.
|
| 322 |
+
charset = self.get_output_charset()
|
| 323 |
+
extra = len(charset) + RFC2047_CHROME_LEN
|
| 324 |
+
# Now comes the hard part. We must encode bytes but we can't split on
|
| 325 |
+
# bytes because some character sets are variable length and each
|
| 326 |
+
# encoded word must stand on its own. So the problem is you have to
|
| 327 |
+
# encode to bytes to figure out this word's length, but you must split
|
| 328 |
+
# on characters. This causes two problems: first, we don't know how
|
| 329 |
+
# many octets a specific substring of unicode characters will get
|
| 330 |
+
# encoded to, and second, we don't know how many ASCII characters
|
| 331 |
+
# those octets will get encoded to. Unless we try it. Which seems
|
| 332 |
+
# inefficient. In the interest of being correct rather than fast (and
|
| 333 |
+
# in the hope that there will be few encoded headers in any such
|
| 334 |
+
# message), brute force it. :(
|
| 335 |
+
lines = []
|
| 336 |
+
current_line = []
|
| 337 |
+
maxlen = next(maxlengths) - extra
|
| 338 |
+
for character in string:
|
| 339 |
+
current_line.append(character)
|
| 340 |
+
this_line = EMPTYSTRING.join(current_line)
|
| 341 |
+
length = encoder_module.header_length(_encode(this_line, charset))
|
| 342 |
+
if length > maxlen:
|
| 343 |
+
# This last character doesn't fit so pop it off.
|
| 344 |
+
current_line.pop()
|
| 345 |
+
# Does nothing fit on the first line?
|
| 346 |
+
if not lines and not current_line:
|
| 347 |
+
lines.append(None)
|
| 348 |
+
else:
|
| 349 |
+
separator = (' ' if lines else '')
|
| 350 |
+
joined_line = EMPTYSTRING.join(current_line)
|
| 351 |
+
header_bytes = _encode(joined_line, codec)
|
| 352 |
+
lines.append(encoder(header_bytes))
|
| 353 |
+
current_line = [character]
|
| 354 |
+
maxlen = next(maxlengths) - extra
|
| 355 |
+
joined_line = EMPTYSTRING.join(current_line)
|
| 356 |
+
header_bytes = _encode(joined_line, codec)
|
| 357 |
+
lines.append(encoder(header_bytes))
|
| 358 |
+
return lines
|
| 359 |
+
|
| 360 |
+
def _get_encoder(self, header_bytes):
|
| 361 |
+
if self.header_encoding == BASE64:
|
| 362 |
+
return email.base64mime
|
| 363 |
+
elif self.header_encoding == QP:
|
| 364 |
+
return email.quoprimime
|
| 365 |
+
elif self.header_encoding == SHORTEST:
|
| 366 |
+
len64 = email.base64mime.header_length(header_bytes)
|
| 367 |
+
lenqp = email.quoprimime.header_length(header_bytes)
|
| 368 |
+
if len64 < lenqp:
|
| 369 |
+
return email.base64mime
|
| 370 |
+
else:
|
| 371 |
+
return email.quoprimime
|
| 372 |
+
else:
|
| 373 |
+
return None
|
| 374 |
+
|
| 375 |
+
def body_encode(self, string):
|
| 376 |
+
"""Body-encode a string by converting it first to bytes.
|
| 377 |
+
|
| 378 |
+
The type of encoding (base64 or quoted-printable) will be based on
|
| 379 |
+
self.body_encoding. If body_encoding is None, we assume the
|
| 380 |
+
output charset is a 7bit encoding, so re-encoding the decoded
|
| 381 |
+
string using the ascii codec produces the correct string version
|
| 382 |
+
of the content.
|
| 383 |
+
"""
|
| 384 |
+
if not string:
|
| 385 |
+
return string
|
| 386 |
+
if self.body_encoding is BASE64:
|
| 387 |
+
if isinstance(string, str):
|
| 388 |
+
string = string.encode(self.output_charset)
|
| 389 |
+
return email.base64mime.body_encode(string)
|
| 390 |
+
elif self.body_encoding is QP:
|
| 391 |
+
# quopromime.body_encode takes a string, but operates on it as if
|
| 392 |
+
# it were a list of byte codes. For a (minimal) history on why
|
| 393 |
+
# this is so, see changeset 0cf700464177. To correctly encode a
|
| 394 |
+
# character set, then, we must turn it into pseudo bytes via the
|
| 395 |
+
# latin1 charset, which will encode any byte as a single code point
|
| 396 |
+
# between 0 and 255, which is what body_encode is expecting.
|
| 397 |
+
if isinstance(string, str):
|
| 398 |
+
string = string.encode(self.output_charset)
|
| 399 |
+
string = string.decode('latin1')
|
| 400 |
+
return email.quoprimime.body_encode(string)
|
| 401 |
+
else:
|
| 402 |
+
if isinstance(string, str):
|
| 403 |
+
string = string.encode(self.output_charset).decode('ascii')
|
| 404 |
+
return string
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/email/contentmanager.py
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import binascii
|
| 2 |
+
import email.charset
|
| 3 |
+
import email.message
|
| 4 |
+
import email.errors
|
| 5 |
+
from email import quoprimime
|
| 6 |
+
|
| 7 |
+
class ContentManager:
|
| 8 |
+
|
| 9 |
+
def __init__(self):
|
| 10 |
+
self.get_handlers = {}
|
| 11 |
+
self.set_handlers = {}
|
| 12 |
+
|
| 13 |
+
def add_get_handler(self, key, handler):
|
| 14 |
+
self.get_handlers[key] = handler
|
| 15 |
+
|
| 16 |
+
def get_content(self, msg, *args, **kw):
|
| 17 |
+
content_type = msg.get_content_type()
|
| 18 |
+
if content_type in self.get_handlers:
|
| 19 |
+
return self.get_handlers[content_type](msg, *args, **kw)
|
| 20 |
+
maintype = msg.get_content_maintype()
|
| 21 |
+
if maintype in self.get_handlers:
|
| 22 |
+
return self.get_handlers[maintype](msg, *args, **kw)
|
| 23 |
+
if '' in self.get_handlers:
|
| 24 |
+
return self.get_handlers[''](msg, *args, **kw)
|
| 25 |
+
raise KeyError(content_type)
|
| 26 |
+
|
| 27 |
+
def add_set_handler(self, typekey, handler):
|
| 28 |
+
self.set_handlers[typekey] = handler
|
| 29 |
+
|
| 30 |
+
def set_content(self, msg, obj, *args, **kw):
|
| 31 |
+
if msg.get_content_maintype() == 'multipart':
|
| 32 |
+
# XXX: is this error a good idea or not? We can remove it later,
|
| 33 |
+
# but we can't add it later, so do it for now.
|
| 34 |
+
raise TypeError("set_content not valid on multipart")
|
| 35 |
+
handler = self._find_set_handler(msg, obj)
|
| 36 |
+
msg.clear_content()
|
| 37 |
+
handler(msg, obj, *args, **kw)
|
| 38 |
+
|
| 39 |
+
def _find_set_handler(self, msg, obj):
|
| 40 |
+
full_path_for_error = None
|
| 41 |
+
for typ in type(obj).__mro__:
|
| 42 |
+
if typ in self.set_handlers:
|
| 43 |
+
return self.set_handlers[typ]
|
| 44 |
+
qname = typ.__qualname__
|
| 45 |
+
modname = getattr(typ, '__module__', '')
|
| 46 |
+
full_path = '.'.join((modname, qname)) if modname else qname
|
| 47 |
+
if full_path_for_error is None:
|
| 48 |
+
full_path_for_error = full_path
|
| 49 |
+
if full_path in self.set_handlers:
|
| 50 |
+
return self.set_handlers[full_path]
|
| 51 |
+
if qname in self.set_handlers:
|
| 52 |
+
return self.set_handlers[qname]
|
| 53 |
+
name = typ.__name__
|
| 54 |
+
if name in self.set_handlers:
|
| 55 |
+
return self.set_handlers[name]
|
| 56 |
+
if None in self.set_handlers:
|
| 57 |
+
return self.set_handlers[None]
|
| 58 |
+
raise KeyError(full_path_for_error)
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
raw_data_manager = ContentManager()
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def get_text_content(msg, errors='replace'):
|
| 65 |
+
content = msg.get_payload(decode=True)
|
| 66 |
+
charset = msg.get_param('charset', 'ASCII')
|
| 67 |
+
return content.decode(charset, errors=errors)
|
| 68 |
+
raw_data_manager.add_get_handler('text', get_text_content)
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
def get_non_text_content(msg):
|
| 72 |
+
return msg.get_payload(decode=True)
|
| 73 |
+
for maintype in 'audio image video application'.split():
|
| 74 |
+
raw_data_manager.add_get_handler(maintype, get_non_text_content)
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def get_message_content(msg):
|
| 78 |
+
return msg.get_payload(0)
|
| 79 |
+
for subtype in 'rfc822 external-body'.split():
|
| 80 |
+
raw_data_manager.add_get_handler('message/'+subtype, get_message_content)
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
def get_and_fixup_unknown_message_content(msg):
|
| 84 |
+
# If we don't understand a message subtype, we are supposed to treat it as
|
| 85 |
+
# if it were application/octet-stream, per
|
| 86 |
+
# tools.ietf.org/html/rfc2046#section-5.2.4. Feedparser doesn't do that,
|
| 87 |
+
# so do our best to fix things up. Note that it is *not* appropriate to
|
| 88 |
+
# model message/partial content as Message objects, so they are handled
|
| 89 |
+
# here as well. (How to reassemble them is out of scope for this comment :)
|
| 90 |
+
return bytes(msg.get_payload(0))
|
| 91 |
+
raw_data_manager.add_get_handler('message',
|
| 92 |
+
get_and_fixup_unknown_message_content)
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
def _prepare_set(msg, maintype, subtype, headers):
|
| 96 |
+
msg['Content-Type'] = '/'.join((maintype, subtype))
|
| 97 |
+
if headers:
|
| 98 |
+
if not hasattr(headers[0], 'name'):
|
| 99 |
+
mp = msg.policy
|
| 100 |
+
headers = [mp.header_factory(*mp.header_source_parse([header]))
|
| 101 |
+
for header in headers]
|
| 102 |
+
try:
|
| 103 |
+
for header in headers:
|
| 104 |
+
if header.defects:
|
| 105 |
+
raise header.defects[0]
|
| 106 |
+
msg[header.name] = header
|
| 107 |
+
except email.errors.HeaderDefect as exc:
|
| 108 |
+
raise ValueError("Invalid header: {}".format(
|
| 109 |
+
header.fold(policy=msg.policy))) from exc
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
def _finalize_set(msg, disposition, filename, cid, params):
|
| 113 |
+
if disposition is None and filename is not None:
|
| 114 |
+
disposition = 'attachment'
|
| 115 |
+
if disposition is not None:
|
| 116 |
+
msg['Content-Disposition'] = disposition
|
| 117 |
+
if filename is not None:
|
| 118 |
+
msg.set_param('filename',
|
| 119 |
+
filename,
|
| 120 |
+
header='Content-Disposition',
|
| 121 |
+
replace=True)
|
| 122 |
+
if cid is not None:
|
| 123 |
+
msg['Content-ID'] = cid
|
| 124 |
+
if params is not None:
|
| 125 |
+
for key, value in params.items():
|
| 126 |
+
msg.set_param(key, value)
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
# XXX: This is a cleaned-up version of base64mime.body_encode (including a bug
|
| 130 |
+
# fix in the calculation of unencoded_bytes_per_line). It would be nice to
|
| 131 |
+
# drop both this and quoprimime.body_encode in favor of enhanced binascii
|
| 132 |
+
# routines that accepted a max_line_length parameter.
|
| 133 |
+
def _encode_base64(data, max_line_length):
|
| 134 |
+
encoded_lines = []
|
| 135 |
+
unencoded_bytes_per_line = max_line_length // 4 * 3
|
| 136 |
+
for i in range(0, len(data), unencoded_bytes_per_line):
|
| 137 |
+
thisline = data[i:i+unencoded_bytes_per_line]
|
| 138 |
+
encoded_lines.append(binascii.b2a_base64(thisline).decode('ascii'))
|
| 139 |
+
return ''.join(encoded_lines)
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
def _encode_text(string, charset, cte, policy):
|
| 143 |
+
lines = string.encode(charset).splitlines()
|
| 144 |
+
linesep = policy.linesep.encode('ascii')
|
| 145 |
+
def embedded_body(lines): return linesep.join(lines) + linesep
|
| 146 |
+
def normal_body(lines): return b'\n'.join(lines) + b'\n'
|
| 147 |
+
if cte==None:
|
| 148 |
+
# Use heuristics to decide on the "best" encoding.
|
| 149 |
+
if max((len(x) for x in lines), default=0) <= policy.max_line_length:
|
| 150 |
+
try:
|
| 151 |
+
return '7bit', normal_body(lines).decode('ascii')
|
| 152 |
+
except UnicodeDecodeError:
|
| 153 |
+
pass
|
| 154 |
+
if policy.cte_type == '8bit':
|
| 155 |
+
return '8bit', normal_body(lines).decode('ascii', 'surrogateescape')
|
| 156 |
+
sniff = embedded_body(lines[:10])
|
| 157 |
+
sniff_qp = quoprimime.body_encode(sniff.decode('latin-1'),
|
| 158 |
+
policy.max_line_length)
|
| 159 |
+
sniff_base64 = binascii.b2a_base64(sniff)
|
| 160 |
+
# This is a little unfair to qp; it includes lineseps, base64 doesn't.
|
| 161 |
+
if len(sniff_qp) > len(sniff_base64):
|
| 162 |
+
cte = 'base64'
|
| 163 |
+
else:
|
| 164 |
+
cte = 'quoted-printable'
|
| 165 |
+
if len(lines) <= 10:
|
| 166 |
+
return cte, sniff_qp
|
| 167 |
+
if cte == '7bit':
|
| 168 |
+
data = normal_body(lines).decode('ascii')
|
| 169 |
+
elif cte == '8bit':
|
| 170 |
+
data = normal_body(lines).decode('ascii', 'surrogateescape')
|
| 171 |
+
elif cte == 'quoted-printable':
|
| 172 |
+
data = quoprimime.body_encode(normal_body(lines).decode('latin-1'),
|
| 173 |
+
policy.max_line_length)
|
| 174 |
+
elif cte == 'base64':
|
| 175 |
+
data = _encode_base64(embedded_body(lines), policy.max_line_length)
|
| 176 |
+
else:
|
| 177 |
+
raise ValueError("Unknown content transfer encoding {}".format(cte))
|
| 178 |
+
return cte, data
|
| 179 |
+
|
| 180 |
+
|
| 181 |
+
def set_text_content(msg, string, subtype="plain", charset='utf-8', cte=None,
|
| 182 |
+
disposition=None, filename=None, cid=None,
|
| 183 |
+
params=None, headers=None):
|
| 184 |
+
_prepare_set(msg, 'text', subtype, headers)
|
| 185 |
+
cte, payload = _encode_text(string, charset, cte, msg.policy)
|
| 186 |
+
msg.set_payload(payload)
|
| 187 |
+
msg.set_param('charset',
|
| 188 |
+
email.charset.ALIASES.get(charset, charset),
|
| 189 |
+
replace=True)
|
| 190 |
+
msg['Content-Transfer-Encoding'] = cte
|
| 191 |
+
_finalize_set(msg, disposition, filename, cid, params)
|
| 192 |
+
raw_data_manager.add_set_handler(str, set_text_content)
|
| 193 |
+
|
| 194 |
+
|
| 195 |
+
def set_message_content(msg, message, subtype="rfc822", cte=None,
|
| 196 |
+
disposition=None, filename=None, cid=None,
|
| 197 |
+
params=None, headers=None):
|
| 198 |
+
if subtype == 'partial':
|
| 199 |
+
raise ValueError("message/partial is not supported for Message objects")
|
| 200 |
+
if subtype == 'rfc822':
|
| 201 |
+
if cte not in (None, '7bit', '8bit', 'binary'):
|
| 202 |
+
# http://tools.ietf.org/html/rfc2046#section-5.2.1 mandate.
|
| 203 |
+
raise ValueError(
|
| 204 |
+
"message/rfc822 parts do not support cte={}".format(cte))
|
| 205 |
+
# 8bit will get coerced on serialization if policy.cte_type='7bit'. We
|
| 206 |
+
# may end up claiming 8bit when it isn't needed, but the only negative
|
| 207 |
+
# result of that should be a gateway that needs to coerce to 7bit
|
| 208 |
+
# having to look through the whole embedded message to discover whether
|
| 209 |
+
# or not it actually has to do anything.
|
| 210 |
+
cte = '8bit' if cte is None else cte
|
| 211 |
+
elif subtype == 'external-body':
|
| 212 |
+
if cte not in (None, '7bit'):
|
| 213 |
+
# http://tools.ietf.org/html/rfc2046#section-5.2.3 mandate.
|
| 214 |
+
raise ValueError(
|
| 215 |
+
"message/external-body parts do not support cte={}".format(cte))
|
| 216 |
+
cte = '7bit'
|
| 217 |
+
elif cte is None:
|
| 218 |
+
# http://tools.ietf.org/html/rfc2046#section-5.2.4 says all future
|
| 219 |
+
# subtypes should be restricted to 7bit, so assume that.
|
| 220 |
+
cte = '7bit'
|
| 221 |
+
_prepare_set(msg, 'message', subtype, headers)
|
| 222 |
+
msg.set_payload([message])
|
| 223 |
+
msg['Content-Transfer-Encoding'] = cte
|
| 224 |
+
_finalize_set(msg, disposition, filename, cid, params)
|
| 225 |
+
raw_data_manager.add_set_handler(email.message.Message, set_message_content)
|
| 226 |
+
|
| 227 |
+
|
| 228 |
+
def set_bytes_content(msg, data, maintype, subtype, cte='base64',
|
| 229 |
+
disposition=None, filename=None, cid=None,
|
| 230 |
+
params=None, headers=None):
|
| 231 |
+
_prepare_set(msg, maintype, subtype, headers)
|
| 232 |
+
if cte == 'base64':
|
| 233 |
+
data = _encode_base64(data, max_line_length=msg.policy.max_line_length)
|
| 234 |
+
elif cte == 'quoted-printable':
|
| 235 |
+
# XXX: quoprimime.body_encode won't encode newline characters in data,
|
| 236 |
+
# so we can't use it. This means max_line_length is ignored. Another
|
| 237 |
+
# bug to fix later. (Note: encoders.quopri is broken on line ends.)
|
| 238 |
+
data = binascii.b2a_qp(data, istext=False, header=False, quotetabs=True)
|
| 239 |
+
data = data.decode('ascii')
|
| 240 |
+
elif cte == '7bit':
|
| 241 |
+
# Make sure it really is only ASCII. The early warning here seems
|
| 242 |
+
# worth the overhead...if you care write your own content manager :).
|
| 243 |
+
data.encode('ascii')
|
| 244 |
+
elif cte in ('8bit', 'binary'):
|
| 245 |
+
data = data.decode('ascii', 'surrogateescape')
|
| 246 |
+
msg.set_payload(data)
|
| 247 |
+
msg['Content-Transfer-Encoding'] = cte
|
| 248 |
+
_finalize_set(msg, disposition, filename, cid, params)
|
| 249 |
+
for typ in (bytes, bytearray, memoryview):
|
| 250 |
+
raw_data_manager.add_set_handler(typ, set_bytes_content)
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/email/encoders.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (C) 2001-2006 Python Software Foundation
|
| 2 |
+
# Author: Barry Warsaw
|
| 3 |
+
# Contact: email-sig@python.org
|
| 4 |
+
|
| 5 |
+
"""Encodings and related functions."""
|
| 6 |
+
|
| 7 |
+
__all__ = [
|
| 8 |
+
'encode_7or8bit',
|
| 9 |
+
'encode_base64',
|
| 10 |
+
'encode_noop',
|
| 11 |
+
'encode_quopri',
|
| 12 |
+
]
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
from base64 import encodebytes as _bencode
|
| 16 |
+
from quopri import encodestring as _encodestring
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def _qencode(s):
|
| 21 |
+
enc = _encodestring(s, quotetabs=True)
|
| 22 |
+
# Must encode spaces, which quopri.encodestring() doesn't do
|
| 23 |
+
return enc.replace(b' ', b'=20')
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def encode_base64(msg):
|
| 27 |
+
"""Encode the message's payload in Base64.
|
| 28 |
+
|
| 29 |
+
Also, add an appropriate Content-Transfer-Encoding header.
|
| 30 |
+
"""
|
| 31 |
+
orig = msg.get_payload(decode=True)
|
| 32 |
+
encdata = str(_bencode(orig), 'ascii')
|
| 33 |
+
msg.set_payload(encdata)
|
| 34 |
+
msg['Content-Transfer-Encoding'] = 'base64'
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def encode_quopri(msg):
|
| 39 |
+
"""Encode the message's payload in quoted-printable.
|
| 40 |
+
|
| 41 |
+
Also, add an appropriate Content-Transfer-Encoding header.
|
| 42 |
+
"""
|
| 43 |
+
orig = msg.get_payload(decode=True)
|
| 44 |
+
encdata = _qencode(orig)
|
| 45 |
+
msg.set_payload(encdata)
|
| 46 |
+
msg['Content-Transfer-Encoding'] = 'quoted-printable'
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def encode_7or8bit(msg):
|
| 51 |
+
"""Set the Content-Transfer-Encoding header to 7bit or 8bit."""
|
| 52 |
+
orig = msg.get_payload(decode=True)
|
| 53 |
+
if orig is None:
|
| 54 |
+
# There's no payload. For backwards compatibility we use 7bit
|
| 55 |
+
msg['Content-Transfer-Encoding'] = '7bit'
|
| 56 |
+
return
|
| 57 |
+
# We play a trick to make this go fast. If decoding from ASCII succeeds,
|
| 58 |
+
# we know the data must be 7bit, otherwise treat it as 8bit.
|
| 59 |
+
try:
|
| 60 |
+
orig.decode('ascii')
|
| 61 |
+
except UnicodeError:
|
| 62 |
+
msg['Content-Transfer-Encoding'] = '8bit'
|
| 63 |
+
else:
|
| 64 |
+
msg['Content-Transfer-Encoding'] = '7bit'
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def encode_noop(msg):
|
| 69 |
+
"""Do nothing."""
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/email/errors.py
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (C) 2001-2006 Python Software Foundation
|
| 2 |
+
# Author: Barry Warsaw
|
| 3 |
+
# Contact: email-sig@python.org
|
| 4 |
+
|
| 5 |
+
"""email package exception classes."""
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class MessageError(Exception):
|
| 9 |
+
"""Base class for errors in the email package."""
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class MessageParseError(MessageError):
|
| 13 |
+
"""Base class for message parsing errors."""
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class HeaderParseError(MessageParseError):
|
| 17 |
+
"""Error while parsing headers."""
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
class BoundaryError(MessageParseError):
|
| 21 |
+
"""Couldn't find terminating boundary."""
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
class MultipartConversionError(MessageError, TypeError):
|
| 25 |
+
"""Conversion to a multipart is prohibited."""
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
class CharsetError(MessageError):
|
| 29 |
+
"""An illegal charset was given."""
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
# These are parsing defects which the parser was able to work around.
|
| 33 |
+
class MessageDefect(ValueError):
|
| 34 |
+
"""Base class for a message defect."""
|
| 35 |
+
|
| 36 |
+
def __init__(self, line=None):
|
| 37 |
+
if line is not None:
|
| 38 |
+
super().__init__(line)
|
| 39 |
+
self.line = line
|
| 40 |
+
|
| 41 |
+
class NoBoundaryInMultipartDefect(MessageDefect):
|
| 42 |
+
"""A message claimed to be a multipart but had no boundary parameter."""
|
| 43 |
+
|
| 44 |
+
class StartBoundaryNotFoundDefect(MessageDefect):
|
| 45 |
+
"""The claimed start boundary was never found."""
|
| 46 |
+
|
| 47 |
+
class CloseBoundaryNotFoundDefect(MessageDefect):
|
| 48 |
+
"""A start boundary was found, but not the corresponding close boundary."""
|
| 49 |
+
|
| 50 |
+
class FirstHeaderLineIsContinuationDefect(MessageDefect):
|
| 51 |
+
"""A message had a continuation line as its first header line."""
|
| 52 |
+
|
| 53 |
+
class MisplacedEnvelopeHeaderDefect(MessageDefect):
|
| 54 |
+
"""A 'Unix-from' header was found in the middle of a header block."""
|
| 55 |
+
|
| 56 |
+
class MissingHeaderBodySeparatorDefect(MessageDefect):
|
| 57 |
+
"""Found line with no leading whitespace and no colon before blank line."""
|
| 58 |
+
# XXX: backward compatibility, just in case (it was never emitted).
|
| 59 |
+
MalformedHeaderDefect = MissingHeaderBodySeparatorDefect
|
| 60 |
+
|
| 61 |
+
class MultipartInvariantViolationDefect(MessageDefect):
|
| 62 |
+
"""A message claimed to be a multipart but no subparts were found."""
|
| 63 |
+
|
| 64 |
+
class InvalidMultipartContentTransferEncodingDefect(MessageDefect):
|
| 65 |
+
"""An invalid content transfer encoding was set on the multipart itself."""
|
| 66 |
+
|
| 67 |
+
class UndecodableBytesDefect(MessageDefect):
|
| 68 |
+
"""Header contained bytes that could not be decoded"""
|
| 69 |
+
|
| 70 |
+
class InvalidBase64PaddingDefect(MessageDefect):
|
| 71 |
+
"""base64 encoded sequence had an incorrect length"""
|
| 72 |
+
|
| 73 |
+
class InvalidBase64CharactersDefect(MessageDefect):
|
| 74 |
+
"""base64 encoded sequence had characters not in base64 alphabet"""
|
| 75 |
+
|
| 76 |
+
class InvalidBase64LengthDefect(MessageDefect):
|
| 77 |
+
"""base64 encoded sequence had invalid length (1 mod 4)"""
|
| 78 |
+
|
| 79 |
+
# These errors are specific to header parsing.
|
| 80 |
+
|
| 81 |
+
class HeaderDefect(MessageDefect):
|
| 82 |
+
"""Base class for a header defect."""
|
| 83 |
+
|
| 84 |
+
def __init__(self, *args, **kw):
|
| 85 |
+
super().__init__(*args, **kw)
|
| 86 |
+
|
| 87 |
+
class InvalidHeaderDefect(HeaderDefect):
|
| 88 |
+
"""Header is not valid, message gives details."""
|
| 89 |
+
|
| 90 |
+
class HeaderMissingRequiredValue(HeaderDefect):
|
| 91 |
+
"""A header that must have a value had none"""
|
| 92 |
+
|
| 93 |
+
class NonPrintableDefect(HeaderDefect):
|
| 94 |
+
"""ASCII characters outside the ascii-printable range found"""
|
| 95 |
+
|
| 96 |
+
def __init__(self, non_printables):
|
| 97 |
+
super().__init__(non_printables)
|
| 98 |
+
self.non_printables = non_printables
|
| 99 |
+
|
| 100 |
+
def __str__(self):
|
| 101 |
+
return ("the following ASCII non-printables found in header: "
|
| 102 |
+
"{}".format(self.non_printables))
|
| 103 |
+
|
| 104 |
+
class ObsoleteHeaderDefect(HeaderDefect):
|
| 105 |
+
"""Header uses syntax declared obsolete by RFC 5322"""
|
| 106 |
+
|
| 107 |
+
class NonASCIILocalPartDefect(HeaderDefect):
|
| 108 |
+
"""local_part contains non-ASCII characters"""
|
| 109 |
+
# This defect only occurs during unicode parsing, not when
|
| 110 |
+
# parsing messages decoded from binary.
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/email/feedparser.py
ADDED
|
@@ -0,0 +1,536 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (C) 2004-2006 Python Software Foundation
|
| 2 |
+
# Authors: Baxter, Wouters and Warsaw
|
| 3 |
+
# Contact: email-sig@python.org
|
| 4 |
+
|
| 5 |
+
"""FeedParser - An email feed parser.
|
| 6 |
+
|
| 7 |
+
The feed parser implements an interface for incrementally parsing an email
|
| 8 |
+
message, line by line. This has advantages for certain applications, such as
|
| 9 |
+
those reading email messages off a socket.
|
| 10 |
+
|
| 11 |
+
FeedParser.feed() is the primary interface for pushing new data into the
|
| 12 |
+
parser. It returns when there's nothing more it can do with the available
|
| 13 |
+
data. When you have no more data to push into the parser, call .close().
|
| 14 |
+
This completes the parsing and returns the root message object.
|
| 15 |
+
|
| 16 |
+
The other advantage of this parser is that it will never raise a parsing
|
| 17 |
+
exception. Instead, when it finds something unexpected, it adds a 'defect' to
|
| 18 |
+
the current message. Defects are just instances that live on the message
|
| 19 |
+
object's .defects attribute.
|
| 20 |
+
"""
|
| 21 |
+
|
| 22 |
+
__all__ = ['FeedParser', 'BytesFeedParser']
|
| 23 |
+
|
| 24 |
+
import re
|
| 25 |
+
|
| 26 |
+
from email import errors
|
| 27 |
+
from email._policybase import compat32
|
| 28 |
+
from collections import deque
|
| 29 |
+
from io import StringIO
|
| 30 |
+
|
| 31 |
+
NLCRE = re.compile(r'\r\n|\r|\n')
|
| 32 |
+
NLCRE_bol = re.compile(r'(\r\n|\r|\n)')
|
| 33 |
+
NLCRE_eol = re.compile(r'(\r\n|\r|\n)\Z')
|
| 34 |
+
NLCRE_crack = re.compile(r'(\r\n|\r|\n)')
|
| 35 |
+
# RFC 2822 $3.6.8 Optional fields. ftext is %d33-57 / %d59-126, Any character
|
| 36 |
+
# except controls, SP, and ":".
|
| 37 |
+
headerRE = re.compile(r'^(From |[\041-\071\073-\176]*:|[\t ])')
|
| 38 |
+
EMPTYSTRING = ''
|
| 39 |
+
NL = '\n'
|
| 40 |
+
|
| 41 |
+
NeedMoreData = object()
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
class BufferedSubFile(object):
|
| 46 |
+
"""A file-ish object that can have new data loaded into it.
|
| 47 |
+
|
| 48 |
+
You can also push and pop line-matching predicates onto a stack. When the
|
| 49 |
+
current predicate matches the current line, a false EOF response
|
| 50 |
+
(i.e. empty string) is returned instead. This lets the parser adhere to a
|
| 51 |
+
simple abstraction -- it parses until EOF closes the current message.
|
| 52 |
+
"""
|
| 53 |
+
def __init__(self):
|
| 54 |
+
# Text stream of the last partial line pushed into this object.
|
| 55 |
+
# See issue 22233 for why this is a text stream and not a list.
|
| 56 |
+
self._partial = StringIO(newline='')
|
| 57 |
+
# A deque of full, pushed lines
|
| 58 |
+
self._lines = deque()
|
| 59 |
+
# The stack of false-EOF checking predicates.
|
| 60 |
+
self._eofstack = []
|
| 61 |
+
# A flag indicating whether the file has been closed or not.
|
| 62 |
+
self._closed = False
|
| 63 |
+
|
| 64 |
+
def push_eof_matcher(self, pred):
|
| 65 |
+
self._eofstack.append(pred)
|
| 66 |
+
|
| 67 |
+
def pop_eof_matcher(self):
|
| 68 |
+
return self._eofstack.pop()
|
| 69 |
+
|
| 70 |
+
def close(self):
|
| 71 |
+
# Don't forget any trailing partial line.
|
| 72 |
+
self._partial.seek(0)
|
| 73 |
+
self.pushlines(self._partial.readlines())
|
| 74 |
+
self._partial.seek(0)
|
| 75 |
+
self._partial.truncate()
|
| 76 |
+
self._closed = True
|
| 77 |
+
|
| 78 |
+
def readline(self):
|
| 79 |
+
if not self._lines:
|
| 80 |
+
if self._closed:
|
| 81 |
+
return ''
|
| 82 |
+
return NeedMoreData
|
| 83 |
+
# Pop the line off the stack and see if it matches the current
|
| 84 |
+
# false-EOF predicate.
|
| 85 |
+
line = self._lines.popleft()
|
| 86 |
+
# RFC 2046, section 5.1.2 requires us to recognize outer level
|
| 87 |
+
# boundaries at any level of inner nesting. Do this, but be sure it's
|
| 88 |
+
# in the order of most to least nested.
|
| 89 |
+
for ateof in reversed(self._eofstack):
|
| 90 |
+
if ateof(line):
|
| 91 |
+
# We're at the false EOF. But push the last line back first.
|
| 92 |
+
self._lines.appendleft(line)
|
| 93 |
+
return ''
|
| 94 |
+
return line
|
| 95 |
+
|
| 96 |
+
def unreadline(self, line):
|
| 97 |
+
# Let the consumer push a line back into the buffer.
|
| 98 |
+
assert line is not NeedMoreData
|
| 99 |
+
self._lines.appendleft(line)
|
| 100 |
+
|
| 101 |
+
def push(self, data):
|
| 102 |
+
"""Push some new data into this object."""
|
| 103 |
+
self._partial.write(data)
|
| 104 |
+
if '\n' not in data and '\r' not in data:
|
| 105 |
+
# No new complete lines, wait for more.
|
| 106 |
+
return
|
| 107 |
+
|
| 108 |
+
# Crack into lines, preserving the linesep characters.
|
| 109 |
+
self._partial.seek(0)
|
| 110 |
+
parts = self._partial.readlines()
|
| 111 |
+
self._partial.seek(0)
|
| 112 |
+
self._partial.truncate()
|
| 113 |
+
|
| 114 |
+
# If the last element of the list does not end in a newline, then treat
|
| 115 |
+
# it as a partial line. We only check for '\n' here because a line
|
| 116 |
+
# ending with '\r' might be a line that was split in the middle of a
|
| 117 |
+
# '\r\n' sequence (see bugs 1555570 and 1721862).
|
| 118 |
+
if not parts[-1].endswith('\n'):
|
| 119 |
+
self._partial.write(parts.pop())
|
| 120 |
+
self.pushlines(parts)
|
| 121 |
+
|
| 122 |
+
def pushlines(self, lines):
|
| 123 |
+
self._lines.extend(lines)
|
| 124 |
+
|
| 125 |
+
def __iter__(self):
|
| 126 |
+
return self
|
| 127 |
+
|
| 128 |
+
def __next__(self):
|
| 129 |
+
line = self.readline()
|
| 130 |
+
if line == '':
|
| 131 |
+
raise StopIteration
|
| 132 |
+
return line
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
class FeedParser:
|
| 137 |
+
"""A feed-style parser of email."""
|
| 138 |
+
|
| 139 |
+
def __init__(self, _factory=None, *, policy=compat32):
|
| 140 |
+
"""_factory is called with no arguments to create a new message obj
|
| 141 |
+
|
| 142 |
+
The policy keyword specifies a policy object that controls a number of
|
| 143 |
+
aspects of the parser's operation. The default policy maintains
|
| 144 |
+
backward compatibility.
|
| 145 |
+
|
| 146 |
+
"""
|
| 147 |
+
self.policy = policy
|
| 148 |
+
self._old_style_factory = False
|
| 149 |
+
if _factory is None:
|
| 150 |
+
if policy.message_factory is None:
|
| 151 |
+
from email.message import Message
|
| 152 |
+
self._factory = Message
|
| 153 |
+
else:
|
| 154 |
+
self._factory = policy.message_factory
|
| 155 |
+
else:
|
| 156 |
+
self._factory = _factory
|
| 157 |
+
try:
|
| 158 |
+
_factory(policy=self.policy)
|
| 159 |
+
except TypeError:
|
| 160 |
+
# Assume this is an old-style factory
|
| 161 |
+
self._old_style_factory = True
|
| 162 |
+
self._input = BufferedSubFile()
|
| 163 |
+
self._msgstack = []
|
| 164 |
+
self._parse = self._parsegen().__next__
|
| 165 |
+
self._cur = None
|
| 166 |
+
self._last = None
|
| 167 |
+
self._headersonly = False
|
| 168 |
+
|
| 169 |
+
# Non-public interface for supporting Parser's headersonly flag
|
| 170 |
+
def _set_headersonly(self):
|
| 171 |
+
self._headersonly = True
|
| 172 |
+
|
| 173 |
+
def feed(self, data):
|
| 174 |
+
"""Push more data into the parser."""
|
| 175 |
+
self._input.push(data)
|
| 176 |
+
self._call_parse()
|
| 177 |
+
|
| 178 |
+
def _call_parse(self):
|
| 179 |
+
try:
|
| 180 |
+
self._parse()
|
| 181 |
+
except StopIteration:
|
| 182 |
+
pass
|
| 183 |
+
|
| 184 |
+
def close(self):
|
| 185 |
+
"""Parse all remaining data and return the root message object."""
|
| 186 |
+
self._input.close()
|
| 187 |
+
self._call_parse()
|
| 188 |
+
root = self._pop_message()
|
| 189 |
+
assert not self._msgstack
|
| 190 |
+
# Look for final set of defects
|
| 191 |
+
if root.get_content_maintype() == 'multipart' \
|
| 192 |
+
and not root.is_multipart():
|
| 193 |
+
defect = errors.MultipartInvariantViolationDefect()
|
| 194 |
+
self.policy.handle_defect(root, defect)
|
| 195 |
+
return root
|
| 196 |
+
|
| 197 |
+
def _new_message(self):
|
| 198 |
+
if self._old_style_factory:
|
| 199 |
+
msg = self._factory()
|
| 200 |
+
else:
|
| 201 |
+
msg = self._factory(policy=self.policy)
|
| 202 |
+
if self._cur and self._cur.get_content_type() == 'multipart/digest':
|
| 203 |
+
msg.set_default_type('message/rfc822')
|
| 204 |
+
if self._msgstack:
|
| 205 |
+
self._msgstack[-1].attach(msg)
|
| 206 |
+
self._msgstack.append(msg)
|
| 207 |
+
self._cur = msg
|
| 208 |
+
self._last = msg
|
| 209 |
+
|
| 210 |
+
def _pop_message(self):
|
| 211 |
+
retval = self._msgstack.pop()
|
| 212 |
+
if self._msgstack:
|
| 213 |
+
self._cur = self._msgstack[-1]
|
| 214 |
+
else:
|
| 215 |
+
self._cur = None
|
| 216 |
+
return retval
|
| 217 |
+
|
| 218 |
+
def _parsegen(self):
|
| 219 |
+
# Create a new message and start by parsing headers.
|
| 220 |
+
self._new_message()
|
| 221 |
+
headers = []
|
| 222 |
+
# Collect the headers, searching for a line that doesn't match the RFC
|
| 223 |
+
# 2822 header or continuation pattern (including an empty line).
|
| 224 |
+
for line in self._input:
|
| 225 |
+
if line is NeedMoreData:
|
| 226 |
+
yield NeedMoreData
|
| 227 |
+
continue
|
| 228 |
+
if not headerRE.match(line):
|
| 229 |
+
# If we saw the RFC defined header/body separator
|
| 230 |
+
# (i.e. newline), just throw it away. Otherwise the line is
|
| 231 |
+
# part of the body so push it back.
|
| 232 |
+
if not NLCRE.match(line):
|
| 233 |
+
defect = errors.MissingHeaderBodySeparatorDefect()
|
| 234 |
+
self.policy.handle_defect(self._cur, defect)
|
| 235 |
+
self._input.unreadline(line)
|
| 236 |
+
break
|
| 237 |
+
headers.append(line)
|
| 238 |
+
# Done with the headers, so parse them and figure out what we're
|
| 239 |
+
# supposed to see in the body of the message.
|
| 240 |
+
self._parse_headers(headers)
|
| 241 |
+
# Headers-only parsing is a backwards compatibility hack, which was
|
| 242 |
+
# necessary in the older parser, which could raise errors. All
|
| 243 |
+
# remaining lines in the input are thrown into the message body.
|
| 244 |
+
if self._headersonly:
|
| 245 |
+
lines = []
|
| 246 |
+
while True:
|
| 247 |
+
line = self._input.readline()
|
| 248 |
+
if line is NeedMoreData:
|
| 249 |
+
yield NeedMoreData
|
| 250 |
+
continue
|
| 251 |
+
if line == '':
|
| 252 |
+
break
|
| 253 |
+
lines.append(line)
|
| 254 |
+
self._cur.set_payload(EMPTYSTRING.join(lines))
|
| 255 |
+
return
|
| 256 |
+
if self._cur.get_content_type() == 'message/delivery-status':
|
| 257 |
+
# message/delivery-status contains blocks of headers separated by
|
| 258 |
+
# a blank line. We'll represent each header block as a separate
|
| 259 |
+
# nested message object, but the processing is a bit different
|
| 260 |
+
# than standard message/* types because there is no body for the
|
| 261 |
+
# nested messages. A blank line separates the subparts.
|
| 262 |
+
while True:
|
| 263 |
+
self._input.push_eof_matcher(NLCRE.match)
|
| 264 |
+
for retval in self._parsegen():
|
| 265 |
+
if retval is NeedMoreData:
|
| 266 |
+
yield NeedMoreData
|
| 267 |
+
continue
|
| 268 |
+
break
|
| 269 |
+
msg = self._pop_message()
|
| 270 |
+
# We need to pop the EOF matcher in order to tell if we're at
|
| 271 |
+
# the end of the current file, not the end of the last block
|
| 272 |
+
# of message headers.
|
| 273 |
+
self._input.pop_eof_matcher()
|
| 274 |
+
# The input stream must be sitting at the newline or at the
|
| 275 |
+
# EOF. We want to see if we're at the end of this subpart, so
|
| 276 |
+
# first consume the blank line, then test the next line to see
|
| 277 |
+
# if we're at this subpart's EOF.
|
| 278 |
+
while True:
|
| 279 |
+
line = self._input.readline()
|
| 280 |
+
if line is NeedMoreData:
|
| 281 |
+
yield NeedMoreData
|
| 282 |
+
continue
|
| 283 |
+
break
|
| 284 |
+
while True:
|
| 285 |
+
line = self._input.readline()
|
| 286 |
+
if line is NeedMoreData:
|
| 287 |
+
yield NeedMoreData
|
| 288 |
+
continue
|
| 289 |
+
break
|
| 290 |
+
if line == '':
|
| 291 |
+
break
|
| 292 |
+
# Not at EOF so this is a line we're going to need.
|
| 293 |
+
self._input.unreadline(line)
|
| 294 |
+
return
|
| 295 |
+
if self._cur.get_content_maintype() == 'message':
|
| 296 |
+
# The message claims to be a message/* type, then what follows is
|
| 297 |
+
# another RFC 2822 message.
|
| 298 |
+
for retval in self._parsegen():
|
| 299 |
+
if retval is NeedMoreData:
|
| 300 |
+
yield NeedMoreData
|
| 301 |
+
continue
|
| 302 |
+
break
|
| 303 |
+
self._pop_message()
|
| 304 |
+
return
|
| 305 |
+
if self._cur.get_content_maintype() == 'multipart':
|
| 306 |
+
boundary = self._cur.get_boundary()
|
| 307 |
+
if boundary is None:
|
| 308 |
+
# The message /claims/ to be a multipart but it has not
|
| 309 |
+
# defined a boundary. That's a problem which we'll handle by
|
| 310 |
+
# reading everything until the EOF and marking the message as
|
| 311 |
+
# defective.
|
| 312 |
+
defect = errors.NoBoundaryInMultipartDefect()
|
| 313 |
+
self.policy.handle_defect(self._cur, defect)
|
| 314 |
+
lines = []
|
| 315 |
+
for line in self._input:
|
| 316 |
+
if line is NeedMoreData:
|
| 317 |
+
yield NeedMoreData
|
| 318 |
+
continue
|
| 319 |
+
lines.append(line)
|
| 320 |
+
self._cur.set_payload(EMPTYSTRING.join(lines))
|
| 321 |
+
return
|
| 322 |
+
# Make sure a valid content type was specified per RFC 2045:6.4.
|
| 323 |
+
if (str(self._cur.get('content-transfer-encoding', '8bit')).lower()
|
| 324 |
+
not in ('7bit', '8bit', 'binary')):
|
| 325 |
+
defect = errors.InvalidMultipartContentTransferEncodingDefect()
|
| 326 |
+
self.policy.handle_defect(self._cur, defect)
|
| 327 |
+
# Create a line match predicate which matches the inter-part
|
| 328 |
+
# boundary as well as the end-of-multipart boundary. Don't push
|
| 329 |
+
# this onto the input stream until we've scanned past the
|
| 330 |
+
# preamble.
|
| 331 |
+
separator = '--' + boundary
|
| 332 |
+
boundaryre = re.compile(
|
| 333 |
+
'(?P<sep>' + re.escape(separator) +
|
| 334 |
+
r')(?P<end>--)?(?P<ws>[ \t]*)(?P<linesep>\r\n|\r|\n)?$')
|
| 335 |
+
capturing_preamble = True
|
| 336 |
+
preamble = []
|
| 337 |
+
linesep = False
|
| 338 |
+
close_boundary_seen = False
|
| 339 |
+
while True:
|
| 340 |
+
line = self._input.readline()
|
| 341 |
+
if line is NeedMoreData:
|
| 342 |
+
yield NeedMoreData
|
| 343 |
+
continue
|
| 344 |
+
if line == '':
|
| 345 |
+
break
|
| 346 |
+
mo = boundaryre.match(line)
|
| 347 |
+
if mo:
|
| 348 |
+
# If we're looking at the end boundary, we're done with
|
| 349 |
+
# this multipart. If there was a newline at the end of
|
| 350 |
+
# the closing boundary, then we need to initialize the
|
| 351 |
+
# epilogue with the empty string (see below).
|
| 352 |
+
if mo.group('end'):
|
| 353 |
+
close_boundary_seen = True
|
| 354 |
+
linesep = mo.group('linesep')
|
| 355 |
+
break
|
| 356 |
+
# We saw an inter-part boundary. Were we in the preamble?
|
| 357 |
+
if capturing_preamble:
|
| 358 |
+
if preamble:
|
| 359 |
+
# According to RFC 2046, the last newline belongs
|
| 360 |
+
# to the boundary.
|
| 361 |
+
lastline = preamble[-1]
|
| 362 |
+
eolmo = NLCRE_eol.search(lastline)
|
| 363 |
+
if eolmo:
|
| 364 |
+
preamble[-1] = lastline[:-len(eolmo.group(0))]
|
| 365 |
+
self._cur.preamble = EMPTYSTRING.join(preamble)
|
| 366 |
+
capturing_preamble = False
|
| 367 |
+
self._input.unreadline(line)
|
| 368 |
+
continue
|
| 369 |
+
# We saw a boundary separating two parts. Consume any
|
| 370 |
+
# multiple boundary lines that may be following. Our
|
| 371 |
+
# interpretation of RFC 2046 BNF grammar does not produce
|
| 372 |
+
# body parts within such double boundaries.
|
| 373 |
+
while True:
|
| 374 |
+
line = self._input.readline()
|
| 375 |
+
if line is NeedMoreData:
|
| 376 |
+
yield NeedMoreData
|
| 377 |
+
continue
|
| 378 |
+
mo = boundaryre.match(line)
|
| 379 |
+
if not mo:
|
| 380 |
+
self._input.unreadline(line)
|
| 381 |
+
break
|
| 382 |
+
# Recurse to parse this subpart; the input stream points
|
| 383 |
+
# at the subpart's first line.
|
| 384 |
+
self._input.push_eof_matcher(boundaryre.match)
|
| 385 |
+
for retval in self._parsegen():
|
| 386 |
+
if retval is NeedMoreData:
|
| 387 |
+
yield NeedMoreData
|
| 388 |
+
continue
|
| 389 |
+
break
|
| 390 |
+
# Because of RFC 2046, the newline preceding the boundary
|
| 391 |
+
# separator actually belongs to the boundary, not the
|
| 392 |
+
# previous subpart's payload (or epilogue if the previous
|
| 393 |
+
# part is a multipart).
|
| 394 |
+
if self._last.get_content_maintype() == 'multipart':
|
| 395 |
+
epilogue = self._last.epilogue
|
| 396 |
+
if epilogue == '':
|
| 397 |
+
self._last.epilogue = None
|
| 398 |
+
elif epilogue is not None:
|
| 399 |
+
mo = NLCRE_eol.search(epilogue)
|
| 400 |
+
if mo:
|
| 401 |
+
end = len(mo.group(0))
|
| 402 |
+
self._last.epilogue = epilogue[:-end]
|
| 403 |
+
else:
|
| 404 |
+
payload = self._last._payload
|
| 405 |
+
if isinstance(payload, str):
|
| 406 |
+
mo = NLCRE_eol.search(payload)
|
| 407 |
+
if mo:
|
| 408 |
+
payload = payload[:-len(mo.group(0))]
|
| 409 |
+
self._last._payload = payload
|
| 410 |
+
self._input.pop_eof_matcher()
|
| 411 |
+
self._pop_message()
|
| 412 |
+
# Set the multipart up for newline cleansing, which will
|
| 413 |
+
# happen if we're in a nested multipart.
|
| 414 |
+
self._last = self._cur
|
| 415 |
+
else:
|
| 416 |
+
# I think we must be in the preamble
|
| 417 |
+
assert capturing_preamble
|
| 418 |
+
preamble.append(line)
|
| 419 |
+
# We've seen either the EOF or the end boundary. If we're still
|
| 420 |
+
# capturing the preamble, we never saw the start boundary. Note
|
| 421 |
+
# that as a defect and store the captured text as the payload.
|
| 422 |
+
if capturing_preamble:
|
| 423 |
+
defect = errors.StartBoundaryNotFoundDefect()
|
| 424 |
+
self.policy.handle_defect(self._cur, defect)
|
| 425 |
+
self._cur.set_payload(EMPTYSTRING.join(preamble))
|
| 426 |
+
epilogue = []
|
| 427 |
+
for line in self._input:
|
| 428 |
+
if line is NeedMoreData:
|
| 429 |
+
yield NeedMoreData
|
| 430 |
+
continue
|
| 431 |
+
self._cur.epilogue = EMPTYSTRING.join(epilogue)
|
| 432 |
+
return
|
| 433 |
+
# If we're not processing the preamble, then we might have seen
|
| 434 |
+
# EOF without seeing that end boundary...that is also a defect.
|
| 435 |
+
if not close_boundary_seen:
|
| 436 |
+
defect = errors.CloseBoundaryNotFoundDefect()
|
| 437 |
+
self.policy.handle_defect(self._cur, defect)
|
| 438 |
+
return
|
| 439 |
+
# Everything from here to the EOF is epilogue. If the end boundary
|
| 440 |
+
# ended in a newline, we'll need to make sure the epilogue isn't
|
| 441 |
+
# None
|
| 442 |
+
if linesep:
|
| 443 |
+
epilogue = ['']
|
| 444 |
+
else:
|
| 445 |
+
epilogue = []
|
| 446 |
+
for line in self._input:
|
| 447 |
+
if line is NeedMoreData:
|
| 448 |
+
yield NeedMoreData
|
| 449 |
+
continue
|
| 450 |
+
epilogue.append(line)
|
| 451 |
+
# Any CRLF at the front of the epilogue is not technically part of
|
| 452 |
+
# the epilogue. Also, watch out for an empty string epilogue,
|
| 453 |
+
# which means a single newline.
|
| 454 |
+
if epilogue:
|
| 455 |
+
firstline = epilogue[0]
|
| 456 |
+
bolmo = NLCRE_bol.match(firstline)
|
| 457 |
+
if bolmo:
|
| 458 |
+
epilogue[0] = firstline[len(bolmo.group(0)):]
|
| 459 |
+
self._cur.epilogue = EMPTYSTRING.join(epilogue)
|
| 460 |
+
return
|
| 461 |
+
# Otherwise, it's some non-multipart type, so the entire rest of the
|
| 462 |
+
# file contents becomes the payload.
|
| 463 |
+
lines = []
|
| 464 |
+
for line in self._input:
|
| 465 |
+
if line is NeedMoreData:
|
| 466 |
+
yield NeedMoreData
|
| 467 |
+
continue
|
| 468 |
+
lines.append(line)
|
| 469 |
+
self._cur.set_payload(EMPTYSTRING.join(lines))
|
| 470 |
+
|
| 471 |
+
def _parse_headers(self, lines):
|
| 472 |
+
# Passed a list of lines that make up the headers for the current msg
|
| 473 |
+
lastheader = ''
|
| 474 |
+
lastvalue = []
|
| 475 |
+
for lineno, line in enumerate(lines):
|
| 476 |
+
# Check for continuation
|
| 477 |
+
if line[0] in ' \t':
|
| 478 |
+
if not lastheader:
|
| 479 |
+
# The first line of the headers was a continuation. This
|
| 480 |
+
# is illegal, so let's note the defect, store the illegal
|
| 481 |
+
# line, and ignore it for purposes of headers.
|
| 482 |
+
defect = errors.FirstHeaderLineIsContinuationDefect(line)
|
| 483 |
+
self.policy.handle_defect(self._cur, defect)
|
| 484 |
+
continue
|
| 485 |
+
lastvalue.append(line)
|
| 486 |
+
continue
|
| 487 |
+
if lastheader:
|
| 488 |
+
self._cur.set_raw(*self.policy.header_source_parse(lastvalue))
|
| 489 |
+
lastheader, lastvalue = '', []
|
| 490 |
+
# Check for envelope header, i.e. unix-from
|
| 491 |
+
if line.startswith('From '):
|
| 492 |
+
if lineno == 0:
|
| 493 |
+
# Strip off the trailing newline
|
| 494 |
+
mo = NLCRE_eol.search(line)
|
| 495 |
+
if mo:
|
| 496 |
+
line = line[:-len(mo.group(0))]
|
| 497 |
+
self._cur.set_unixfrom(line)
|
| 498 |
+
continue
|
| 499 |
+
elif lineno == len(lines) - 1:
|
| 500 |
+
# Something looking like a unix-from at the end - it's
|
| 501 |
+
# probably the first line of the body, so push back the
|
| 502 |
+
# line and stop.
|
| 503 |
+
self._input.unreadline(line)
|
| 504 |
+
return
|
| 505 |
+
else:
|
| 506 |
+
# Weirdly placed unix-from line. Note this as a defect
|
| 507 |
+
# and ignore it.
|
| 508 |
+
defect = errors.MisplacedEnvelopeHeaderDefect(line)
|
| 509 |
+
self._cur.defects.append(defect)
|
| 510 |
+
continue
|
| 511 |
+
# Split the line on the colon separating field name from value.
|
| 512 |
+
# There will always be a colon, because if there wasn't the part of
|
| 513 |
+
# the parser that calls us would have started parsing the body.
|
| 514 |
+
i = line.find(':')
|
| 515 |
+
|
| 516 |
+
# If the colon is on the start of the line the header is clearly
|
| 517 |
+
# malformed, but we might be able to salvage the rest of the
|
| 518 |
+
# message. Track the error but keep going.
|
| 519 |
+
if i == 0:
|
| 520 |
+
defect = errors.InvalidHeaderDefect("Missing header name.")
|
| 521 |
+
self._cur.defects.append(defect)
|
| 522 |
+
continue
|
| 523 |
+
|
| 524 |
+
assert i>0, "_parse_headers fed line with no : and no leading WS"
|
| 525 |
+
lastheader = line[:i]
|
| 526 |
+
lastvalue = [line]
|
| 527 |
+
# Done with all the lines, so handle the last header.
|
| 528 |
+
if lastheader:
|
| 529 |
+
self._cur.set_raw(*self.policy.header_source_parse(lastvalue))
|
| 530 |
+
|
| 531 |
+
|
| 532 |
+
class BytesFeedParser(FeedParser):
|
| 533 |
+
"""Like FeedParser, but feed accepts bytes."""
|
| 534 |
+
|
| 535 |
+
def feed(self, data):
|
| 536 |
+
super().feed(data.decode('ascii', 'surrogateescape'))
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/email/generator.py
ADDED
|
@@ -0,0 +1,512 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (C) 2001-2010 Python Software Foundation
|
| 2 |
+
# Author: Barry Warsaw
|
| 3 |
+
# Contact: email-sig@python.org
|
| 4 |
+
|
| 5 |
+
"""Classes to generate plain text from a message object tree."""
|
| 6 |
+
|
| 7 |
+
__all__ = ['Generator', 'DecodedGenerator', 'BytesGenerator']
|
| 8 |
+
|
| 9 |
+
import re
|
| 10 |
+
import sys
|
| 11 |
+
import time
|
| 12 |
+
import random
|
| 13 |
+
|
| 14 |
+
from copy import deepcopy
|
| 15 |
+
from io import StringIO, BytesIO
|
| 16 |
+
from email.utils import _has_surrogates
|
| 17 |
+
|
| 18 |
+
UNDERSCORE = '_'
|
| 19 |
+
NL = '\n' # XXX: no longer used by the code below.
|
| 20 |
+
|
| 21 |
+
NLCRE = re.compile(r'\r\n|\r|\n')
|
| 22 |
+
fcre = re.compile(r'^From ', re.MULTILINE)
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
class Generator:
|
| 27 |
+
"""Generates output from a Message object tree.
|
| 28 |
+
|
| 29 |
+
This basic generator writes the message to the given file object as plain
|
| 30 |
+
text.
|
| 31 |
+
"""
|
| 32 |
+
#
|
| 33 |
+
# Public interface
|
| 34 |
+
#
|
| 35 |
+
|
| 36 |
+
def __init__(self, outfp, mangle_from_=None, maxheaderlen=None, *,
|
| 37 |
+
policy=None):
|
| 38 |
+
"""Create the generator for message flattening.
|
| 39 |
+
|
| 40 |
+
outfp is the output file-like object for writing the message to. It
|
| 41 |
+
must have a write() method.
|
| 42 |
+
|
| 43 |
+
Optional mangle_from_ is a flag that, when True (the default if policy
|
| 44 |
+
is not set), escapes From_ lines in the body of the message by putting
|
| 45 |
+
a `>' in front of them.
|
| 46 |
+
|
| 47 |
+
Optional maxheaderlen specifies the longest length for a non-continued
|
| 48 |
+
header. When a header line is longer (in characters, with tabs
|
| 49 |
+
expanded to 8 spaces) than maxheaderlen, the header will split as
|
| 50 |
+
defined in the Header class. Set maxheaderlen to zero to disable
|
| 51 |
+
header wrapping. The default is 78, as recommended (but not required)
|
| 52 |
+
by RFC 2822.
|
| 53 |
+
|
| 54 |
+
The policy keyword specifies a policy object that controls a number of
|
| 55 |
+
aspects of the generator's operation. If no policy is specified,
|
| 56 |
+
the policy associated with the Message object passed to the
|
| 57 |
+
flatten method is used.
|
| 58 |
+
|
| 59 |
+
"""
|
| 60 |
+
|
| 61 |
+
if mangle_from_ is None:
|
| 62 |
+
mangle_from_ = True if policy is None else policy.mangle_from_
|
| 63 |
+
self._fp = outfp
|
| 64 |
+
self._mangle_from_ = mangle_from_
|
| 65 |
+
self.maxheaderlen = maxheaderlen
|
| 66 |
+
self.policy = policy
|
| 67 |
+
|
| 68 |
+
def write(self, s):
|
| 69 |
+
# Just delegate to the file object
|
| 70 |
+
self._fp.write(s)
|
| 71 |
+
|
| 72 |
+
def flatten(self, msg, unixfrom=False, linesep=None):
|
| 73 |
+
r"""Print the message object tree rooted at msg to the output file
|
| 74 |
+
specified when the Generator instance was created.
|
| 75 |
+
|
| 76 |
+
unixfrom is a flag that forces the printing of a Unix From_ delimiter
|
| 77 |
+
before the first object in the message tree. If the original message
|
| 78 |
+
has no From_ delimiter, a `standard' one is crafted. By default, this
|
| 79 |
+
is False to inhibit the printing of any From_ delimiter.
|
| 80 |
+
|
| 81 |
+
Note that for subobjects, no From_ line is printed.
|
| 82 |
+
|
| 83 |
+
linesep specifies the characters used to indicate a new line in
|
| 84 |
+
the output. The default value is determined by the policy specified
|
| 85 |
+
when the Generator instance was created or, if none was specified,
|
| 86 |
+
from the policy associated with the msg.
|
| 87 |
+
|
| 88 |
+
"""
|
| 89 |
+
# We use the _XXX constants for operating on data that comes directly
|
| 90 |
+
# from the msg, and _encoded_XXX constants for operating on data that
|
| 91 |
+
# has already been converted (to bytes in the BytesGenerator) and
|
| 92 |
+
# inserted into a temporary buffer.
|
| 93 |
+
policy = msg.policy if self.policy is None else self.policy
|
| 94 |
+
if linesep is not None:
|
| 95 |
+
policy = policy.clone(linesep=linesep)
|
| 96 |
+
if self.maxheaderlen is not None:
|
| 97 |
+
policy = policy.clone(max_line_length=self.maxheaderlen)
|
| 98 |
+
self._NL = policy.linesep
|
| 99 |
+
self._encoded_NL = self._encode(self._NL)
|
| 100 |
+
self._EMPTY = ''
|
| 101 |
+
self._encoded_EMPTY = self._encode(self._EMPTY)
|
| 102 |
+
# Because we use clone (below) when we recursively process message
|
| 103 |
+
# subparts, and because clone uses the computed policy (not None),
|
| 104 |
+
# submessages will automatically get set to the computed policy when
|
| 105 |
+
# they are processed by this code.
|
| 106 |
+
old_gen_policy = self.policy
|
| 107 |
+
old_msg_policy = msg.policy
|
| 108 |
+
try:
|
| 109 |
+
self.policy = policy
|
| 110 |
+
msg.policy = policy
|
| 111 |
+
if unixfrom:
|
| 112 |
+
ufrom = msg.get_unixfrom()
|
| 113 |
+
if not ufrom:
|
| 114 |
+
ufrom = 'From nobody ' + time.ctime(time.time())
|
| 115 |
+
self.write(ufrom + self._NL)
|
| 116 |
+
self._write(msg)
|
| 117 |
+
finally:
|
| 118 |
+
self.policy = old_gen_policy
|
| 119 |
+
msg.policy = old_msg_policy
|
| 120 |
+
|
| 121 |
+
def clone(self, fp):
|
| 122 |
+
"""Clone this generator with the exact same options."""
|
| 123 |
+
return self.__class__(fp,
|
| 124 |
+
self._mangle_from_,
|
| 125 |
+
None, # Use policy setting, which we've adjusted
|
| 126 |
+
policy=self.policy)
|
| 127 |
+
|
| 128 |
+
#
|
| 129 |
+
# Protected interface - undocumented ;/
|
| 130 |
+
#
|
| 131 |
+
|
| 132 |
+
# Note that we use 'self.write' when what we are writing is coming from
|
| 133 |
+
# the source, and self._fp.write when what we are writing is coming from a
|
| 134 |
+
# buffer (because the Bytes subclass has already had a chance to transform
|
| 135 |
+
# the data in its write method in that case). This is an entirely
|
| 136 |
+
# pragmatic split determined by experiment; we could be more general by
|
| 137 |
+
# always using write and having the Bytes subclass write method detect when
|
| 138 |
+
# it has already transformed the input; but, since this whole thing is a
|
| 139 |
+
# hack anyway this seems good enough.
|
| 140 |
+
|
| 141 |
+
def _new_buffer(self):
|
| 142 |
+
# BytesGenerator overrides this to return BytesIO.
|
| 143 |
+
return StringIO()
|
| 144 |
+
|
| 145 |
+
def _encode(self, s):
|
| 146 |
+
# BytesGenerator overrides this to encode strings to bytes.
|
| 147 |
+
return s
|
| 148 |
+
|
| 149 |
+
def _write_lines(self, lines):
|
| 150 |
+
# We have to transform the line endings.
|
| 151 |
+
if not lines:
|
| 152 |
+
return
|
| 153 |
+
lines = NLCRE.split(lines)
|
| 154 |
+
for line in lines[:-1]:
|
| 155 |
+
self.write(line)
|
| 156 |
+
self.write(self._NL)
|
| 157 |
+
if lines[-1]:
|
| 158 |
+
self.write(lines[-1])
|
| 159 |
+
# XXX logic tells me this else should be needed, but the tests fail
|
| 160 |
+
# with it and pass without it. (NLCRE.split ends with a blank element
|
| 161 |
+
# if and only if there was a trailing newline.)
|
| 162 |
+
#else:
|
| 163 |
+
# self.write(self._NL)
|
| 164 |
+
|
| 165 |
+
def _write(self, msg):
|
| 166 |
+
# We can't write the headers yet because of the following scenario:
|
| 167 |
+
# say a multipart message includes the boundary string somewhere in
|
| 168 |
+
# its body. We'd have to calculate the new boundary /before/ we write
|
| 169 |
+
# the headers so that we can write the correct Content-Type:
|
| 170 |
+
# parameter.
|
| 171 |
+
#
|
| 172 |
+
# The way we do this, so as to make the _handle_*() methods simpler,
|
| 173 |
+
# is to cache any subpart writes into a buffer. The we write the
|
| 174 |
+
# headers and the buffer contents. That way, subpart handlers can
|
| 175 |
+
# Do The Right Thing, and can still modify the Content-Type: header if
|
| 176 |
+
# necessary.
|
| 177 |
+
oldfp = self._fp
|
| 178 |
+
try:
|
| 179 |
+
self._munge_cte = None
|
| 180 |
+
self._fp = sfp = self._new_buffer()
|
| 181 |
+
self._dispatch(msg)
|
| 182 |
+
finally:
|
| 183 |
+
self._fp = oldfp
|
| 184 |
+
munge_cte = self._munge_cte
|
| 185 |
+
del self._munge_cte
|
| 186 |
+
# If we munged the cte, copy the message again and re-fix the CTE.
|
| 187 |
+
if munge_cte:
|
| 188 |
+
msg = deepcopy(msg)
|
| 189 |
+
# Preserve the header order if the CTE header already exists.
|
| 190 |
+
if msg.get('content-transfer-encoding') is None:
|
| 191 |
+
msg['Content-Transfer-Encoding'] = munge_cte[0]
|
| 192 |
+
else:
|
| 193 |
+
msg.replace_header('content-transfer-encoding', munge_cte[0])
|
| 194 |
+
msg.replace_header('content-type', munge_cte[1])
|
| 195 |
+
# Write the headers. First we see if the message object wants to
|
| 196 |
+
# handle that itself. If not, we'll do it generically.
|
| 197 |
+
meth = getattr(msg, '_write_headers', None)
|
| 198 |
+
if meth is None:
|
| 199 |
+
self._write_headers(msg)
|
| 200 |
+
else:
|
| 201 |
+
meth(self)
|
| 202 |
+
self._fp.write(sfp.getvalue())
|
| 203 |
+
|
| 204 |
+
def _dispatch(self, msg):
|
| 205 |
+
# Get the Content-Type: for the message, then try to dispatch to
|
| 206 |
+
# self._handle_<maintype>_<subtype>(). If there's no handler for the
|
| 207 |
+
# full MIME type, then dispatch to self._handle_<maintype>(). If
|
| 208 |
+
# that's missing too, then dispatch to self._writeBody().
|
| 209 |
+
main = msg.get_content_maintype()
|
| 210 |
+
sub = msg.get_content_subtype()
|
| 211 |
+
specific = UNDERSCORE.join((main, sub)).replace('-', '_')
|
| 212 |
+
meth = getattr(self, '_handle_' + specific, None)
|
| 213 |
+
if meth is None:
|
| 214 |
+
generic = main.replace('-', '_')
|
| 215 |
+
meth = getattr(self, '_handle_' + generic, None)
|
| 216 |
+
if meth is None:
|
| 217 |
+
meth = self._writeBody
|
| 218 |
+
meth(msg)
|
| 219 |
+
|
| 220 |
+
#
|
| 221 |
+
# Default handlers
|
| 222 |
+
#
|
| 223 |
+
|
| 224 |
+
def _write_headers(self, msg):
|
| 225 |
+
for h, v in msg.raw_items():
|
| 226 |
+
self.write(self.policy.fold(h, v))
|
| 227 |
+
# A blank line always separates headers from body
|
| 228 |
+
self.write(self._NL)
|
| 229 |
+
|
| 230 |
+
#
|
| 231 |
+
# Handlers for writing types and subtypes
|
| 232 |
+
#
|
| 233 |
+
|
| 234 |
+
def _handle_text(self, msg):
|
| 235 |
+
payload = msg.get_payload()
|
| 236 |
+
if payload is None:
|
| 237 |
+
return
|
| 238 |
+
if not isinstance(payload, str):
|
| 239 |
+
raise TypeError('string payload expected: %s' % type(payload))
|
| 240 |
+
if _has_surrogates(msg._payload):
|
| 241 |
+
charset = msg.get_param('charset')
|
| 242 |
+
if charset is not None:
|
| 243 |
+
# XXX: This copy stuff is an ugly hack to avoid modifying the
|
| 244 |
+
# existing message.
|
| 245 |
+
msg = deepcopy(msg)
|
| 246 |
+
del msg['content-transfer-encoding']
|
| 247 |
+
msg.set_payload(payload, charset)
|
| 248 |
+
payload = msg.get_payload()
|
| 249 |
+
self._munge_cte = (msg['content-transfer-encoding'],
|
| 250 |
+
msg['content-type'])
|
| 251 |
+
if self._mangle_from_:
|
| 252 |
+
payload = fcre.sub('>From ', payload)
|
| 253 |
+
self._write_lines(payload)
|
| 254 |
+
|
| 255 |
+
# Default body handler
|
| 256 |
+
_writeBody = _handle_text
|
| 257 |
+
|
| 258 |
+
def _handle_multipart(self, msg):
|
| 259 |
+
# The trick here is to write out each part separately, merge them all
|
| 260 |
+
# together, and then make sure that the boundary we've chosen isn't
|
| 261 |
+
# present in the payload.
|
| 262 |
+
msgtexts = []
|
| 263 |
+
subparts = msg.get_payload()
|
| 264 |
+
if subparts is None:
|
| 265 |
+
subparts = []
|
| 266 |
+
elif isinstance(subparts, str):
|
| 267 |
+
# e.g. a non-strict parse of a message with no starting boundary.
|
| 268 |
+
self.write(subparts)
|
| 269 |
+
return
|
| 270 |
+
elif not isinstance(subparts, list):
|
| 271 |
+
# Scalar payload
|
| 272 |
+
subparts = [subparts]
|
| 273 |
+
for part in subparts:
|
| 274 |
+
s = self._new_buffer()
|
| 275 |
+
g = self.clone(s)
|
| 276 |
+
g.flatten(part, unixfrom=False, linesep=self._NL)
|
| 277 |
+
msgtexts.append(s.getvalue())
|
| 278 |
+
# BAW: What about boundaries that are wrapped in double-quotes?
|
| 279 |
+
boundary = msg.get_boundary()
|
| 280 |
+
if not boundary:
|
| 281 |
+
# Create a boundary that doesn't appear in any of the
|
| 282 |
+
# message texts.
|
| 283 |
+
alltext = self._encoded_NL.join(msgtexts)
|
| 284 |
+
boundary = self._make_boundary(alltext)
|
| 285 |
+
msg.set_boundary(boundary)
|
| 286 |
+
# If there's a preamble, write it out, with a trailing CRLF
|
| 287 |
+
if msg.preamble is not None:
|
| 288 |
+
if self._mangle_from_:
|
| 289 |
+
preamble = fcre.sub('>From ', msg.preamble)
|
| 290 |
+
else:
|
| 291 |
+
preamble = msg.preamble
|
| 292 |
+
self._write_lines(preamble)
|
| 293 |
+
self.write(self._NL)
|
| 294 |
+
# dash-boundary transport-padding CRLF
|
| 295 |
+
self.write('--' + boundary + self._NL)
|
| 296 |
+
# body-part
|
| 297 |
+
if msgtexts:
|
| 298 |
+
self._fp.write(msgtexts.pop(0))
|
| 299 |
+
# *encapsulation
|
| 300 |
+
# --> delimiter transport-padding
|
| 301 |
+
# --> CRLF body-part
|
| 302 |
+
for body_part in msgtexts:
|
| 303 |
+
# delimiter transport-padding CRLF
|
| 304 |
+
self.write(self._NL + '--' + boundary + self._NL)
|
| 305 |
+
# body-part
|
| 306 |
+
self._fp.write(body_part)
|
| 307 |
+
# close-delimiter transport-padding
|
| 308 |
+
self.write(self._NL + '--' + boundary + '--' + self._NL)
|
| 309 |
+
if msg.epilogue is not None:
|
| 310 |
+
if self._mangle_from_:
|
| 311 |
+
epilogue = fcre.sub('>From ', msg.epilogue)
|
| 312 |
+
else:
|
| 313 |
+
epilogue = msg.epilogue
|
| 314 |
+
self._write_lines(epilogue)
|
| 315 |
+
|
| 316 |
+
def _handle_multipart_signed(self, msg):
|
| 317 |
+
# The contents of signed parts has to stay unmodified in order to keep
|
| 318 |
+
# the signature intact per RFC1847 2.1, so we disable header wrapping.
|
| 319 |
+
# RDM: This isn't enough to completely preserve the part, but it helps.
|
| 320 |
+
p = self.policy
|
| 321 |
+
self.policy = p.clone(max_line_length=0)
|
| 322 |
+
try:
|
| 323 |
+
self._handle_multipart(msg)
|
| 324 |
+
finally:
|
| 325 |
+
self.policy = p
|
| 326 |
+
|
| 327 |
+
def _handle_message_delivery_status(self, msg):
|
| 328 |
+
# We can't just write the headers directly to self's file object
|
| 329 |
+
# because this will leave an extra newline between the last header
|
| 330 |
+
# block and the boundary. Sigh.
|
| 331 |
+
blocks = []
|
| 332 |
+
for part in msg.get_payload():
|
| 333 |
+
s = self._new_buffer()
|
| 334 |
+
g = self.clone(s)
|
| 335 |
+
g.flatten(part, unixfrom=False, linesep=self._NL)
|
| 336 |
+
text = s.getvalue()
|
| 337 |
+
lines = text.split(self._encoded_NL)
|
| 338 |
+
# Strip off the unnecessary trailing empty line
|
| 339 |
+
if lines and lines[-1] == self._encoded_EMPTY:
|
| 340 |
+
blocks.append(self._encoded_NL.join(lines[:-1]))
|
| 341 |
+
else:
|
| 342 |
+
blocks.append(text)
|
| 343 |
+
# Now join all the blocks with an empty line. This has the lovely
|
| 344 |
+
# effect of separating each block with an empty line, but not adding
|
| 345 |
+
# an extra one after the last one.
|
| 346 |
+
self._fp.write(self._encoded_NL.join(blocks))
|
| 347 |
+
|
| 348 |
+
def _handle_message(self, msg):
|
| 349 |
+
s = self._new_buffer()
|
| 350 |
+
g = self.clone(s)
|
| 351 |
+
# The payload of a message/rfc822 part should be a multipart sequence
|
| 352 |
+
# of length 1. The zeroth element of the list should be the Message
|
| 353 |
+
# object for the subpart. Extract that object, stringify it, and
|
| 354 |
+
# write it out.
|
| 355 |
+
# Except, it turns out, when it's a string instead, which happens when
|
| 356 |
+
# and only when HeaderParser is used on a message of mime type
|
| 357 |
+
# message/rfc822. Such messages are generated by, for example,
|
| 358 |
+
# Groupwise when forwarding unadorned messages. (Issue 7970.) So
|
| 359 |
+
# in that case we just emit the string body.
|
| 360 |
+
payload = msg._payload
|
| 361 |
+
if isinstance(payload, list):
|
| 362 |
+
g.flatten(msg.get_payload(0), unixfrom=False, linesep=self._NL)
|
| 363 |
+
payload = s.getvalue()
|
| 364 |
+
else:
|
| 365 |
+
payload = self._encode(payload)
|
| 366 |
+
self._fp.write(payload)
|
| 367 |
+
|
| 368 |
+
# This used to be a module level function; we use a classmethod for this
|
| 369 |
+
# and _compile_re so we can continue to provide the module level function
|
| 370 |
+
# for backward compatibility by doing
|
| 371 |
+
# _make_boundary = Generator._make_boundary
|
| 372 |
+
# at the end of the module. It *is* internal, so we could drop that...
|
| 373 |
+
@classmethod
|
| 374 |
+
def _make_boundary(cls, text=None):
|
| 375 |
+
# Craft a random boundary. If text is given, ensure that the chosen
|
| 376 |
+
# boundary doesn't appear in the text.
|
| 377 |
+
token = random.randrange(sys.maxsize)
|
| 378 |
+
boundary = ('=' * 15) + (_fmt % token) + '=='
|
| 379 |
+
if text is None:
|
| 380 |
+
return boundary
|
| 381 |
+
b = boundary
|
| 382 |
+
counter = 0
|
| 383 |
+
while True:
|
| 384 |
+
cre = cls._compile_re('^--' + re.escape(b) + '(--)?$', re.MULTILINE)
|
| 385 |
+
if not cre.search(text):
|
| 386 |
+
break
|
| 387 |
+
b = boundary + '.' + str(counter)
|
| 388 |
+
counter += 1
|
| 389 |
+
return b
|
| 390 |
+
|
| 391 |
+
@classmethod
|
| 392 |
+
def _compile_re(cls, s, flags):
|
| 393 |
+
return re.compile(s, flags)
|
| 394 |
+
|
| 395 |
+
|
| 396 |
+
class BytesGenerator(Generator):
|
| 397 |
+
"""Generates a bytes version of a Message object tree.
|
| 398 |
+
|
| 399 |
+
Functionally identical to the base Generator except that the output is
|
| 400 |
+
bytes and not string. When surrogates were used in the input to encode
|
| 401 |
+
bytes, these are decoded back to bytes for output. If the policy has
|
| 402 |
+
cte_type set to 7bit, then the message is transformed such that the
|
| 403 |
+
non-ASCII bytes are properly content transfer encoded, using the charset
|
| 404 |
+
unknown-8bit.
|
| 405 |
+
|
| 406 |
+
The outfp object must accept bytes in its write method.
|
| 407 |
+
"""
|
| 408 |
+
|
| 409 |
+
def write(self, s):
|
| 410 |
+
self._fp.write(s.encode('ascii', 'surrogateescape'))
|
| 411 |
+
|
| 412 |
+
def _new_buffer(self):
|
| 413 |
+
return BytesIO()
|
| 414 |
+
|
| 415 |
+
def _encode(self, s):
|
| 416 |
+
return s.encode('ascii')
|
| 417 |
+
|
| 418 |
+
def _write_headers(self, msg):
|
| 419 |
+
# This is almost the same as the string version, except for handling
|
| 420 |
+
# strings with 8bit bytes.
|
| 421 |
+
for h, v in msg.raw_items():
|
| 422 |
+
self._fp.write(self.policy.fold_binary(h, v))
|
| 423 |
+
# A blank line always separates headers from body
|
| 424 |
+
self.write(self._NL)
|
| 425 |
+
|
| 426 |
+
def _handle_text(self, msg):
|
| 427 |
+
# If the string has surrogates the original source was bytes, so
|
| 428 |
+
# just write it back out.
|
| 429 |
+
if msg._payload is None:
|
| 430 |
+
return
|
| 431 |
+
if _has_surrogates(msg._payload) and not self.policy.cte_type=='7bit':
|
| 432 |
+
if self._mangle_from_:
|
| 433 |
+
msg._payload = fcre.sub(">From ", msg._payload)
|
| 434 |
+
self._write_lines(msg._payload)
|
| 435 |
+
else:
|
| 436 |
+
super(BytesGenerator,self)._handle_text(msg)
|
| 437 |
+
|
| 438 |
+
# Default body handler
|
| 439 |
+
_writeBody = _handle_text
|
| 440 |
+
|
| 441 |
+
@classmethod
|
| 442 |
+
def _compile_re(cls, s, flags):
|
| 443 |
+
return re.compile(s.encode('ascii'), flags)
|
| 444 |
+
|
| 445 |
+
|
| 446 |
+
|
| 447 |
+
_FMT = '[Non-text (%(type)s) part of message omitted, filename %(filename)s]'
|
| 448 |
+
|
| 449 |
+
class DecodedGenerator(Generator):
|
| 450 |
+
"""Generates a text representation of a message.
|
| 451 |
+
|
| 452 |
+
Like the Generator base class, except that non-text parts are substituted
|
| 453 |
+
with a format string representing the part.
|
| 454 |
+
"""
|
| 455 |
+
def __init__(self, outfp, mangle_from_=None, maxheaderlen=None, fmt=None, *,
|
| 456 |
+
policy=None):
|
| 457 |
+
"""Like Generator.__init__() except that an additional optional
|
| 458 |
+
argument is allowed.
|
| 459 |
+
|
| 460 |
+
Walks through all subparts of a message. If the subpart is of main
|
| 461 |
+
type `text', then it prints the decoded payload of the subpart.
|
| 462 |
+
|
| 463 |
+
Otherwise, fmt is a format string that is used instead of the message
|
| 464 |
+
payload. fmt is expanded with the following keywords (in
|
| 465 |
+
%(keyword)s format):
|
| 466 |
+
|
| 467 |
+
type : Full MIME type of the non-text part
|
| 468 |
+
maintype : Main MIME type of the non-text part
|
| 469 |
+
subtype : Sub-MIME type of the non-text part
|
| 470 |
+
filename : Filename of the non-text part
|
| 471 |
+
description: Description associated with the non-text part
|
| 472 |
+
encoding : Content transfer encoding of the non-text part
|
| 473 |
+
|
| 474 |
+
The default value for fmt is None, meaning
|
| 475 |
+
|
| 476 |
+
[Non-text (%(type)s) part of message omitted, filename %(filename)s]
|
| 477 |
+
"""
|
| 478 |
+
Generator.__init__(self, outfp, mangle_from_, maxheaderlen,
|
| 479 |
+
policy=policy)
|
| 480 |
+
if fmt is None:
|
| 481 |
+
self._fmt = _FMT
|
| 482 |
+
else:
|
| 483 |
+
self._fmt = fmt
|
| 484 |
+
|
| 485 |
+
def _dispatch(self, msg):
|
| 486 |
+
for part in msg.walk():
|
| 487 |
+
maintype = part.get_content_maintype()
|
| 488 |
+
if maintype == 'text':
|
| 489 |
+
print(part.get_payload(decode=False), file=self)
|
| 490 |
+
elif maintype == 'multipart':
|
| 491 |
+
# Just skip this
|
| 492 |
+
pass
|
| 493 |
+
else:
|
| 494 |
+
print(self._fmt % {
|
| 495 |
+
'type' : part.get_content_type(),
|
| 496 |
+
'maintype' : part.get_content_maintype(),
|
| 497 |
+
'subtype' : part.get_content_subtype(),
|
| 498 |
+
'filename' : part.get_filename('[no filename]'),
|
| 499 |
+
'description': part.get('Content-Description',
|
| 500 |
+
'[no description]'),
|
| 501 |
+
'encoding' : part.get('Content-Transfer-Encoding',
|
| 502 |
+
'[no encoding]'),
|
| 503 |
+
}, file=self)
|
| 504 |
+
|
| 505 |
+
|
| 506 |
+
|
| 507 |
+
# Helper used by Generator._make_boundary
|
| 508 |
+
_width = len(repr(sys.maxsize-1))
|
| 509 |
+
_fmt = '%%0%dd' % _width
|
| 510 |
+
|
| 511 |
+
# Backward compatibility
|
| 512 |
+
_make_boundary = Generator._make_boundary
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/email/header.py
ADDED
|
@@ -0,0 +1,578 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (C) 2002-2007 Python Software Foundation
|
| 2 |
+
# Author: Ben Gertzfield, Barry Warsaw
|
| 3 |
+
# Contact: email-sig@python.org
|
| 4 |
+
|
| 5 |
+
"""Header encoding and decoding functionality."""
|
| 6 |
+
|
| 7 |
+
__all__ = [
|
| 8 |
+
'Header',
|
| 9 |
+
'decode_header',
|
| 10 |
+
'make_header',
|
| 11 |
+
]
|
| 12 |
+
|
| 13 |
+
import re
|
| 14 |
+
import binascii
|
| 15 |
+
|
| 16 |
+
import email.quoprimime
|
| 17 |
+
import email.base64mime
|
| 18 |
+
|
| 19 |
+
from email.errors import HeaderParseError
|
| 20 |
+
from email import charset as _charset
|
| 21 |
+
Charset = _charset.Charset
|
| 22 |
+
|
| 23 |
+
NL = '\n'
|
| 24 |
+
SPACE = ' '
|
| 25 |
+
BSPACE = b' '
|
| 26 |
+
SPACE8 = ' ' * 8
|
| 27 |
+
EMPTYSTRING = ''
|
| 28 |
+
MAXLINELEN = 78
|
| 29 |
+
FWS = ' \t'
|
| 30 |
+
|
| 31 |
+
USASCII = Charset('us-ascii')
|
| 32 |
+
UTF8 = Charset('utf-8')
|
| 33 |
+
|
| 34 |
+
# Match encoded-word strings in the form =?charset?q?Hello_World?=
|
| 35 |
+
ecre = re.compile(r'''
|
| 36 |
+
=\? # literal =?
|
| 37 |
+
(?P<charset>[^?]*?) # non-greedy up to the next ? is the charset
|
| 38 |
+
\? # literal ?
|
| 39 |
+
(?P<encoding>[qQbB]) # either a "q" or a "b", case insensitive
|
| 40 |
+
\? # literal ?
|
| 41 |
+
(?P<encoded>.*?) # non-greedy up to the next ?= is the encoded string
|
| 42 |
+
\?= # literal ?=
|
| 43 |
+
''', re.VERBOSE | re.MULTILINE)
|
| 44 |
+
|
| 45 |
+
# Field name regexp, including trailing colon, but not separating whitespace,
|
| 46 |
+
# according to RFC 2822. Character range is from tilde to exclamation mark.
|
| 47 |
+
# For use with .match()
|
| 48 |
+
fcre = re.compile(r'[\041-\176]+:$')
|
| 49 |
+
|
| 50 |
+
# Find a header embedded in a putative header value. Used to check for
|
| 51 |
+
# header injection attack.
|
| 52 |
+
_embedded_header = re.compile(r'\n[^ \t]+:')
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
# Helpers
|
| 57 |
+
_max_append = email.quoprimime._max_append
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def decode_header(header):
|
| 62 |
+
"""Decode a message header value without converting charset.
|
| 63 |
+
|
| 64 |
+
Returns a list of (string, charset) pairs containing each of the decoded
|
| 65 |
+
parts of the header. Charset is None for non-encoded parts of the header,
|
| 66 |
+
otherwise a lower-case string containing the name of the character set
|
| 67 |
+
specified in the encoded string.
|
| 68 |
+
|
| 69 |
+
header may be a string that may or may not contain RFC2047 encoded words,
|
| 70 |
+
or it may be a Header object.
|
| 71 |
+
|
| 72 |
+
An email.errors.HeaderParseError may be raised when certain decoding error
|
| 73 |
+
occurs (e.g. a base64 decoding exception).
|
| 74 |
+
"""
|
| 75 |
+
# If it is a Header object, we can just return the encoded chunks.
|
| 76 |
+
if hasattr(header, '_chunks'):
|
| 77 |
+
return [(_charset._encode(string, str(charset)), str(charset))
|
| 78 |
+
for string, charset in header._chunks]
|
| 79 |
+
# If no encoding, just return the header with no charset.
|
| 80 |
+
if not ecre.search(header):
|
| 81 |
+
return [(header, None)]
|
| 82 |
+
# First step is to parse all the encoded parts into triplets of the form
|
| 83 |
+
# (encoded_string, encoding, charset). For unencoded strings, the last
|
| 84 |
+
# two parts will be None.
|
| 85 |
+
words = []
|
| 86 |
+
for line in header.splitlines():
|
| 87 |
+
parts = ecre.split(line)
|
| 88 |
+
first = True
|
| 89 |
+
while parts:
|
| 90 |
+
unencoded = parts.pop(0)
|
| 91 |
+
if first:
|
| 92 |
+
unencoded = unencoded.lstrip()
|
| 93 |
+
first = False
|
| 94 |
+
if unencoded:
|
| 95 |
+
words.append((unencoded, None, None))
|
| 96 |
+
if parts:
|
| 97 |
+
charset = parts.pop(0).lower()
|
| 98 |
+
encoding = parts.pop(0).lower()
|
| 99 |
+
encoded = parts.pop(0)
|
| 100 |
+
words.append((encoded, encoding, charset))
|
| 101 |
+
# Now loop over words and remove words that consist of whitespace
|
| 102 |
+
# between two encoded strings.
|
| 103 |
+
droplist = []
|
| 104 |
+
for n, w in enumerate(words):
|
| 105 |
+
if n>1 and w[1] and words[n-2][1] and words[n-1][0].isspace():
|
| 106 |
+
droplist.append(n-1)
|
| 107 |
+
for d in reversed(droplist):
|
| 108 |
+
del words[d]
|
| 109 |
+
|
| 110 |
+
# The next step is to decode each encoded word by applying the reverse
|
| 111 |
+
# base64 or quopri transformation. decoded_words is now a list of the
|
| 112 |
+
# form (decoded_word, charset).
|
| 113 |
+
decoded_words = []
|
| 114 |
+
for encoded_string, encoding, charset in words:
|
| 115 |
+
if encoding is None:
|
| 116 |
+
# This is an unencoded word.
|
| 117 |
+
decoded_words.append((encoded_string, charset))
|
| 118 |
+
elif encoding == 'q':
|
| 119 |
+
word = email.quoprimime.header_decode(encoded_string)
|
| 120 |
+
decoded_words.append((word, charset))
|
| 121 |
+
elif encoding == 'b':
|
| 122 |
+
paderr = len(encoded_string) % 4 # Postel's law: add missing padding
|
| 123 |
+
if paderr:
|
| 124 |
+
encoded_string += '==='[:4 - paderr]
|
| 125 |
+
try:
|
| 126 |
+
word = email.base64mime.decode(encoded_string)
|
| 127 |
+
except binascii.Error:
|
| 128 |
+
raise HeaderParseError('Base64 decoding error')
|
| 129 |
+
else:
|
| 130 |
+
decoded_words.append((word, charset))
|
| 131 |
+
else:
|
| 132 |
+
raise AssertionError('Unexpected encoding: ' + encoding)
|
| 133 |
+
# Now convert all words to bytes and collapse consecutive runs of
|
| 134 |
+
# similarly encoded words.
|
| 135 |
+
collapsed = []
|
| 136 |
+
last_word = last_charset = None
|
| 137 |
+
for word, charset in decoded_words:
|
| 138 |
+
if isinstance(word, str):
|
| 139 |
+
word = bytes(word, 'raw-unicode-escape')
|
| 140 |
+
if last_word is None:
|
| 141 |
+
last_word = word
|
| 142 |
+
last_charset = charset
|
| 143 |
+
elif charset != last_charset:
|
| 144 |
+
collapsed.append((last_word, last_charset))
|
| 145 |
+
last_word = word
|
| 146 |
+
last_charset = charset
|
| 147 |
+
elif last_charset is None:
|
| 148 |
+
last_word += BSPACE + word
|
| 149 |
+
else:
|
| 150 |
+
last_word += word
|
| 151 |
+
collapsed.append((last_word, last_charset))
|
| 152 |
+
return collapsed
|
| 153 |
+
|
| 154 |
+
|
| 155 |
+
|
| 156 |
+
def make_header(decoded_seq, maxlinelen=None, header_name=None,
|
| 157 |
+
continuation_ws=' '):
|
| 158 |
+
"""Create a Header from a sequence of pairs as returned by decode_header()
|
| 159 |
+
|
| 160 |
+
decode_header() takes a header value string and returns a sequence of
|
| 161 |
+
pairs of the format (decoded_string, charset) where charset is the string
|
| 162 |
+
name of the character set.
|
| 163 |
+
|
| 164 |
+
This function takes one of those sequence of pairs and returns a Header
|
| 165 |
+
instance. Optional maxlinelen, header_name, and continuation_ws are as in
|
| 166 |
+
the Header constructor.
|
| 167 |
+
"""
|
| 168 |
+
h = Header(maxlinelen=maxlinelen, header_name=header_name,
|
| 169 |
+
continuation_ws=continuation_ws)
|
| 170 |
+
for s, charset in decoded_seq:
|
| 171 |
+
# None means us-ascii but we can simply pass it on to h.append()
|
| 172 |
+
if charset is not None and not isinstance(charset, Charset):
|
| 173 |
+
charset = Charset(charset)
|
| 174 |
+
h.append(s, charset)
|
| 175 |
+
return h
|
| 176 |
+
|
| 177 |
+
|
| 178 |
+
|
| 179 |
+
class Header:
|
| 180 |
+
def __init__(self, s=None, charset=None,
|
| 181 |
+
maxlinelen=None, header_name=None,
|
| 182 |
+
continuation_ws=' ', errors='strict'):
|
| 183 |
+
"""Create a MIME-compliant header that can contain many character sets.
|
| 184 |
+
|
| 185 |
+
Optional s is the initial header value. If None, the initial header
|
| 186 |
+
value is not set. You can later append to the header with .append()
|
| 187 |
+
method calls. s may be a byte string or a Unicode string, but see the
|
| 188 |
+
.append() documentation for semantics.
|
| 189 |
+
|
| 190 |
+
Optional charset serves two purposes: it has the same meaning as the
|
| 191 |
+
charset argument to the .append() method. It also sets the default
|
| 192 |
+
character set for all subsequent .append() calls that omit the charset
|
| 193 |
+
argument. If charset is not provided in the constructor, the us-ascii
|
| 194 |
+
charset is used both as s's initial charset and as the default for
|
| 195 |
+
subsequent .append() calls.
|
| 196 |
+
|
| 197 |
+
The maximum line length can be specified explicitly via maxlinelen. For
|
| 198 |
+
splitting the first line to a shorter value (to account for the field
|
| 199 |
+
header which isn't included in s, e.g. `Subject') pass in the name of
|
| 200 |
+
the field in header_name. The default maxlinelen is 78 as recommended
|
| 201 |
+
by RFC 2822.
|
| 202 |
+
|
| 203 |
+
continuation_ws must be RFC 2822 compliant folding whitespace (usually
|
| 204 |
+
either a space or a hard tab) which will be prepended to continuation
|
| 205 |
+
lines.
|
| 206 |
+
|
| 207 |
+
errors is passed through to the .append() call.
|
| 208 |
+
"""
|
| 209 |
+
if charset is None:
|
| 210 |
+
charset = USASCII
|
| 211 |
+
elif not isinstance(charset, Charset):
|
| 212 |
+
charset = Charset(charset)
|
| 213 |
+
self._charset = charset
|
| 214 |
+
self._continuation_ws = continuation_ws
|
| 215 |
+
self._chunks = []
|
| 216 |
+
if s is not None:
|
| 217 |
+
self.append(s, charset, errors)
|
| 218 |
+
if maxlinelen is None:
|
| 219 |
+
maxlinelen = MAXLINELEN
|
| 220 |
+
self._maxlinelen = maxlinelen
|
| 221 |
+
if header_name is None:
|
| 222 |
+
self._headerlen = 0
|
| 223 |
+
else:
|
| 224 |
+
# Take the separating colon and space into account.
|
| 225 |
+
self._headerlen = len(header_name) + 2
|
| 226 |
+
|
| 227 |
+
def __str__(self):
|
| 228 |
+
"""Return the string value of the header."""
|
| 229 |
+
self._normalize()
|
| 230 |
+
uchunks = []
|
| 231 |
+
lastcs = None
|
| 232 |
+
lastspace = None
|
| 233 |
+
for string, charset in self._chunks:
|
| 234 |
+
# We must preserve spaces between encoded and non-encoded word
|
| 235 |
+
# boundaries, which means for us we need to add a space when we go
|
| 236 |
+
# from a charset to None/us-ascii, or from None/us-ascii to a
|
| 237 |
+
# charset. Only do this for the second and subsequent chunks.
|
| 238 |
+
# Don't add a space if the None/us-ascii string already has
|
| 239 |
+
# a space (trailing or leading depending on transition)
|
| 240 |
+
nextcs = charset
|
| 241 |
+
if nextcs == _charset.UNKNOWN8BIT:
|
| 242 |
+
original_bytes = string.encode('ascii', 'surrogateescape')
|
| 243 |
+
string = original_bytes.decode('ascii', 'replace')
|
| 244 |
+
if uchunks:
|
| 245 |
+
hasspace = string and self._nonctext(string[0])
|
| 246 |
+
if lastcs not in (None, 'us-ascii'):
|
| 247 |
+
if nextcs in (None, 'us-ascii') and not hasspace:
|
| 248 |
+
uchunks.append(SPACE)
|
| 249 |
+
nextcs = None
|
| 250 |
+
elif nextcs not in (None, 'us-ascii') and not lastspace:
|
| 251 |
+
uchunks.append(SPACE)
|
| 252 |
+
lastspace = string and self._nonctext(string[-1])
|
| 253 |
+
lastcs = nextcs
|
| 254 |
+
uchunks.append(string)
|
| 255 |
+
return EMPTYSTRING.join(uchunks)
|
| 256 |
+
|
| 257 |
+
# Rich comparison operators for equality only. BAW: does it make sense to
|
| 258 |
+
# have or explicitly disable <, <=, >, >= operators?
|
| 259 |
+
def __eq__(self, other):
|
| 260 |
+
# other may be a Header or a string. Both are fine so coerce
|
| 261 |
+
# ourselves to a unicode (of the unencoded header value), swap the
|
| 262 |
+
# args and do another comparison.
|
| 263 |
+
return other == str(self)
|
| 264 |
+
|
| 265 |
+
def append(self, s, charset=None, errors='strict'):
|
| 266 |
+
"""Append a string to the MIME header.
|
| 267 |
+
|
| 268 |
+
Optional charset, if given, should be a Charset instance or the name
|
| 269 |
+
of a character set (which will be converted to a Charset instance). A
|
| 270 |
+
value of None (the default) means that the charset given in the
|
| 271 |
+
constructor is used.
|
| 272 |
+
|
| 273 |
+
s may be a byte string or a Unicode string. If it is a byte string
|
| 274 |
+
(i.e. isinstance(s, str) is false), then charset is the encoding of
|
| 275 |
+
that byte string, and a UnicodeError will be raised if the string
|
| 276 |
+
cannot be decoded with that charset. If s is a Unicode string, then
|
| 277 |
+
charset is a hint specifying the character set of the characters in
|
| 278 |
+
the string. In either case, when producing an RFC 2822 compliant
|
| 279 |
+
header using RFC 2047 rules, the string will be encoded using the
|
| 280 |
+
output codec of the charset. If the string cannot be encoded to the
|
| 281 |
+
output codec, a UnicodeError will be raised.
|
| 282 |
+
|
| 283 |
+
Optional `errors' is passed as the errors argument to the decode
|
| 284 |
+
call if s is a byte string.
|
| 285 |
+
"""
|
| 286 |
+
if charset is None:
|
| 287 |
+
charset = self._charset
|
| 288 |
+
elif not isinstance(charset, Charset):
|
| 289 |
+
charset = Charset(charset)
|
| 290 |
+
if not isinstance(s, str):
|
| 291 |
+
input_charset = charset.input_codec or 'us-ascii'
|
| 292 |
+
if input_charset == _charset.UNKNOWN8BIT:
|
| 293 |
+
s = s.decode('us-ascii', 'surrogateescape')
|
| 294 |
+
else:
|
| 295 |
+
s = s.decode(input_charset, errors)
|
| 296 |
+
# Ensure that the bytes we're storing can be decoded to the output
|
| 297 |
+
# character set, otherwise an early error is raised.
|
| 298 |
+
output_charset = charset.output_codec or 'us-ascii'
|
| 299 |
+
if output_charset != _charset.UNKNOWN8BIT:
|
| 300 |
+
try:
|
| 301 |
+
s.encode(output_charset, errors)
|
| 302 |
+
except UnicodeEncodeError:
|
| 303 |
+
if output_charset!='us-ascii':
|
| 304 |
+
raise
|
| 305 |
+
charset = UTF8
|
| 306 |
+
self._chunks.append((s, charset))
|
| 307 |
+
|
| 308 |
+
def _nonctext(self, s):
|
| 309 |
+
"""True if string s is not a ctext character of RFC822.
|
| 310 |
+
"""
|
| 311 |
+
return s.isspace() or s in ('(', ')', '\\')
|
| 312 |
+
|
| 313 |
+
def encode(self, splitchars=';, \t', maxlinelen=None, linesep='\n'):
|
| 314 |
+
r"""Encode a message header into an RFC-compliant format.
|
| 315 |
+
|
| 316 |
+
There are many issues involved in converting a given string for use in
|
| 317 |
+
an email header. Only certain character sets are readable in most
|
| 318 |
+
email clients, and as header strings can only contain a subset of
|
| 319 |
+
7-bit ASCII, care must be taken to properly convert and encode (with
|
| 320 |
+
Base64 or quoted-printable) header strings. In addition, there is a
|
| 321 |
+
75-character length limit on any given encoded header field, so
|
| 322 |
+
line-wrapping must be performed, even with double-byte character sets.
|
| 323 |
+
|
| 324 |
+
Optional maxlinelen specifies the maximum length of each generated
|
| 325 |
+
line, exclusive of the linesep string. Individual lines may be longer
|
| 326 |
+
than maxlinelen if a folding point cannot be found. The first line
|
| 327 |
+
will be shorter by the length of the header name plus ": " if a header
|
| 328 |
+
name was specified at Header construction time. The default value for
|
| 329 |
+
maxlinelen is determined at header construction time.
|
| 330 |
+
|
| 331 |
+
Optional splitchars is a string containing characters which should be
|
| 332 |
+
given extra weight by the splitting algorithm during normal header
|
| 333 |
+
wrapping. This is in very rough support of RFC 2822's `higher level
|
| 334 |
+
syntactic breaks': split points preceded by a splitchar are preferred
|
| 335 |
+
during line splitting, with the characters preferred in the order in
|
| 336 |
+
which they appear in the string. Space and tab may be included in the
|
| 337 |
+
string to indicate whether preference should be given to one over the
|
| 338 |
+
other as a split point when other split chars do not appear in the line
|
| 339 |
+
being split. Splitchars does not affect RFC 2047 encoded lines.
|
| 340 |
+
|
| 341 |
+
Optional linesep is a string to be used to separate the lines of
|
| 342 |
+
the value. The default value is the most useful for typical
|
| 343 |
+
Python applications, but it can be set to \r\n to produce RFC-compliant
|
| 344 |
+
line separators when needed.
|
| 345 |
+
"""
|
| 346 |
+
self._normalize()
|
| 347 |
+
if maxlinelen is None:
|
| 348 |
+
maxlinelen = self._maxlinelen
|
| 349 |
+
# A maxlinelen of 0 means don't wrap. For all practical purposes,
|
| 350 |
+
# choosing a huge number here accomplishes that and makes the
|
| 351 |
+
# _ValueFormatter algorithm much simpler.
|
| 352 |
+
if maxlinelen == 0:
|
| 353 |
+
maxlinelen = 1000000
|
| 354 |
+
formatter = _ValueFormatter(self._headerlen, maxlinelen,
|
| 355 |
+
self._continuation_ws, splitchars)
|
| 356 |
+
lastcs = None
|
| 357 |
+
hasspace = lastspace = None
|
| 358 |
+
for string, charset in self._chunks:
|
| 359 |
+
if hasspace is not None:
|
| 360 |
+
hasspace = string and self._nonctext(string[0])
|
| 361 |
+
if lastcs not in (None, 'us-ascii'):
|
| 362 |
+
if not hasspace or charset not in (None, 'us-ascii'):
|
| 363 |
+
formatter.add_transition()
|
| 364 |
+
elif charset not in (None, 'us-ascii') and not lastspace:
|
| 365 |
+
formatter.add_transition()
|
| 366 |
+
lastspace = string and self._nonctext(string[-1])
|
| 367 |
+
lastcs = charset
|
| 368 |
+
hasspace = False
|
| 369 |
+
lines = string.splitlines()
|
| 370 |
+
if lines:
|
| 371 |
+
formatter.feed('', lines[0], charset)
|
| 372 |
+
else:
|
| 373 |
+
formatter.feed('', '', charset)
|
| 374 |
+
for line in lines[1:]:
|
| 375 |
+
formatter.newline()
|
| 376 |
+
if charset.header_encoding is not None:
|
| 377 |
+
formatter.feed(self._continuation_ws, ' ' + line.lstrip(),
|
| 378 |
+
charset)
|
| 379 |
+
else:
|
| 380 |
+
sline = line.lstrip()
|
| 381 |
+
fws = line[:len(line)-len(sline)]
|
| 382 |
+
formatter.feed(fws, sline, charset)
|
| 383 |
+
if len(lines) > 1:
|
| 384 |
+
formatter.newline()
|
| 385 |
+
if self._chunks:
|
| 386 |
+
formatter.add_transition()
|
| 387 |
+
value = formatter._str(linesep)
|
| 388 |
+
if _embedded_header.search(value):
|
| 389 |
+
raise HeaderParseError("header value appears to contain "
|
| 390 |
+
"an embedded header: {!r}".format(value))
|
| 391 |
+
return value
|
| 392 |
+
|
| 393 |
+
def _normalize(self):
|
| 394 |
+
# Step 1: Normalize the chunks so that all runs of identical charsets
|
| 395 |
+
# get collapsed into a single unicode string.
|
| 396 |
+
chunks = []
|
| 397 |
+
last_charset = None
|
| 398 |
+
last_chunk = []
|
| 399 |
+
for string, charset in self._chunks:
|
| 400 |
+
if charset == last_charset:
|
| 401 |
+
last_chunk.append(string)
|
| 402 |
+
else:
|
| 403 |
+
if last_charset is not None:
|
| 404 |
+
chunks.append((SPACE.join(last_chunk), last_charset))
|
| 405 |
+
last_chunk = [string]
|
| 406 |
+
last_charset = charset
|
| 407 |
+
if last_chunk:
|
| 408 |
+
chunks.append((SPACE.join(last_chunk), last_charset))
|
| 409 |
+
self._chunks = chunks
|
| 410 |
+
|
| 411 |
+
|
| 412 |
+
|
| 413 |
+
class _ValueFormatter:
|
| 414 |
+
def __init__(self, headerlen, maxlen, continuation_ws, splitchars):
|
| 415 |
+
self._maxlen = maxlen
|
| 416 |
+
self._continuation_ws = continuation_ws
|
| 417 |
+
self._continuation_ws_len = len(continuation_ws)
|
| 418 |
+
self._splitchars = splitchars
|
| 419 |
+
self._lines = []
|
| 420 |
+
self._current_line = _Accumulator(headerlen)
|
| 421 |
+
|
| 422 |
+
def _str(self, linesep):
|
| 423 |
+
self.newline()
|
| 424 |
+
return linesep.join(self._lines)
|
| 425 |
+
|
| 426 |
+
def __str__(self):
|
| 427 |
+
return self._str(NL)
|
| 428 |
+
|
| 429 |
+
def newline(self):
|
| 430 |
+
end_of_line = self._current_line.pop()
|
| 431 |
+
if end_of_line != (' ', ''):
|
| 432 |
+
self._current_line.push(*end_of_line)
|
| 433 |
+
if len(self._current_line) > 0:
|
| 434 |
+
if self._current_line.is_onlyws() and self._lines:
|
| 435 |
+
self._lines[-1] += str(self._current_line)
|
| 436 |
+
else:
|
| 437 |
+
self._lines.append(str(self._current_line))
|
| 438 |
+
self._current_line.reset()
|
| 439 |
+
|
| 440 |
+
def add_transition(self):
|
| 441 |
+
self._current_line.push(' ', '')
|
| 442 |
+
|
| 443 |
+
def feed(self, fws, string, charset):
|
| 444 |
+
# If the charset has no header encoding (i.e. it is an ASCII encoding)
|
| 445 |
+
# then we must split the header at the "highest level syntactic break"
|
| 446 |
+
# possible. Note that we don't have a lot of smarts about field
|
| 447 |
+
# syntax; we just try to break on semi-colons, then commas, then
|
| 448 |
+
# whitespace. Eventually, this should be pluggable.
|
| 449 |
+
if charset.header_encoding is None:
|
| 450 |
+
self._ascii_split(fws, string, self._splitchars)
|
| 451 |
+
return
|
| 452 |
+
# Otherwise, we're doing either a Base64 or a quoted-printable
|
| 453 |
+
# encoding which means we don't need to split the line on syntactic
|
| 454 |
+
# breaks. We can basically just find enough characters to fit on the
|
| 455 |
+
# current line, minus the RFC 2047 chrome. What makes this trickier
|
| 456 |
+
# though is that we have to split at octet boundaries, not character
|
| 457 |
+
# boundaries but it's only safe to split at character boundaries so at
|
| 458 |
+
# best we can only get close.
|
| 459 |
+
encoded_lines = charset.header_encode_lines(string, self._maxlengths())
|
| 460 |
+
# The first element extends the current line, but if it's None then
|
| 461 |
+
# nothing more fit on the current line so start a new line.
|
| 462 |
+
try:
|
| 463 |
+
first_line = encoded_lines.pop(0)
|
| 464 |
+
except IndexError:
|
| 465 |
+
# There are no encoded lines, so we're done.
|
| 466 |
+
return
|
| 467 |
+
if first_line is not None:
|
| 468 |
+
self._append_chunk(fws, first_line)
|
| 469 |
+
try:
|
| 470 |
+
last_line = encoded_lines.pop()
|
| 471 |
+
except IndexError:
|
| 472 |
+
# There was only one line.
|
| 473 |
+
return
|
| 474 |
+
self.newline()
|
| 475 |
+
self._current_line.push(self._continuation_ws, last_line)
|
| 476 |
+
# Everything else are full lines in themselves.
|
| 477 |
+
for line in encoded_lines:
|
| 478 |
+
self._lines.append(self._continuation_ws + line)
|
| 479 |
+
|
| 480 |
+
def _maxlengths(self):
|
| 481 |
+
# The first line's length.
|
| 482 |
+
yield self._maxlen - len(self._current_line)
|
| 483 |
+
while True:
|
| 484 |
+
yield self._maxlen - self._continuation_ws_len
|
| 485 |
+
|
| 486 |
+
def _ascii_split(self, fws, string, splitchars):
|
| 487 |
+
# The RFC 2822 header folding algorithm is simple in principle but
|
| 488 |
+
# complex in practice. Lines may be folded any place where "folding
|
| 489 |
+
# white space" appears by inserting a linesep character in front of the
|
| 490 |
+
# FWS. The complication is that not all spaces or tabs qualify as FWS,
|
| 491 |
+
# and we are also supposed to prefer to break at "higher level
|
| 492 |
+
# syntactic breaks". We can't do either of these without intimate
|
| 493 |
+
# knowledge of the structure of structured headers, which we don't have
|
| 494 |
+
# here. So the best we can do here is prefer to break at the specified
|
| 495 |
+
# splitchars, and hope that we don't choose any spaces or tabs that
|
| 496 |
+
# aren't legal FWS. (This is at least better than the old algorithm,
|
| 497 |
+
# where we would sometimes *introduce* FWS after a splitchar, or the
|
| 498 |
+
# algorithm before that, where we would turn all white space runs into
|
| 499 |
+
# single spaces or tabs.)
|
| 500 |
+
parts = re.split("(["+FWS+"]+)", fws+string)
|
| 501 |
+
if parts[0]:
|
| 502 |
+
parts[:0] = ['']
|
| 503 |
+
else:
|
| 504 |
+
parts.pop(0)
|
| 505 |
+
for fws, part in zip(*[iter(parts)]*2):
|
| 506 |
+
self._append_chunk(fws, part)
|
| 507 |
+
|
| 508 |
+
def _append_chunk(self, fws, string):
|
| 509 |
+
self._current_line.push(fws, string)
|
| 510 |
+
if len(self._current_line) > self._maxlen:
|
| 511 |
+
# Find the best split point, working backward from the end.
|
| 512 |
+
# There might be none, on a long first line.
|
| 513 |
+
for ch in self._splitchars:
|
| 514 |
+
for i in range(self._current_line.part_count()-1, 0, -1):
|
| 515 |
+
if ch.isspace():
|
| 516 |
+
fws = self._current_line[i][0]
|
| 517 |
+
if fws and fws[0]==ch:
|
| 518 |
+
break
|
| 519 |
+
prevpart = self._current_line[i-1][1]
|
| 520 |
+
if prevpart and prevpart[-1]==ch:
|
| 521 |
+
break
|
| 522 |
+
else:
|
| 523 |
+
continue
|
| 524 |
+
break
|
| 525 |
+
else:
|
| 526 |
+
fws, part = self._current_line.pop()
|
| 527 |
+
if self._current_line._initial_size > 0:
|
| 528 |
+
# There will be a header, so leave it on a line by itself.
|
| 529 |
+
self.newline()
|
| 530 |
+
if not fws:
|
| 531 |
+
# We don't use continuation_ws here because the whitespace
|
| 532 |
+
# after a header should always be a space.
|
| 533 |
+
fws = ' '
|
| 534 |
+
self._current_line.push(fws, part)
|
| 535 |
+
return
|
| 536 |
+
remainder = self._current_line.pop_from(i)
|
| 537 |
+
self._lines.append(str(self._current_line))
|
| 538 |
+
self._current_line.reset(remainder)
|
| 539 |
+
|
| 540 |
+
|
| 541 |
+
class _Accumulator(list):
|
| 542 |
+
|
| 543 |
+
def __init__(self, initial_size=0):
|
| 544 |
+
self._initial_size = initial_size
|
| 545 |
+
super().__init__()
|
| 546 |
+
|
| 547 |
+
def push(self, fws, string):
|
| 548 |
+
self.append((fws, string))
|
| 549 |
+
|
| 550 |
+
def pop_from(self, i=0):
|
| 551 |
+
popped = self[i:]
|
| 552 |
+
self[i:] = []
|
| 553 |
+
return popped
|
| 554 |
+
|
| 555 |
+
def pop(self):
|
| 556 |
+
if self.part_count()==0:
|
| 557 |
+
return ('', '')
|
| 558 |
+
return super().pop()
|
| 559 |
+
|
| 560 |
+
def __len__(self):
|
| 561 |
+
return sum((len(fws)+len(part) for fws, part in self),
|
| 562 |
+
self._initial_size)
|
| 563 |
+
|
| 564 |
+
def __str__(self):
|
| 565 |
+
return EMPTYSTRING.join((EMPTYSTRING.join((fws, part))
|
| 566 |
+
for fws, part in self))
|
| 567 |
+
|
| 568 |
+
def reset(self, startval=None):
|
| 569 |
+
if startval is None:
|
| 570 |
+
startval = []
|
| 571 |
+
self[:] = startval
|
| 572 |
+
self._initial_size = 0
|
| 573 |
+
|
| 574 |
+
def is_onlyws(self):
|
| 575 |
+
return self._initial_size==0 and (not self or str(self).isspace())
|
| 576 |
+
|
| 577 |
+
def part_count(self):
|
| 578 |
+
return super().__len__()
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/email/headerregistry.py
ADDED
|
@@ -0,0 +1,607 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Representing and manipulating email headers via custom objects.
|
| 2 |
+
|
| 3 |
+
This module provides an implementation of the HeaderRegistry API.
|
| 4 |
+
The implementation is designed to flexibly follow RFC5322 rules.
|
| 5 |
+
|
| 6 |
+
Eventually HeaderRegistry will be a public API, but it isn't yet,
|
| 7 |
+
and will probably change some before that happens.
|
| 8 |
+
|
| 9 |
+
"""
|
| 10 |
+
from types import MappingProxyType
|
| 11 |
+
|
| 12 |
+
from email import utils
|
| 13 |
+
from email import errors
|
| 14 |
+
from email import _header_value_parser as parser
|
| 15 |
+
|
| 16 |
+
class Address:
|
| 17 |
+
|
| 18 |
+
def __init__(self, display_name='', username='', domain='', addr_spec=None):
|
| 19 |
+
"""Create an object representing a full email address.
|
| 20 |
+
|
| 21 |
+
An address can have a 'display_name', a 'username', and a 'domain'. In
|
| 22 |
+
addition to specifying the username and domain separately, they may be
|
| 23 |
+
specified together by using the addr_spec keyword *instead of* the
|
| 24 |
+
username and domain keywords. If an addr_spec string is specified it
|
| 25 |
+
must be properly quoted according to RFC 5322 rules; an error will be
|
| 26 |
+
raised if it is not.
|
| 27 |
+
|
| 28 |
+
An Address object has display_name, username, domain, and addr_spec
|
| 29 |
+
attributes, all of which are read-only. The addr_spec and the string
|
| 30 |
+
value of the object are both quoted according to RFC5322 rules, but
|
| 31 |
+
without any Content Transfer Encoding.
|
| 32 |
+
|
| 33 |
+
"""
|
| 34 |
+
|
| 35 |
+
inputs = ''.join(filter(None, (display_name, username, domain, addr_spec)))
|
| 36 |
+
if '\r' in inputs or '\n' in inputs:
|
| 37 |
+
raise ValueError("invalid arguments; address parts cannot contain CR or LF")
|
| 38 |
+
|
| 39 |
+
# This clause with its potential 'raise' may only happen when an
|
| 40 |
+
# application program creates an Address object using an addr_spec
|
| 41 |
+
# keyword. The email library code itself must always supply username
|
| 42 |
+
# and domain.
|
| 43 |
+
if addr_spec is not None:
|
| 44 |
+
if username or domain:
|
| 45 |
+
raise TypeError("addrspec specified when username and/or "
|
| 46 |
+
"domain also specified")
|
| 47 |
+
a_s, rest = parser.get_addr_spec(addr_spec)
|
| 48 |
+
if rest:
|
| 49 |
+
raise ValueError("Invalid addr_spec; only '{}' "
|
| 50 |
+
"could be parsed from '{}'".format(
|
| 51 |
+
a_s, addr_spec))
|
| 52 |
+
if a_s.all_defects:
|
| 53 |
+
raise a_s.all_defects[0]
|
| 54 |
+
username = a_s.local_part
|
| 55 |
+
domain = a_s.domain
|
| 56 |
+
self._display_name = display_name
|
| 57 |
+
self._username = username
|
| 58 |
+
self._domain = domain
|
| 59 |
+
|
| 60 |
+
@property
|
| 61 |
+
def display_name(self):
|
| 62 |
+
return self._display_name
|
| 63 |
+
|
| 64 |
+
@property
|
| 65 |
+
def username(self):
|
| 66 |
+
return self._username
|
| 67 |
+
|
| 68 |
+
@property
|
| 69 |
+
def domain(self):
|
| 70 |
+
return self._domain
|
| 71 |
+
|
| 72 |
+
@property
|
| 73 |
+
def addr_spec(self):
|
| 74 |
+
"""The addr_spec (username@domain) portion of the address, quoted
|
| 75 |
+
according to RFC 5322 rules, but with no Content Transfer Encoding.
|
| 76 |
+
"""
|
| 77 |
+
nameset = set(self.username)
|
| 78 |
+
if len(nameset) > len(nameset-parser.DOT_ATOM_ENDS):
|
| 79 |
+
lp = parser.quote_string(self.username)
|
| 80 |
+
else:
|
| 81 |
+
lp = self.username
|
| 82 |
+
if self.domain:
|
| 83 |
+
return lp + '@' + self.domain
|
| 84 |
+
if not lp:
|
| 85 |
+
return '<>'
|
| 86 |
+
return lp
|
| 87 |
+
|
| 88 |
+
def __repr__(self):
|
| 89 |
+
return "{}(display_name={!r}, username={!r}, domain={!r})".format(
|
| 90 |
+
self.__class__.__name__,
|
| 91 |
+
self.display_name, self.username, self.domain)
|
| 92 |
+
|
| 93 |
+
def __str__(self):
|
| 94 |
+
nameset = set(self.display_name)
|
| 95 |
+
if len(nameset) > len(nameset-parser.SPECIALS):
|
| 96 |
+
disp = parser.quote_string(self.display_name)
|
| 97 |
+
else:
|
| 98 |
+
disp = self.display_name
|
| 99 |
+
if disp:
|
| 100 |
+
addr_spec = '' if self.addr_spec=='<>' else self.addr_spec
|
| 101 |
+
return "{} <{}>".format(disp, addr_spec)
|
| 102 |
+
return self.addr_spec
|
| 103 |
+
|
| 104 |
+
def __eq__(self, other):
|
| 105 |
+
if type(other) != type(self):
|
| 106 |
+
return False
|
| 107 |
+
return (self.display_name == other.display_name and
|
| 108 |
+
self.username == other.username and
|
| 109 |
+
self.domain == other.domain)
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
class Group:
|
| 113 |
+
|
| 114 |
+
def __init__(self, display_name=None, addresses=None):
|
| 115 |
+
"""Create an object representing an address group.
|
| 116 |
+
|
| 117 |
+
An address group consists of a display_name followed by colon and a
|
| 118 |
+
list of addresses (see Address) terminated by a semi-colon. The Group
|
| 119 |
+
is created by specifying a display_name and a possibly empty list of
|
| 120 |
+
Address objects. A Group can also be used to represent a single
|
| 121 |
+
address that is not in a group, which is convenient when manipulating
|
| 122 |
+
lists that are a combination of Groups and individual Addresses. In
|
| 123 |
+
this case the display_name should be set to None. In particular, the
|
| 124 |
+
string representation of a Group whose display_name is None is the same
|
| 125 |
+
as the Address object, if there is one and only one Address object in
|
| 126 |
+
the addresses list.
|
| 127 |
+
|
| 128 |
+
"""
|
| 129 |
+
self._display_name = display_name
|
| 130 |
+
self._addresses = tuple(addresses) if addresses else tuple()
|
| 131 |
+
|
| 132 |
+
@property
|
| 133 |
+
def display_name(self):
|
| 134 |
+
return self._display_name
|
| 135 |
+
|
| 136 |
+
@property
|
| 137 |
+
def addresses(self):
|
| 138 |
+
return self._addresses
|
| 139 |
+
|
| 140 |
+
def __repr__(self):
|
| 141 |
+
return "{}(display_name={!r}, addresses={!r}".format(
|
| 142 |
+
self.__class__.__name__,
|
| 143 |
+
self.display_name, self.addresses)
|
| 144 |
+
|
| 145 |
+
def __str__(self):
|
| 146 |
+
if self.display_name is None and len(self.addresses)==1:
|
| 147 |
+
return str(self.addresses[0])
|
| 148 |
+
disp = self.display_name
|
| 149 |
+
if disp is not None:
|
| 150 |
+
nameset = set(disp)
|
| 151 |
+
if len(nameset) > len(nameset-parser.SPECIALS):
|
| 152 |
+
disp = parser.quote_string(disp)
|
| 153 |
+
adrstr = ", ".join(str(x) for x in self.addresses)
|
| 154 |
+
adrstr = ' ' + adrstr if adrstr else adrstr
|
| 155 |
+
return "{}:{};".format(disp, adrstr)
|
| 156 |
+
|
| 157 |
+
def __eq__(self, other):
|
| 158 |
+
if type(other) != type(self):
|
| 159 |
+
return False
|
| 160 |
+
return (self.display_name == other.display_name and
|
| 161 |
+
self.addresses == other.addresses)
|
| 162 |
+
|
| 163 |
+
|
| 164 |
+
# Header Classes #
|
| 165 |
+
|
| 166 |
+
class BaseHeader(str):
|
| 167 |
+
|
| 168 |
+
"""Base class for message headers.
|
| 169 |
+
|
| 170 |
+
Implements generic behavior and provides tools for subclasses.
|
| 171 |
+
|
| 172 |
+
A subclass must define a classmethod named 'parse' that takes an unfolded
|
| 173 |
+
value string and a dictionary as its arguments. The dictionary will
|
| 174 |
+
contain one key, 'defects', initialized to an empty list. After the call
|
| 175 |
+
the dictionary must contain two additional keys: parse_tree, set to the
|
| 176 |
+
parse tree obtained from parsing the header, and 'decoded', set to the
|
| 177 |
+
string value of the idealized representation of the data from the value.
|
| 178 |
+
(That is, encoded words are decoded, and values that have canonical
|
| 179 |
+
representations are so represented.)
|
| 180 |
+
|
| 181 |
+
The defects key is intended to collect parsing defects, which the message
|
| 182 |
+
parser will subsequently dispose of as appropriate. The parser should not,
|
| 183 |
+
insofar as practical, raise any errors. Defects should be added to the
|
| 184 |
+
list instead. The standard header parsers register defects for RFC
|
| 185 |
+
compliance issues, for obsolete RFC syntax, and for unrecoverable parsing
|
| 186 |
+
errors.
|
| 187 |
+
|
| 188 |
+
The parse method may add additional keys to the dictionary. In this case
|
| 189 |
+
the subclass must define an 'init' method, which will be passed the
|
| 190 |
+
dictionary as its keyword arguments. The method should use (usually by
|
| 191 |
+
setting them as the value of similarly named attributes) and remove all the
|
| 192 |
+
extra keys added by its parse method, and then use super to call its parent
|
| 193 |
+
class with the remaining arguments and keywords.
|
| 194 |
+
|
| 195 |
+
The subclass should also make sure that a 'max_count' attribute is defined
|
| 196 |
+
that is either None or 1. XXX: need to better define this API.
|
| 197 |
+
|
| 198 |
+
"""
|
| 199 |
+
|
| 200 |
+
def __new__(cls, name, value):
|
| 201 |
+
kwds = {'defects': []}
|
| 202 |
+
cls.parse(value, kwds)
|
| 203 |
+
if utils._has_surrogates(kwds['decoded']):
|
| 204 |
+
kwds['decoded'] = utils._sanitize(kwds['decoded'])
|
| 205 |
+
self = str.__new__(cls, kwds['decoded'])
|
| 206 |
+
del kwds['decoded']
|
| 207 |
+
self.init(name, **kwds)
|
| 208 |
+
return self
|
| 209 |
+
|
| 210 |
+
def init(self, name, *, parse_tree, defects):
|
| 211 |
+
self._name = name
|
| 212 |
+
self._parse_tree = parse_tree
|
| 213 |
+
self._defects = defects
|
| 214 |
+
|
| 215 |
+
@property
|
| 216 |
+
def name(self):
|
| 217 |
+
return self._name
|
| 218 |
+
|
| 219 |
+
@property
|
| 220 |
+
def defects(self):
|
| 221 |
+
return tuple(self._defects)
|
| 222 |
+
|
| 223 |
+
def __reduce__(self):
|
| 224 |
+
return (
|
| 225 |
+
_reconstruct_header,
|
| 226 |
+
(
|
| 227 |
+
self.__class__.__name__,
|
| 228 |
+
self.__class__.__bases__,
|
| 229 |
+
str(self),
|
| 230 |
+
),
|
| 231 |
+
self.__dict__)
|
| 232 |
+
|
| 233 |
+
@classmethod
|
| 234 |
+
def _reconstruct(cls, value):
|
| 235 |
+
return str.__new__(cls, value)
|
| 236 |
+
|
| 237 |
+
def fold(self, *, policy):
|
| 238 |
+
"""Fold header according to policy.
|
| 239 |
+
|
| 240 |
+
The parsed representation of the header is folded according to
|
| 241 |
+
RFC5322 rules, as modified by the policy. If the parse tree
|
| 242 |
+
contains surrogateescaped bytes, the bytes are CTE encoded using
|
| 243 |
+
the charset 'unknown-8bit".
|
| 244 |
+
|
| 245 |
+
Any non-ASCII characters in the parse tree are CTE encoded using
|
| 246 |
+
charset utf-8. XXX: make this a policy setting.
|
| 247 |
+
|
| 248 |
+
The returned value is an ASCII-only string possibly containing linesep
|
| 249 |
+
characters, and ending with a linesep character. The string includes
|
| 250 |
+
the header name and the ': ' separator.
|
| 251 |
+
|
| 252 |
+
"""
|
| 253 |
+
# At some point we need to put fws here if it was in the source.
|
| 254 |
+
header = parser.Header([
|
| 255 |
+
parser.HeaderLabel([
|
| 256 |
+
parser.ValueTerminal(self.name, 'header-name'),
|
| 257 |
+
parser.ValueTerminal(':', 'header-sep')]),
|
| 258 |
+
])
|
| 259 |
+
if self._parse_tree:
|
| 260 |
+
header.append(
|
| 261 |
+
parser.CFWSList([parser.WhiteSpaceTerminal(' ', 'fws')]))
|
| 262 |
+
header.append(self._parse_tree)
|
| 263 |
+
return header.fold(policy=policy)
|
| 264 |
+
|
| 265 |
+
|
| 266 |
+
def _reconstruct_header(cls_name, bases, value):
|
| 267 |
+
return type(cls_name, bases, {})._reconstruct(value)
|
| 268 |
+
|
| 269 |
+
|
| 270 |
+
class UnstructuredHeader:
|
| 271 |
+
|
| 272 |
+
max_count = None
|
| 273 |
+
value_parser = staticmethod(parser.get_unstructured)
|
| 274 |
+
|
| 275 |
+
@classmethod
|
| 276 |
+
def parse(cls, value, kwds):
|
| 277 |
+
kwds['parse_tree'] = cls.value_parser(value)
|
| 278 |
+
kwds['decoded'] = str(kwds['parse_tree'])
|
| 279 |
+
|
| 280 |
+
|
| 281 |
+
class UniqueUnstructuredHeader(UnstructuredHeader):
|
| 282 |
+
|
| 283 |
+
max_count = 1
|
| 284 |
+
|
| 285 |
+
|
| 286 |
+
class DateHeader:
|
| 287 |
+
|
| 288 |
+
"""Header whose value consists of a single timestamp.
|
| 289 |
+
|
| 290 |
+
Provides an additional attribute, datetime, which is either an aware
|
| 291 |
+
datetime using a timezone, or a naive datetime if the timezone
|
| 292 |
+
in the input string is -0000. Also accepts a datetime as input.
|
| 293 |
+
The 'value' attribute is the normalized form of the timestamp,
|
| 294 |
+
which means it is the output of format_datetime on the datetime.
|
| 295 |
+
"""
|
| 296 |
+
|
| 297 |
+
max_count = None
|
| 298 |
+
|
| 299 |
+
# This is used only for folding, not for creating 'decoded'.
|
| 300 |
+
value_parser = staticmethod(parser.get_unstructured)
|
| 301 |
+
|
| 302 |
+
@classmethod
|
| 303 |
+
def parse(cls, value, kwds):
|
| 304 |
+
if not value:
|
| 305 |
+
kwds['defects'].append(errors.HeaderMissingRequiredValue())
|
| 306 |
+
kwds['datetime'] = None
|
| 307 |
+
kwds['decoded'] = ''
|
| 308 |
+
kwds['parse_tree'] = parser.TokenList()
|
| 309 |
+
return
|
| 310 |
+
if isinstance(value, str):
|
| 311 |
+
value = utils.parsedate_to_datetime(value)
|
| 312 |
+
kwds['datetime'] = value
|
| 313 |
+
kwds['decoded'] = utils.format_datetime(kwds['datetime'])
|
| 314 |
+
kwds['parse_tree'] = cls.value_parser(kwds['decoded'])
|
| 315 |
+
|
| 316 |
+
def init(self, *args, **kw):
|
| 317 |
+
self._datetime = kw.pop('datetime')
|
| 318 |
+
super().init(*args, **kw)
|
| 319 |
+
|
| 320 |
+
@property
|
| 321 |
+
def datetime(self):
|
| 322 |
+
return self._datetime
|
| 323 |
+
|
| 324 |
+
|
| 325 |
+
class UniqueDateHeader(DateHeader):
|
| 326 |
+
|
| 327 |
+
max_count = 1
|
| 328 |
+
|
| 329 |
+
|
| 330 |
+
class AddressHeader:
|
| 331 |
+
|
| 332 |
+
max_count = None
|
| 333 |
+
|
| 334 |
+
@staticmethod
|
| 335 |
+
def value_parser(value):
|
| 336 |
+
address_list, value = parser.get_address_list(value)
|
| 337 |
+
assert not value, 'this should not happen'
|
| 338 |
+
return address_list
|
| 339 |
+
|
| 340 |
+
@classmethod
|
| 341 |
+
def parse(cls, value, kwds):
|
| 342 |
+
if isinstance(value, str):
|
| 343 |
+
# We are translating here from the RFC language (address/mailbox)
|
| 344 |
+
# to our API language (group/address).
|
| 345 |
+
kwds['parse_tree'] = address_list = cls.value_parser(value)
|
| 346 |
+
groups = []
|
| 347 |
+
for addr in address_list.addresses:
|
| 348 |
+
groups.append(Group(addr.display_name,
|
| 349 |
+
[Address(mb.display_name or '',
|
| 350 |
+
mb.local_part or '',
|
| 351 |
+
mb.domain or '')
|
| 352 |
+
for mb in addr.all_mailboxes]))
|
| 353 |
+
defects = list(address_list.all_defects)
|
| 354 |
+
else:
|
| 355 |
+
# Assume it is Address/Group stuff
|
| 356 |
+
if not hasattr(value, '__iter__'):
|
| 357 |
+
value = [value]
|
| 358 |
+
groups = [Group(None, [item]) if not hasattr(item, 'addresses')
|
| 359 |
+
else item
|
| 360 |
+
for item in value]
|
| 361 |
+
defects = []
|
| 362 |
+
kwds['groups'] = groups
|
| 363 |
+
kwds['defects'] = defects
|
| 364 |
+
kwds['decoded'] = ', '.join([str(item) for item in groups])
|
| 365 |
+
if 'parse_tree' not in kwds:
|
| 366 |
+
kwds['parse_tree'] = cls.value_parser(kwds['decoded'])
|
| 367 |
+
|
| 368 |
+
def init(self, *args, **kw):
|
| 369 |
+
self._groups = tuple(kw.pop('groups'))
|
| 370 |
+
self._addresses = None
|
| 371 |
+
super().init(*args, **kw)
|
| 372 |
+
|
| 373 |
+
@property
|
| 374 |
+
def groups(self):
|
| 375 |
+
return self._groups
|
| 376 |
+
|
| 377 |
+
@property
|
| 378 |
+
def addresses(self):
|
| 379 |
+
if self._addresses is None:
|
| 380 |
+
self._addresses = tuple(address for group in self._groups
|
| 381 |
+
for address in group.addresses)
|
| 382 |
+
return self._addresses
|
| 383 |
+
|
| 384 |
+
|
| 385 |
+
class UniqueAddressHeader(AddressHeader):
|
| 386 |
+
|
| 387 |
+
max_count = 1
|
| 388 |
+
|
| 389 |
+
|
| 390 |
+
class SingleAddressHeader(AddressHeader):
|
| 391 |
+
|
| 392 |
+
@property
|
| 393 |
+
def address(self):
|
| 394 |
+
if len(self.addresses)!=1:
|
| 395 |
+
raise ValueError(("value of single address header {} is not "
|
| 396 |
+
"a single address").format(self.name))
|
| 397 |
+
return self.addresses[0]
|
| 398 |
+
|
| 399 |
+
|
| 400 |
+
class UniqueSingleAddressHeader(SingleAddressHeader):
|
| 401 |
+
|
| 402 |
+
max_count = 1
|
| 403 |
+
|
| 404 |
+
|
| 405 |
+
class MIMEVersionHeader:
|
| 406 |
+
|
| 407 |
+
max_count = 1
|
| 408 |
+
|
| 409 |
+
value_parser = staticmethod(parser.parse_mime_version)
|
| 410 |
+
|
| 411 |
+
@classmethod
|
| 412 |
+
def parse(cls, value, kwds):
|
| 413 |
+
kwds['parse_tree'] = parse_tree = cls.value_parser(value)
|
| 414 |
+
kwds['decoded'] = str(parse_tree)
|
| 415 |
+
kwds['defects'].extend(parse_tree.all_defects)
|
| 416 |
+
kwds['major'] = None if parse_tree.minor is None else parse_tree.major
|
| 417 |
+
kwds['minor'] = parse_tree.minor
|
| 418 |
+
if parse_tree.minor is not None:
|
| 419 |
+
kwds['version'] = '{}.{}'.format(kwds['major'], kwds['minor'])
|
| 420 |
+
else:
|
| 421 |
+
kwds['version'] = None
|
| 422 |
+
|
| 423 |
+
def init(self, *args, **kw):
|
| 424 |
+
self._version = kw.pop('version')
|
| 425 |
+
self._major = kw.pop('major')
|
| 426 |
+
self._minor = kw.pop('minor')
|
| 427 |
+
super().init(*args, **kw)
|
| 428 |
+
|
| 429 |
+
@property
|
| 430 |
+
def major(self):
|
| 431 |
+
return self._major
|
| 432 |
+
|
| 433 |
+
@property
|
| 434 |
+
def minor(self):
|
| 435 |
+
return self._minor
|
| 436 |
+
|
| 437 |
+
@property
|
| 438 |
+
def version(self):
|
| 439 |
+
return self._version
|
| 440 |
+
|
| 441 |
+
|
| 442 |
+
class ParameterizedMIMEHeader:
|
| 443 |
+
|
| 444 |
+
# Mixin that handles the params dict. Must be subclassed and
|
| 445 |
+
# a property value_parser for the specific header provided.
|
| 446 |
+
|
| 447 |
+
max_count = 1
|
| 448 |
+
|
| 449 |
+
@classmethod
|
| 450 |
+
def parse(cls, value, kwds):
|
| 451 |
+
kwds['parse_tree'] = parse_tree = cls.value_parser(value)
|
| 452 |
+
kwds['decoded'] = str(parse_tree)
|
| 453 |
+
kwds['defects'].extend(parse_tree.all_defects)
|
| 454 |
+
if parse_tree.params is None:
|
| 455 |
+
kwds['params'] = {}
|
| 456 |
+
else:
|
| 457 |
+
# The MIME RFCs specify that parameter ordering is arbitrary.
|
| 458 |
+
kwds['params'] = {utils._sanitize(name).lower():
|
| 459 |
+
utils._sanitize(value)
|
| 460 |
+
for name, value in parse_tree.params}
|
| 461 |
+
|
| 462 |
+
def init(self, *args, **kw):
|
| 463 |
+
self._params = kw.pop('params')
|
| 464 |
+
super().init(*args, **kw)
|
| 465 |
+
|
| 466 |
+
@property
|
| 467 |
+
def params(self):
|
| 468 |
+
return MappingProxyType(self._params)
|
| 469 |
+
|
| 470 |
+
|
| 471 |
+
class ContentTypeHeader(ParameterizedMIMEHeader):
|
| 472 |
+
|
| 473 |
+
value_parser = staticmethod(parser.parse_content_type_header)
|
| 474 |
+
|
| 475 |
+
def init(self, *args, **kw):
|
| 476 |
+
super().init(*args, **kw)
|
| 477 |
+
self._maintype = utils._sanitize(self._parse_tree.maintype)
|
| 478 |
+
self._subtype = utils._sanitize(self._parse_tree.subtype)
|
| 479 |
+
|
| 480 |
+
@property
|
| 481 |
+
def maintype(self):
|
| 482 |
+
return self._maintype
|
| 483 |
+
|
| 484 |
+
@property
|
| 485 |
+
def subtype(self):
|
| 486 |
+
return self._subtype
|
| 487 |
+
|
| 488 |
+
@property
|
| 489 |
+
def content_type(self):
|
| 490 |
+
return self.maintype + '/' + self.subtype
|
| 491 |
+
|
| 492 |
+
|
| 493 |
+
class ContentDispositionHeader(ParameterizedMIMEHeader):
|
| 494 |
+
|
| 495 |
+
value_parser = staticmethod(parser.parse_content_disposition_header)
|
| 496 |
+
|
| 497 |
+
def init(self, *args, **kw):
|
| 498 |
+
super().init(*args, **kw)
|
| 499 |
+
cd = self._parse_tree.content_disposition
|
| 500 |
+
self._content_disposition = cd if cd is None else utils._sanitize(cd)
|
| 501 |
+
|
| 502 |
+
@property
|
| 503 |
+
def content_disposition(self):
|
| 504 |
+
return self._content_disposition
|
| 505 |
+
|
| 506 |
+
|
| 507 |
+
class ContentTransferEncodingHeader:
|
| 508 |
+
|
| 509 |
+
max_count = 1
|
| 510 |
+
|
| 511 |
+
value_parser = staticmethod(parser.parse_content_transfer_encoding_header)
|
| 512 |
+
|
| 513 |
+
@classmethod
|
| 514 |
+
def parse(cls, value, kwds):
|
| 515 |
+
kwds['parse_tree'] = parse_tree = cls.value_parser(value)
|
| 516 |
+
kwds['decoded'] = str(parse_tree)
|
| 517 |
+
kwds['defects'].extend(parse_tree.all_defects)
|
| 518 |
+
|
| 519 |
+
def init(self, *args, **kw):
|
| 520 |
+
super().init(*args, **kw)
|
| 521 |
+
self._cte = utils._sanitize(self._parse_tree.cte)
|
| 522 |
+
|
| 523 |
+
@property
|
| 524 |
+
def cte(self):
|
| 525 |
+
return self._cte
|
| 526 |
+
|
| 527 |
+
|
| 528 |
+
class MessageIDHeader:
|
| 529 |
+
|
| 530 |
+
max_count = 1
|
| 531 |
+
value_parser = staticmethod(parser.parse_message_id)
|
| 532 |
+
|
| 533 |
+
@classmethod
|
| 534 |
+
def parse(cls, value, kwds):
|
| 535 |
+
kwds['parse_tree'] = parse_tree = cls.value_parser(value)
|
| 536 |
+
kwds['decoded'] = str(parse_tree)
|
| 537 |
+
kwds['defects'].extend(parse_tree.all_defects)
|
| 538 |
+
|
| 539 |
+
|
| 540 |
+
# The header factory #
|
| 541 |
+
|
| 542 |
+
_default_header_map = {
|
| 543 |
+
'subject': UniqueUnstructuredHeader,
|
| 544 |
+
'date': UniqueDateHeader,
|
| 545 |
+
'resent-date': DateHeader,
|
| 546 |
+
'orig-date': UniqueDateHeader,
|
| 547 |
+
'sender': UniqueSingleAddressHeader,
|
| 548 |
+
'resent-sender': SingleAddressHeader,
|
| 549 |
+
'to': UniqueAddressHeader,
|
| 550 |
+
'resent-to': AddressHeader,
|
| 551 |
+
'cc': UniqueAddressHeader,
|
| 552 |
+
'resent-cc': AddressHeader,
|
| 553 |
+
'bcc': UniqueAddressHeader,
|
| 554 |
+
'resent-bcc': AddressHeader,
|
| 555 |
+
'from': UniqueAddressHeader,
|
| 556 |
+
'resent-from': AddressHeader,
|
| 557 |
+
'reply-to': UniqueAddressHeader,
|
| 558 |
+
'mime-version': MIMEVersionHeader,
|
| 559 |
+
'content-type': ContentTypeHeader,
|
| 560 |
+
'content-disposition': ContentDispositionHeader,
|
| 561 |
+
'content-transfer-encoding': ContentTransferEncodingHeader,
|
| 562 |
+
'message-id': MessageIDHeader,
|
| 563 |
+
}
|
| 564 |
+
|
| 565 |
+
class HeaderRegistry:
|
| 566 |
+
|
| 567 |
+
"""A header_factory and header registry."""
|
| 568 |
+
|
| 569 |
+
def __init__(self, base_class=BaseHeader, default_class=UnstructuredHeader,
|
| 570 |
+
use_default_map=True):
|
| 571 |
+
"""Create a header_factory that works with the Policy API.
|
| 572 |
+
|
| 573 |
+
base_class is the class that will be the last class in the created
|
| 574 |
+
header class's __bases__ list. default_class is the class that will be
|
| 575 |
+
used if "name" (see __call__) does not appear in the registry.
|
| 576 |
+
use_default_map controls whether or not the default mapping of names to
|
| 577 |
+
specialized classes is copied in to the registry when the factory is
|
| 578 |
+
created. The default is True.
|
| 579 |
+
|
| 580 |
+
"""
|
| 581 |
+
self.registry = {}
|
| 582 |
+
self.base_class = base_class
|
| 583 |
+
self.default_class = default_class
|
| 584 |
+
if use_default_map:
|
| 585 |
+
self.registry.update(_default_header_map)
|
| 586 |
+
|
| 587 |
+
def map_to_type(self, name, cls):
|
| 588 |
+
"""Register cls as the specialized class for handling "name" headers.
|
| 589 |
+
|
| 590 |
+
"""
|
| 591 |
+
self.registry[name.lower()] = cls
|
| 592 |
+
|
| 593 |
+
def __getitem__(self, name):
|
| 594 |
+
cls = self.registry.get(name.lower(), self.default_class)
|
| 595 |
+
return type('_'+cls.__name__, (cls, self.base_class), {})
|
| 596 |
+
|
| 597 |
+
def __call__(self, name, value):
|
| 598 |
+
"""Create a header instance for header 'name' from 'value'.
|
| 599 |
+
|
| 600 |
+
Creates a header instance by creating a specialized class for parsing
|
| 601 |
+
and representing the specified header by combining the factory
|
| 602 |
+
base_class with a specialized class from the registry or the
|
| 603 |
+
default_class, and passing the name and value to the constructed
|
| 604 |
+
class's constructor.
|
| 605 |
+
|
| 606 |
+
"""
|
| 607 |
+
return self[name](name, value)
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/email/iterators.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (C) 2001-2006 Python Software Foundation
|
| 2 |
+
# Author: Barry Warsaw
|
| 3 |
+
# Contact: email-sig@python.org
|
| 4 |
+
|
| 5 |
+
"""Various types of useful iterators and generators."""
|
| 6 |
+
|
| 7 |
+
__all__ = [
|
| 8 |
+
'body_line_iterator',
|
| 9 |
+
'typed_subpart_iterator',
|
| 10 |
+
'walk',
|
| 11 |
+
# Do not include _structure() since it's part of the debugging API.
|
| 12 |
+
]
|
| 13 |
+
|
| 14 |
+
import sys
|
| 15 |
+
from io import StringIO
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
# This function will become a method of the Message class
|
| 20 |
+
def walk(self):
|
| 21 |
+
"""Walk over the message tree, yielding each subpart.
|
| 22 |
+
|
| 23 |
+
The walk is performed in depth-first order. This method is a
|
| 24 |
+
generator.
|
| 25 |
+
"""
|
| 26 |
+
yield self
|
| 27 |
+
if self.is_multipart():
|
| 28 |
+
for subpart in self.get_payload():
|
| 29 |
+
yield from subpart.walk()
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
# These two functions are imported into the Iterators.py interface module.
|
| 34 |
+
def body_line_iterator(msg, decode=False):
|
| 35 |
+
"""Iterate over the parts, returning string payloads line-by-line.
|
| 36 |
+
|
| 37 |
+
Optional decode (default False) is passed through to .get_payload().
|
| 38 |
+
"""
|
| 39 |
+
for subpart in msg.walk():
|
| 40 |
+
payload = subpart.get_payload(decode=decode)
|
| 41 |
+
if isinstance(payload, str):
|
| 42 |
+
yield from StringIO(payload)
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def typed_subpart_iterator(msg, maintype='text', subtype=None):
|
| 46 |
+
"""Iterate over the subparts with a given MIME type.
|
| 47 |
+
|
| 48 |
+
Use `maintype' as the main MIME type to match against; this defaults to
|
| 49 |
+
"text". Optional `subtype' is the MIME subtype to match against; if
|
| 50 |
+
omitted, only the main type is matched.
|
| 51 |
+
"""
|
| 52 |
+
for subpart in msg.walk():
|
| 53 |
+
if subpart.get_content_maintype() == maintype:
|
| 54 |
+
if subtype is None or subpart.get_content_subtype() == subtype:
|
| 55 |
+
yield subpart
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
def _structure(msg, fp=None, level=0, include_default=False):
|
| 60 |
+
"""A handy debugging aid"""
|
| 61 |
+
if fp is None:
|
| 62 |
+
fp = sys.stdout
|
| 63 |
+
tab = ' ' * (level * 4)
|
| 64 |
+
print(tab + msg.get_content_type(), end='', file=fp)
|
| 65 |
+
if include_default:
|
| 66 |
+
print(' [%s]' % msg.get_default_type(), file=fp)
|
| 67 |
+
else:
|
| 68 |
+
print(file=fp)
|
| 69 |
+
if msg.is_multipart():
|
| 70 |
+
for subpart in msg.get_payload():
|
| 71 |
+
_structure(subpart, fp, level+1, include_default)
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/email/message.py
ADDED
|
@@ -0,0 +1,1173 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (C) 2001-2007 Python Software Foundation
|
| 2 |
+
# Author: Barry Warsaw
|
| 3 |
+
# Contact: email-sig@python.org
|
| 4 |
+
|
| 5 |
+
"""Basic message object for the email package object model."""
|
| 6 |
+
|
| 7 |
+
__all__ = ['Message', 'EmailMessage']
|
| 8 |
+
|
| 9 |
+
import re
|
| 10 |
+
import uu
|
| 11 |
+
import quopri
|
| 12 |
+
from io import BytesIO, StringIO
|
| 13 |
+
|
| 14 |
+
# Intrapackage imports
|
| 15 |
+
from email import utils
|
| 16 |
+
from email import errors
|
| 17 |
+
from email._policybase import Policy, compat32
|
| 18 |
+
from email import charset as _charset
|
| 19 |
+
from email._encoded_words import decode_b
|
| 20 |
+
Charset = _charset.Charset
|
| 21 |
+
|
| 22 |
+
SEMISPACE = '; '
|
| 23 |
+
|
| 24 |
+
# Regular expression that matches `special' characters in parameters, the
|
| 25 |
+
# existence of which force quoting of the parameter value.
|
| 26 |
+
tspecials = re.compile(r'[ \(\)<>@,;:\\"/\[\]\?=]')
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def _splitparam(param):
|
| 30 |
+
# Split header parameters. BAW: this may be too simple. It isn't
|
| 31 |
+
# strictly RFC 2045 (section 5.1) compliant, but it catches most headers
|
| 32 |
+
# found in the wild. We may eventually need a full fledged parser.
|
| 33 |
+
# RDM: we might have a Header here; for now just stringify it.
|
| 34 |
+
a, sep, b = str(param).partition(';')
|
| 35 |
+
if not sep:
|
| 36 |
+
return a.strip(), None
|
| 37 |
+
return a.strip(), b.strip()
|
| 38 |
+
|
| 39 |
+
def _formatparam(param, value=None, quote=True):
|
| 40 |
+
"""Convenience function to format and return a key=value pair.
|
| 41 |
+
|
| 42 |
+
This will quote the value if needed or if quote is true. If value is a
|
| 43 |
+
three tuple (charset, language, value), it will be encoded according
|
| 44 |
+
to RFC2231 rules. If it contains non-ascii characters it will likewise
|
| 45 |
+
be encoded according to RFC2231 rules, using the utf-8 charset and
|
| 46 |
+
a null language.
|
| 47 |
+
"""
|
| 48 |
+
if value is not None and len(value) > 0:
|
| 49 |
+
# A tuple is used for RFC 2231 encoded parameter values where items
|
| 50 |
+
# are (charset, language, value). charset is a string, not a Charset
|
| 51 |
+
# instance. RFC 2231 encoded values are never quoted, per RFC.
|
| 52 |
+
if isinstance(value, tuple):
|
| 53 |
+
# Encode as per RFC 2231
|
| 54 |
+
param += '*'
|
| 55 |
+
value = utils.encode_rfc2231(value[2], value[0], value[1])
|
| 56 |
+
return '%s=%s' % (param, value)
|
| 57 |
+
else:
|
| 58 |
+
try:
|
| 59 |
+
value.encode('ascii')
|
| 60 |
+
except UnicodeEncodeError:
|
| 61 |
+
param += '*'
|
| 62 |
+
value = utils.encode_rfc2231(value, 'utf-8', '')
|
| 63 |
+
return '%s=%s' % (param, value)
|
| 64 |
+
# BAW: Please check this. I think that if quote is set it should
|
| 65 |
+
# force quoting even if not necessary.
|
| 66 |
+
if quote or tspecials.search(value):
|
| 67 |
+
return '%s="%s"' % (param, utils.quote(value))
|
| 68 |
+
else:
|
| 69 |
+
return '%s=%s' % (param, value)
|
| 70 |
+
else:
|
| 71 |
+
return param
|
| 72 |
+
|
| 73 |
+
def _parseparam(s):
|
| 74 |
+
# RDM This might be a Header, so for now stringify it.
|
| 75 |
+
s = ';' + str(s)
|
| 76 |
+
plist = []
|
| 77 |
+
while s[:1] == ';':
|
| 78 |
+
s = s[1:]
|
| 79 |
+
end = s.find(';')
|
| 80 |
+
while end > 0 and (s.count('"', 0, end) - s.count('\\"', 0, end)) % 2:
|
| 81 |
+
end = s.find(';', end + 1)
|
| 82 |
+
if end < 0:
|
| 83 |
+
end = len(s)
|
| 84 |
+
f = s[:end]
|
| 85 |
+
if '=' in f:
|
| 86 |
+
i = f.index('=')
|
| 87 |
+
f = f[:i].strip().lower() + '=' + f[i+1:].strip()
|
| 88 |
+
plist.append(f.strip())
|
| 89 |
+
s = s[end:]
|
| 90 |
+
return plist
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
def _unquotevalue(value):
|
| 94 |
+
# This is different than utils.collapse_rfc2231_value() because it doesn't
|
| 95 |
+
# try to convert the value to a unicode. Message.get_param() and
|
| 96 |
+
# Message.get_params() are both currently defined to return the tuple in
|
| 97 |
+
# the face of RFC 2231 parameters.
|
| 98 |
+
if isinstance(value, tuple):
|
| 99 |
+
return value[0], value[1], utils.unquote(value[2])
|
| 100 |
+
else:
|
| 101 |
+
return utils.unquote(value)
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
class Message:
|
| 106 |
+
"""Basic message object.
|
| 107 |
+
|
| 108 |
+
A message object is defined as something that has a bunch of RFC 2822
|
| 109 |
+
headers and a payload. It may optionally have an envelope header
|
| 110 |
+
(a.k.a. Unix-From or From_ header). If the message is a container (i.e. a
|
| 111 |
+
multipart or a message/rfc822), then the payload is a list of Message
|
| 112 |
+
objects, otherwise it is a string.
|
| 113 |
+
|
| 114 |
+
Message objects implement part of the `mapping' interface, which assumes
|
| 115 |
+
there is exactly one occurrence of the header per message. Some headers
|
| 116 |
+
do in fact appear multiple times (e.g. Received) and for those headers,
|
| 117 |
+
you must use the explicit API to set or get all the headers. Not all of
|
| 118 |
+
the mapping methods are implemented.
|
| 119 |
+
"""
|
| 120 |
+
def __init__(self, policy=compat32):
|
| 121 |
+
self.policy = policy
|
| 122 |
+
self._headers = []
|
| 123 |
+
self._unixfrom = None
|
| 124 |
+
self._payload = None
|
| 125 |
+
self._charset = None
|
| 126 |
+
# Defaults for multipart messages
|
| 127 |
+
self.preamble = self.epilogue = None
|
| 128 |
+
self.defects = []
|
| 129 |
+
# Default content type
|
| 130 |
+
self._default_type = 'text/plain'
|
| 131 |
+
|
| 132 |
+
def __str__(self):
|
| 133 |
+
"""Return the entire formatted message as a string.
|
| 134 |
+
"""
|
| 135 |
+
return self.as_string()
|
| 136 |
+
|
| 137 |
+
def as_string(self, unixfrom=False, maxheaderlen=0, policy=None):
|
| 138 |
+
"""Return the entire formatted message as a string.
|
| 139 |
+
|
| 140 |
+
Optional 'unixfrom', when true, means include the Unix From_ envelope
|
| 141 |
+
header. For backward compatibility reasons, if maxheaderlen is
|
| 142 |
+
not specified it defaults to 0, so you must override it explicitly
|
| 143 |
+
if you want a different maxheaderlen. 'policy' is passed to the
|
| 144 |
+
Generator instance used to serialize the message; if it is not
|
| 145 |
+
specified the policy associated with the message instance is used.
|
| 146 |
+
|
| 147 |
+
If the message object contains binary data that is not encoded
|
| 148 |
+
according to RFC standards, the non-compliant data will be replaced by
|
| 149 |
+
unicode "unknown character" code points.
|
| 150 |
+
"""
|
| 151 |
+
from email.generator import Generator
|
| 152 |
+
policy = self.policy if policy is None else policy
|
| 153 |
+
fp = StringIO()
|
| 154 |
+
g = Generator(fp,
|
| 155 |
+
mangle_from_=False,
|
| 156 |
+
maxheaderlen=maxheaderlen,
|
| 157 |
+
policy=policy)
|
| 158 |
+
g.flatten(self, unixfrom=unixfrom)
|
| 159 |
+
return fp.getvalue()
|
| 160 |
+
|
| 161 |
+
def __bytes__(self):
|
| 162 |
+
"""Return the entire formatted message as a bytes object.
|
| 163 |
+
"""
|
| 164 |
+
return self.as_bytes()
|
| 165 |
+
|
| 166 |
+
def as_bytes(self, unixfrom=False, policy=None):
|
| 167 |
+
"""Return the entire formatted message as a bytes object.
|
| 168 |
+
|
| 169 |
+
Optional 'unixfrom', when true, means include the Unix From_ envelope
|
| 170 |
+
header. 'policy' is passed to the BytesGenerator instance used to
|
| 171 |
+
serialize the message; if not specified the policy associated with
|
| 172 |
+
the message instance is used.
|
| 173 |
+
"""
|
| 174 |
+
from email.generator import BytesGenerator
|
| 175 |
+
policy = self.policy if policy is None else policy
|
| 176 |
+
fp = BytesIO()
|
| 177 |
+
g = BytesGenerator(fp, mangle_from_=False, policy=policy)
|
| 178 |
+
g.flatten(self, unixfrom=unixfrom)
|
| 179 |
+
return fp.getvalue()
|
| 180 |
+
|
| 181 |
+
def is_multipart(self):
|
| 182 |
+
"""Return True if the message consists of multiple parts."""
|
| 183 |
+
return isinstance(self._payload, list)
|
| 184 |
+
|
| 185 |
+
#
|
| 186 |
+
# Unix From_ line
|
| 187 |
+
#
|
| 188 |
+
def set_unixfrom(self, unixfrom):
|
| 189 |
+
self._unixfrom = unixfrom
|
| 190 |
+
|
| 191 |
+
def get_unixfrom(self):
|
| 192 |
+
return self._unixfrom
|
| 193 |
+
|
| 194 |
+
#
|
| 195 |
+
# Payload manipulation.
|
| 196 |
+
#
|
| 197 |
+
def attach(self, payload):
|
| 198 |
+
"""Add the given payload to the current payload.
|
| 199 |
+
|
| 200 |
+
The current payload will always be a list of objects after this method
|
| 201 |
+
is called. If you want to set the payload to a scalar object, use
|
| 202 |
+
set_payload() instead.
|
| 203 |
+
"""
|
| 204 |
+
if self._payload is None:
|
| 205 |
+
self._payload = [payload]
|
| 206 |
+
else:
|
| 207 |
+
try:
|
| 208 |
+
self._payload.append(payload)
|
| 209 |
+
except AttributeError:
|
| 210 |
+
raise TypeError("Attach is not valid on a message with a"
|
| 211 |
+
" non-multipart payload")
|
| 212 |
+
|
| 213 |
+
def get_payload(self, i=None, decode=False):
|
| 214 |
+
"""Return a reference to the payload.
|
| 215 |
+
|
| 216 |
+
The payload will either be a list object or a string. If you mutate
|
| 217 |
+
the list object, you modify the message's payload in place. Optional
|
| 218 |
+
i returns that index into the payload.
|
| 219 |
+
|
| 220 |
+
Optional decode is a flag indicating whether the payload should be
|
| 221 |
+
decoded or not, according to the Content-Transfer-Encoding header
|
| 222 |
+
(default is False).
|
| 223 |
+
|
| 224 |
+
When True and the message is not a multipart, the payload will be
|
| 225 |
+
decoded if this header's value is `quoted-printable' or `base64'. If
|
| 226 |
+
some other encoding is used, or the header is missing, or if the
|
| 227 |
+
payload has bogus data (i.e. bogus base64 or uuencoded data), the
|
| 228 |
+
payload is returned as-is.
|
| 229 |
+
|
| 230 |
+
If the message is a multipart and the decode flag is True, then None
|
| 231 |
+
is returned.
|
| 232 |
+
"""
|
| 233 |
+
# Here is the logic table for this code, based on the email5.0.0 code:
|
| 234 |
+
# i decode is_multipart result
|
| 235 |
+
# ------ ------ ------------ ------------------------------
|
| 236 |
+
# None True True None
|
| 237 |
+
# i True True None
|
| 238 |
+
# None False True _payload (a list)
|
| 239 |
+
# i False True _payload element i (a Message)
|
| 240 |
+
# i False False error (not a list)
|
| 241 |
+
# i True False error (not a list)
|
| 242 |
+
# None False False _payload
|
| 243 |
+
# None True False _payload decoded (bytes)
|
| 244 |
+
# Note that Barry planned to factor out the 'decode' case, but that
|
| 245 |
+
# isn't so easy now that we handle the 8 bit data, which needs to be
|
| 246 |
+
# converted in both the decode and non-decode path.
|
| 247 |
+
if self.is_multipart():
|
| 248 |
+
if decode:
|
| 249 |
+
return None
|
| 250 |
+
if i is None:
|
| 251 |
+
return self._payload
|
| 252 |
+
else:
|
| 253 |
+
return self._payload[i]
|
| 254 |
+
# For backward compatibility, Use isinstance and this error message
|
| 255 |
+
# instead of the more logical is_multipart test.
|
| 256 |
+
if i is not None and not isinstance(self._payload, list):
|
| 257 |
+
raise TypeError('Expected list, got %s' % type(self._payload))
|
| 258 |
+
payload = self._payload
|
| 259 |
+
# cte might be a Header, so for now stringify it.
|
| 260 |
+
cte = str(self.get('content-transfer-encoding', '')).lower()
|
| 261 |
+
# payload may be bytes here.
|
| 262 |
+
if isinstance(payload, str):
|
| 263 |
+
if utils._has_surrogates(payload):
|
| 264 |
+
bpayload = payload.encode('ascii', 'surrogateescape')
|
| 265 |
+
if not decode:
|
| 266 |
+
try:
|
| 267 |
+
payload = bpayload.decode(self.get_param('charset', 'ascii'), 'replace')
|
| 268 |
+
except LookupError:
|
| 269 |
+
payload = bpayload.decode('ascii', 'replace')
|
| 270 |
+
elif decode:
|
| 271 |
+
try:
|
| 272 |
+
bpayload = payload.encode('ascii')
|
| 273 |
+
except UnicodeError:
|
| 274 |
+
# This won't happen for RFC compliant messages (messages
|
| 275 |
+
# containing only ASCII code points in the unicode input).
|
| 276 |
+
# If it does happen, turn the string into bytes in a way
|
| 277 |
+
# guaranteed not to fail.
|
| 278 |
+
bpayload = payload.encode('raw-unicode-escape')
|
| 279 |
+
if not decode:
|
| 280 |
+
return payload
|
| 281 |
+
if cte == 'quoted-printable':
|
| 282 |
+
return quopri.decodestring(bpayload)
|
| 283 |
+
elif cte == 'base64':
|
| 284 |
+
# XXX: this is a bit of a hack; decode_b should probably be factored
|
| 285 |
+
# out somewhere, but I haven't figured out where yet.
|
| 286 |
+
value, defects = decode_b(b''.join(bpayload.splitlines()))
|
| 287 |
+
for defect in defects:
|
| 288 |
+
self.policy.handle_defect(self, defect)
|
| 289 |
+
return value
|
| 290 |
+
elif cte in ('x-uuencode', 'uuencode', 'uue', 'x-uue'):
|
| 291 |
+
in_file = BytesIO(bpayload)
|
| 292 |
+
out_file = BytesIO()
|
| 293 |
+
try:
|
| 294 |
+
uu.decode(in_file, out_file, quiet=True)
|
| 295 |
+
return out_file.getvalue()
|
| 296 |
+
except uu.Error:
|
| 297 |
+
# Some decoding problem
|
| 298 |
+
return bpayload
|
| 299 |
+
if isinstance(payload, str):
|
| 300 |
+
return bpayload
|
| 301 |
+
return payload
|
| 302 |
+
|
| 303 |
+
def set_payload(self, payload, charset=None):
|
| 304 |
+
"""Set the payload to the given value.
|
| 305 |
+
|
| 306 |
+
Optional charset sets the message's default character set. See
|
| 307 |
+
set_charset() for details.
|
| 308 |
+
"""
|
| 309 |
+
if hasattr(payload, 'encode'):
|
| 310 |
+
if charset is None:
|
| 311 |
+
self._payload = payload
|
| 312 |
+
return
|
| 313 |
+
if not isinstance(charset, Charset):
|
| 314 |
+
charset = Charset(charset)
|
| 315 |
+
payload = payload.encode(charset.output_charset)
|
| 316 |
+
if hasattr(payload, 'decode'):
|
| 317 |
+
self._payload = payload.decode('ascii', 'surrogateescape')
|
| 318 |
+
else:
|
| 319 |
+
self._payload = payload
|
| 320 |
+
if charset is not None:
|
| 321 |
+
self.set_charset(charset)
|
| 322 |
+
|
| 323 |
+
def set_charset(self, charset):
|
| 324 |
+
"""Set the charset of the payload to a given character set.
|
| 325 |
+
|
| 326 |
+
charset can be a Charset instance, a string naming a character set, or
|
| 327 |
+
None. If it is a string it will be converted to a Charset instance.
|
| 328 |
+
If charset is None, the charset parameter will be removed from the
|
| 329 |
+
Content-Type field. Anything else will generate a TypeError.
|
| 330 |
+
|
| 331 |
+
The message will be assumed to be of type text/* encoded with
|
| 332 |
+
charset.input_charset. It will be converted to charset.output_charset
|
| 333 |
+
and encoded properly, if needed, when generating the plain text
|
| 334 |
+
representation of the message. MIME headers (MIME-Version,
|
| 335 |
+
Content-Type, Content-Transfer-Encoding) will be added as needed.
|
| 336 |
+
"""
|
| 337 |
+
if charset is None:
|
| 338 |
+
self.del_param('charset')
|
| 339 |
+
self._charset = None
|
| 340 |
+
return
|
| 341 |
+
if not isinstance(charset, Charset):
|
| 342 |
+
charset = Charset(charset)
|
| 343 |
+
self._charset = charset
|
| 344 |
+
if 'MIME-Version' not in self:
|
| 345 |
+
self.add_header('MIME-Version', '1.0')
|
| 346 |
+
if 'Content-Type' not in self:
|
| 347 |
+
self.add_header('Content-Type', 'text/plain',
|
| 348 |
+
charset=charset.get_output_charset())
|
| 349 |
+
else:
|
| 350 |
+
self.set_param('charset', charset.get_output_charset())
|
| 351 |
+
if charset != charset.get_output_charset():
|
| 352 |
+
self._payload = charset.body_encode(self._payload)
|
| 353 |
+
if 'Content-Transfer-Encoding' not in self:
|
| 354 |
+
cte = charset.get_body_encoding()
|
| 355 |
+
try:
|
| 356 |
+
cte(self)
|
| 357 |
+
except TypeError:
|
| 358 |
+
# This 'if' is for backward compatibility, it allows unicode
|
| 359 |
+
# through even though that won't work correctly if the
|
| 360 |
+
# message is serialized.
|
| 361 |
+
payload = self._payload
|
| 362 |
+
if payload:
|
| 363 |
+
try:
|
| 364 |
+
payload = payload.encode('ascii', 'surrogateescape')
|
| 365 |
+
except UnicodeError:
|
| 366 |
+
payload = payload.encode(charset.output_charset)
|
| 367 |
+
self._payload = charset.body_encode(payload)
|
| 368 |
+
self.add_header('Content-Transfer-Encoding', cte)
|
| 369 |
+
|
| 370 |
+
def get_charset(self):
|
| 371 |
+
"""Return the Charset instance associated with the message's payload.
|
| 372 |
+
"""
|
| 373 |
+
return self._charset
|
| 374 |
+
|
| 375 |
+
#
|
| 376 |
+
# MAPPING INTERFACE (partial)
|
| 377 |
+
#
|
| 378 |
+
def __len__(self):
|
| 379 |
+
"""Return the total number of headers, including duplicates."""
|
| 380 |
+
return len(self._headers)
|
| 381 |
+
|
| 382 |
+
def __getitem__(self, name):
|
| 383 |
+
"""Get a header value.
|
| 384 |
+
|
| 385 |
+
Return None if the header is missing instead of raising an exception.
|
| 386 |
+
|
| 387 |
+
Note that if the header appeared multiple times, exactly which
|
| 388 |
+
occurrence gets returned is undefined. Use get_all() to get all
|
| 389 |
+
the values matching a header field name.
|
| 390 |
+
"""
|
| 391 |
+
return self.get(name)
|
| 392 |
+
|
| 393 |
+
def __setitem__(self, name, val):
|
| 394 |
+
"""Set the value of a header.
|
| 395 |
+
|
| 396 |
+
Note: this does not overwrite an existing header with the same field
|
| 397 |
+
name. Use __delitem__() first to delete any existing headers.
|
| 398 |
+
"""
|
| 399 |
+
max_count = self.policy.header_max_count(name)
|
| 400 |
+
if max_count:
|
| 401 |
+
lname = name.lower()
|
| 402 |
+
found = 0
|
| 403 |
+
for k, v in self._headers:
|
| 404 |
+
if k.lower() == lname:
|
| 405 |
+
found += 1
|
| 406 |
+
if found >= max_count:
|
| 407 |
+
raise ValueError("There may be at most {} {} headers "
|
| 408 |
+
"in a message".format(max_count, name))
|
| 409 |
+
self._headers.append(self.policy.header_store_parse(name, val))
|
| 410 |
+
|
| 411 |
+
def __delitem__(self, name):
|
| 412 |
+
"""Delete all occurrences of a header, if present.
|
| 413 |
+
|
| 414 |
+
Does not raise an exception if the header is missing.
|
| 415 |
+
"""
|
| 416 |
+
name = name.lower()
|
| 417 |
+
newheaders = []
|
| 418 |
+
for k, v in self._headers:
|
| 419 |
+
if k.lower() != name:
|
| 420 |
+
newheaders.append((k, v))
|
| 421 |
+
self._headers = newheaders
|
| 422 |
+
|
| 423 |
+
def __contains__(self, name):
|
| 424 |
+
return name.lower() in [k.lower() for k, v in self._headers]
|
| 425 |
+
|
| 426 |
+
def __iter__(self):
|
| 427 |
+
for field, value in self._headers:
|
| 428 |
+
yield field
|
| 429 |
+
|
| 430 |
+
def keys(self):
|
| 431 |
+
"""Return a list of all the message's header field names.
|
| 432 |
+
|
| 433 |
+
These will be sorted in the order they appeared in the original
|
| 434 |
+
message, or were added to the message, and may contain duplicates.
|
| 435 |
+
Any fields deleted and re-inserted are always appended to the header
|
| 436 |
+
list.
|
| 437 |
+
"""
|
| 438 |
+
return [k for k, v in self._headers]
|
| 439 |
+
|
| 440 |
+
def values(self):
|
| 441 |
+
"""Return a list of all the message's header values.
|
| 442 |
+
|
| 443 |
+
These will be sorted in the order they appeared in the original
|
| 444 |
+
message, or were added to the message, and may contain duplicates.
|
| 445 |
+
Any fields deleted and re-inserted are always appended to the header
|
| 446 |
+
list.
|
| 447 |
+
"""
|
| 448 |
+
return [self.policy.header_fetch_parse(k, v)
|
| 449 |
+
for k, v in self._headers]
|
| 450 |
+
|
| 451 |
+
def items(self):
|
| 452 |
+
"""Get all the message's header fields and values.
|
| 453 |
+
|
| 454 |
+
These will be sorted in the order they appeared in the original
|
| 455 |
+
message, or were added to the message, and may contain duplicates.
|
| 456 |
+
Any fields deleted and re-inserted are always appended to the header
|
| 457 |
+
list.
|
| 458 |
+
"""
|
| 459 |
+
return [(k, self.policy.header_fetch_parse(k, v))
|
| 460 |
+
for k, v in self._headers]
|
| 461 |
+
|
| 462 |
+
def get(self, name, failobj=None):
|
| 463 |
+
"""Get a header value.
|
| 464 |
+
|
| 465 |
+
Like __getitem__() but return failobj instead of None when the field
|
| 466 |
+
is missing.
|
| 467 |
+
"""
|
| 468 |
+
name = name.lower()
|
| 469 |
+
for k, v in self._headers:
|
| 470 |
+
if k.lower() == name:
|
| 471 |
+
return self.policy.header_fetch_parse(k, v)
|
| 472 |
+
return failobj
|
| 473 |
+
|
| 474 |
+
#
|
| 475 |
+
# "Internal" methods (public API, but only intended for use by a parser
|
| 476 |
+
# or generator, not normal application code.
|
| 477 |
+
#
|
| 478 |
+
|
| 479 |
+
def set_raw(self, name, value):
|
| 480 |
+
"""Store name and value in the model without modification.
|
| 481 |
+
|
| 482 |
+
This is an "internal" API, intended only for use by a parser.
|
| 483 |
+
"""
|
| 484 |
+
self._headers.append((name, value))
|
| 485 |
+
|
| 486 |
+
def raw_items(self):
|
| 487 |
+
"""Return the (name, value) header pairs without modification.
|
| 488 |
+
|
| 489 |
+
This is an "internal" API, intended only for use by a generator.
|
| 490 |
+
"""
|
| 491 |
+
return iter(self._headers.copy())
|
| 492 |
+
|
| 493 |
+
#
|
| 494 |
+
# Additional useful stuff
|
| 495 |
+
#
|
| 496 |
+
|
| 497 |
+
def get_all(self, name, failobj=None):
|
| 498 |
+
"""Return a list of all the values for the named field.
|
| 499 |
+
|
| 500 |
+
These will be sorted in the order they appeared in the original
|
| 501 |
+
message, and may contain duplicates. Any fields deleted and
|
| 502 |
+
re-inserted are always appended to the header list.
|
| 503 |
+
|
| 504 |
+
If no such fields exist, failobj is returned (defaults to None).
|
| 505 |
+
"""
|
| 506 |
+
values = []
|
| 507 |
+
name = name.lower()
|
| 508 |
+
for k, v in self._headers:
|
| 509 |
+
if k.lower() == name:
|
| 510 |
+
values.append(self.policy.header_fetch_parse(k, v))
|
| 511 |
+
if not values:
|
| 512 |
+
return failobj
|
| 513 |
+
return values
|
| 514 |
+
|
| 515 |
+
def add_header(self, _name, _value, **_params):
|
| 516 |
+
"""Extended header setting.
|
| 517 |
+
|
| 518 |
+
name is the header field to add. keyword arguments can be used to set
|
| 519 |
+
additional parameters for the header field, with underscores converted
|
| 520 |
+
to dashes. Normally the parameter will be added as key="value" unless
|
| 521 |
+
value is None, in which case only the key will be added. If a
|
| 522 |
+
parameter value contains non-ASCII characters it can be specified as a
|
| 523 |
+
three-tuple of (charset, language, value), in which case it will be
|
| 524 |
+
encoded according to RFC2231 rules. Otherwise it will be encoded using
|
| 525 |
+
the utf-8 charset and a language of ''.
|
| 526 |
+
|
| 527 |
+
Examples:
|
| 528 |
+
|
| 529 |
+
msg.add_header('content-disposition', 'attachment', filename='bud.gif')
|
| 530 |
+
msg.add_header('content-disposition', 'attachment',
|
| 531 |
+
filename=('utf-8', '', Fußballer.ppt'))
|
| 532 |
+
msg.add_header('content-disposition', 'attachment',
|
| 533 |
+
filename='Fußballer.ppt'))
|
| 534 |
+
"""
|
| 535 |
+
parts = []
|
| 536 |
+
for k, v in _params.items():
|
| 537 |
+
if v is None:
|
| 538 |
+
parts.append(k.replace('_', '-'))
|
| 539 |
+
else:
|
| 540 |
+
parts.append(_formatparam(k.replace('_', '-'), v))
|
| 541 |
+
if _value is not None:
|
| 542 |
+
parts.insert(0, _value)
|
| 543 |
+
self[_name] = SEMISPACE.join(parts)
|
| 544 |
+
|
| 545 |
+
def replace_header(self, _name, _value):
|
| 546 |
+
"""Replace a header.
|
| 547 |
+
|
| 548 |
+
Replace the first matching header found in the message, retaining
|
| 549 |
+
header order and case. If no matching header was found, a KeyError is
|
| 550 |
+
raised.
|
| 551 |
+
"""
|
| 552 |
+
_name = _name.lower()
|
| 553 |
+
for i, (k, v) in zip(range(len(self._headers)), self._headers):
|
| 554 |
+
if k.lower() == _name:
|
| 555 |
+
self._headers[i] = self.policy.header_store_parse(k, _value)
|
| 556 |
+
break
|
| 557 |
+
else:
|
| 558 |
+
raise KeyError(_name)
|
| 559 |
+
|
| 560 |
+
#
|
| 561 |
+
# Use these three methods instead of the three above.
|
| 562 |
+
#
|
| 563 |
+
|
| 564 |
+
def get_content_type(self):
|
| 565 |
+
"""Return the message's content type.
|
| 566 |
+
|
| 567 |
+
The returned string is coerced to lower case of the form
|
| 568 |
+
`maintype/subtype'. If there was no Content-Type header in the
|
| 569 |
+
message, the default type as given by get_default_type() will be
|
| 570 |
+
returned. Since according to RFC 2045, messages always have a default
|
| 571 |
+
type this will always return a value.
|
| 572 |
+
|
| 573 |
+
RFC 2045 defines a message's default type to be text/plain unless it
|
| 574 |
+
appears inside a multipart/digest container, in which case it would be
|
| 575 |
+
message/rfc822.
|
| 576 |
+
"""
|
| 577 |
+
missing = object()
|
| 578 |
+
value = self.get('content-type', missing)
|
| 579 |
+
if value is missing:
|
| 580 |
+
# This should have no parameters
|
| 581 |
+
return self.get_default_type()
|
| 582 |
+
ctype = _splitparam(value)[0].lower()
|
| 583 |
+
# RFC 2045, section 5.2 says if its invalid, use text/plain
|
| 584 |
+
if ctype.count('/') != 1:
|
| 585 |
+
return 'text/plain'
|
| 586 |
+
return ctype
|
| 587 |
+
|
| 588 |
+
def get_content_maintype(self):
|
| 589 |
+
"""Return the message's main content type.
|
| 590 |
+
|
| 591 |
+
This is the `maintype' part of the string returned by
|
| 592 |
+
get_content_type().
|
| 593 |
+
"""
|
| 594 |
+
ctype = self.get_content_type()
|
| 595 |
+
return ctype.split('/')[0]
|
| 596 |
+
|
| 597 |
+
def get_content_subtype(self):
|
| 598 |
+
"""Returns the message's sub-content type.
|
| 599 |
+
|
| 600 |
+
This is the `subtype' part of the string returned by
|
| 601 |
+
get_content_type().
|
| 602 |
+
"""
|
| 603 |
+
ctype = self.get_content_type()
|
| 604 |
+
return ctype.split('/')[1]
|
| 605 |
+
|
| 606 |
+
def get_default_type(self):
|
| 607 |
+
"""Return the `default' content type.
|
| 608 |
+
|
| 609 |
+
Most messages have a default content type of text/plain, except for
|
| 610 |
+
messages that are subparts of multipart/digest containers. Such
|
| 611 |
+
subparts have a default content type of message/rfc822.
|
| 612 |
+
"""
|
| 613 |
+
return self._default_type
|
| 614 |
+
|
| 615 |
+
def set_default_type(self, ctype):
|
| 616 |
+
"""Set the `default' content type.
|
| 617 |
+
|
| 618 |
+
ctype should be either "text/plain" or "message/rfc822", although this
|
| 619 |
+
is not enforced. The default content type is not stored in the
|
| 620 |
+
Content-Type header.
|
| 621 |
+
"""
|
| 622 |
+
self._default_type = ctype
|
| 623 |
+
|
| 624 |
+
def _get_params_preserve(self, failobj, header):
|
| 625 |
+
# Like get_params() but preserves the quoting of values. BAW:
|
| 626 |
+
# should this be part of the public interface?
|
| 627 |
+
missing = object()
|
| 628 |
+
value = self.get(header, missing)
|
| 629 |
+
if value is missing:
|
| 630 |
+
return failobj
|
| 631 |
+
params = []
|
| 632 |
+
for p in _parseparam(value):
|
| 633 |
+
try:
|
| 634 |
+
name, val = p.split('=', 1)
|
| 635 |
+
name = name.strip()
|
| 636 |
+
val = val.strip()
|
| 637 |
+
except ValueError:
|
| 638 |
+
# Must have been a bare attribute
|
| 639 |
+
name = p.strip()
|
| 640 |
+
val = ''
|
| 641 |
+
params.append((name, val))
|
| 642 |
+
params = utils.decode_params(params)
|
| 643 |
+
return params
|
| 644 |
+
|
| 645 |
+
def get_params(self, failobj=None, header='content-type', unquote=True):
|
| 646 |
+
"""Return the message's Content-Type parameters, as a list.
|
| 647 |
+
|
| 648 |
+
The elements of the returned list are 2-tuples of key/value pairs, as
|
| 649 |
+
split on the `=' sign. The left hand side of the `=' is the key,
|
| 650 |
+
while the right hand side is the value. If there is no `=' sign in
|
| 651 |
+
the parameter the value is the empty string. The value is as
|
| 652 |
+
described in the get_param() method.
|
| 653 |
+
|
| 654 |
+
Optional failobj is the object to return if there is no Content-Type
|
| 655 |
+
header. Optional header is the header to search instead of
|
| 656 |
+
Content-Type. If unquote is True, the value is unquoted.
|
| 657 |
+
"""
|
| 658 |
+
missing = object()
|
| 659 |
+
params = self._get_params_preserve(missing, header)
|
| 660 |
+
if params is missing:
|
| 661 |
+
return failobj
|
| 662 |
+
if unquote:
|
| 663 |
+
return [(k, _unquotevalue(v)) for k, v in params]
|
| 664 |
+
else:
|
| 665 |
+
return params
|
| 666 |
+
|
| 667 |
+
def get_param(self, param, failobj=None, header='content-type',
|
| 668 |
+
unquote=True):
|
| 669 |
+
"""Return the parameter value if found in the Content-Type header.
|
| 670 |
+
|
| 671 |
+
Optional failobj is the object to return if there is no Content-Type
|
| 672 |
+
header, or the Content-Type header has no such parameter. Optional
|
| 673 |
+
header is the header to search instead of Content-Type.
|
| 674 |
+
|
| 675 |
+
Parameter keys are always compared case insensitively. The return
|
| 676 |
+
value can either be a string, or a 3-tuple if the parameter was RFC
|
| 677 |
+
2231 encoded. When it's a 3-tuple, the elements of the value are of
|
| 678 |
+
the form (CHARSET, LANGUAGE, VALUE). Note that both CHARSET and
|
| 679 |
+
LANGUAGE can be None, in which case you should consider VALUE to be
|
| 680 |
+
encoded in the us-ascii charset. You can usually ignore LANGUAGE.
|
| 681 |
+
The parameter value (either the returned string, or the VALUE item in
|
| 682 |
+
the 3-tuple) is always unquoted, unless unquote is set to False.
|
| 683 |
+
|
| 684 |
+
If your application doesn't care whether the parameter was RFC 2231
|
| 685 |
+
encoded, it can turn the return value into a string as follows:
|
| 686 |
+
|
| 687 |
+
rawparam = msg.get_param('foo')
|
| 688 |
+
param = email.utils.collapse_rfc2231_value(rawparam)
|
| 689 |
+
|
| 690 |
+
"""
|
| 691 |
+
if header not in self:
|
| 692 |
+
return failobj
|
| 693 |
+
for k, v in self._get_params_preserve(failobj, header):
|
| 694 |
+
if k.lower() == param.lower():
|
| 695 |
+
if unquote:
|
| 696 |
+
return _unquotevalue(v)
|
| 697 |
+
else:
|
| 698 |
+
return v
|
| 699 |
+
return failobj
|
| 700 |
+
|
| 701 |
+
def set_param(self, param, value, header='Content-Type', requote=True,
|
| 702 |
+
charset=None, language='', replace=False):
|
| 703 |
+
"""Set a parameter in the Content-Type header.
|
| 704 |
+
|
| 705 |
+
If the parameter already exists in the header, its value will be
|
| 706 |
+
replaced with the new value.
|
| 707 |
+
|
| 708 |
+
If header is Content-Type and has not yet been defined for this
|
| 709 |
+
message, it will be set to "text/plain" and the new parameter and
|
| 710 |
+
value will be appended as per RFC 2045.
|
| 711 |
+
|
| 712 |
+
An alternate header can be specified in the header argument, and all
|
| 713 |
+
parameters will be quoted as necessary unless requote is False.
|
| 714 |
+
|
| 715 |
+
If charset is specified, the parameter will be encoded according to RFC
|
| 716 |
+
2231. Optional language specifies the RFC 2231 language, defaulting
|
| 717 |
+
to the empty string. Both charset and language should be strings.
|
| 718 |
+
"""
|
| 719 |
+
if not isinstance(value, tuple) and charset:
|
| 720 |
+
value = (charset, language, value)
|
| 721 |
+
|
| 722 |
+
if header not in self and header.lower() == 'content-type':
|
| 723 |
+
ctype = 'text/plain'
|
| 724 |
+
else:
|
| 725 |
+
ctype = self.get(header)
|
| 726 |
+
if not self.get_param(param, header=header):
|
| 727 |
+
if not ctype:
|
| 728 |
+
ctype = _formatparam(param, value, requote)
|
| 729 |
+
else:
|
| 730 |
+
ctype = SEMISPACE.join(
|
| 731 |
+
[ctype, _formatparam(param, value, requote)])
|
| 732 |
+
else:
|
| 733 |
+
ctype = ''
|
| 734 |
+
for old_param, old_value in self.get_params(header=header,
|
| 735 |
+
unquote=requote):
|
| 736 |
+
append_param = ''
|
| 737 |
+
if old_param.lower() == param.lower():
|
| 738 |
+
append_param = _formatparam(param, value, requote)
|
| 739 |
+
else:
|
| 740 |
+
append_param = _formatparam(old_param, old_value, requote)
|
| 741 |
+
if not ctype:
|
| 742 |
+
ctype = append_param
|
| 743 |
+
else:
|
| 744 |
+
ctype = SEMISPACE.join([ctype, append_param])
|
| 745 |
+
if ctype != self.get(header):
|
| 746 |
+
if replace:
|
| 747 |
+
self.replace_header(header, ctype)
|
| 748 |
+
else:
|
| 749 |
+
del self[header]
|
| 750 |
+
self[header] = ctype
|
| 751 |
+
|
| 752 |
+
def del_param(self, param, header='content-type', requote=True):
|
| 753 |
+
"""Remove the given parameter completely from the Content-Type header.
|
| 754 |
+
|
| 755 |
+
The header will be re-written in place without the parameter or its
|
| 756 |
+
value. All values will be quoted as necessary unless requote is
|
| 757 |
+
False. Optional header specifies an alternative to the Content-Type
|
| 758 |
+
header.
|
| 759 |
+
"""
|
| 760 |
+
if header not in self:
|
| 761 |
+
return
|
| 762 |
+
new_ctype = ''
|
| 763 |
+
for p, v in self.get_params(header=header, unquote=requote):
|
| 764 |
+
if p.lower() != param.lower():
|
| 765 |
+
if not new_ctype:
|
| 766 |
+
new_ctype = _formatparam(p, v, requote)
|
| 767 |
+
else:
|
| 768 |
+
new_ctype = SEMISPACE.join([new_ctype,
|
| 769 |
+
_formatparam(p, v, requote)])
|
| 770 |
+
if new_ctype != self.get(header):
|
| 771 |
+
del self[header]
|
| 772 |
+
self[header] = new_ctype
|
| 773 |
+
|
| 774 |
+
def set_type(self, type, header='Content-Type', requote=True):
|
| 775 |
+
"""Set the main type and subtype for the Content-Type header.
|
| 776 |
+
|
| 777 |
+
type must be a string in the form "maintype/subtype", otherwise a
|
| 778 |
+
ValueError is raised.
|
| 779 |
+
|
| 780 |
+
This method replaces the Content-Type header, keeping all the
|
| 781 |
+
parameters in place. If requote is False, this leaves the existing
|
| 782 |
+
header's quoting as is. Otherwise, the parameters will be quoted (the
|
| 783 |
+
default).
|
| 784 |
+
|
| 785 |
+
An alternative header can be specified in the header argument. When
|
| 786 |
+
the Content-Type header is set, we'll always also add a MIME-Version
|
| 787 |
+
header.
|
| 788 |
+
"""
|
| 789 |
+
# BAW: should we be strict?
|
| 790 |
+
if not type.count('/') == 1:
|
| 791 |
+
raise ValueError
|
| 792 |
+
# Set the Content-Type, you get a MIME-Version
|
| 793 |
+
if header.lower() == 'content-type':
|
| 794 |
+
del self['mime-version']
|
| 795 |
+
self['MIME-Version'] = '1.0'
|
| 796 |
+
if header not in self:
|
| 797 |
+
self[header] = type
|
| 798 |
+
return
|
| 799 |
+
params = self.get_params(header=header, unquote=requote)
|
| 800 |
+
del self[header]
|
| 801 |
+
self[header] = type
|
| 802 |
+
# Skip the first param; it's the old type.
|
| 803 |
+
for p, v in params[1:]:
|
| 804 |
+
self.set_param(p, v, header, requote)
|
| 805 |
+
|
| 806 |
+
def get_filename(self, failobj=None):
|
| 807 |
+
"""Return the filename associated with the payload if present.
|
| 808 |
+
|
| 809 |
+
The filename is extracted from the Content-Disposition header's
|
| 810 |
+
`filename' parameter, and it is unquoted. If that header is missing
|
| 811 |
+
the `filename' parameter, this method falls back to looking for the
|
| 812 |
+
`name' parameter.
|
| 813 |
+
"""
|
| 814 |
+
missing = object()
|
| 815 |
+
filename = self.get_param('filename', missing, 'content-disposition')
|
| 816 |
+
if filename is missing:
|
| 817 |
+
filename = self.get_param('name', missing, 'content-type')
|
| 818 |
+
if filename is missing:
|
| 819 |
+
return failobj
|
| 820 |
+
return utils.collapse_rfc2231_value(filename).strip()
|
| 821 |
+
|
| 822 |
+
def get_boundary(self, failobj=None):
|
| 823 |
+
"""Return the boundary associated with the payload if present.
|
| 824 |
+
|
| 825 |
+
The boundary is extracted from the Content-Type header's `boundary'
|
| 826 |
+
parameter, and it is unquoted.
|
| 827 |
+
"""
|
| 828 |
+
missing = object()
|
| 829 |
+
boundary = self.get_param('boundary', missing)
|
| 830 |
+
if boundary is missing:
|
| 831 |
+
return failobj
|
| 832 |
+
# RFC 2046 says that boundaries may begin but not end in w/s
|
| 833 |
+
return utils.collapse_rfc2231_value(boundary).rstrip()
|
| 834 |
+
|
| 835 |
+
def set_boundary(self, boundary):
|
| 836 |
+
"""Set the boundary parameter in Content-Type to 'boundary'.
|
| 837 |
+
|
| 838 |
+
This is subtly different than deleting the Content-Type header and
|
| 839 |
+
adding a new one with a new boundary parameter via add_header(). The
|
| 840 |
+
main difference is that using the set_boundary() method preserves the
|
| 841 |
+
order of the Content-Type header in the original message.
|
| 842 |
+
|
| 843 |
+
HeaderParseError is raised if the message has no Content-Type header.
|
| 844 |
+
"""
|
| 845 |
+
missing = object()
|
| 846 |
+
params = self._get_params_preserve(missing, 'content-type')
|
| 847 |
+
if params is missing:
|
| 848 |
+
# There was no Content-Type header, and we don't know what type
|
| 849 |
+
# to set it to, so raise an exception.
|
| 850 |
+
raise errors.HeaderParseError('No Content-Type header found')
|
| 851 |
+
newparams = []
|
| 852 |
+
foundp = False
|
| 853 |
+
for pk, pv in params:
|
| 854 |
+
if pk.lower() == 'boundary':
|
| 855 |
+
newparams.append(('boundary', '"%s"' % boundary))
|
| 856 |
+
foundp = True
|
| 857 |
+
else:
|
| 858 |
+
newparams.append((pk, pv))
|
| 859 |
+
if not foundp:
|
| 860 |
+
# The original Content-Type header had no boundary attribute.
|
| 861 |
+
# Tack one on the end. BAW: should we raise an exception
|
| 862 |
+
# instead???
|
| 863 |
+
newparams.append(('boundary', '"%s"' % boundary))
|
| 864 |
+
# Replace the existing Content-Type header with the new value
|
| 865 |
+
newheaders = []
|
| 866 |
+
for h, v in self._headers:
|
| 867 |
+
if h.lower() == 'content-type':
|
| 868 |
+
parts = []
|
| 869 |
+
for k, v in newparams:
|
| 870 |
+
if v == '':
|
| 871 |
+
parts.append(k)
|
| 872 |
+
else:
|
| 873 |
+
parts.append('%s=%s' % (k, v))
|
| 874 |
+
val = SEMISPACE.join(parts)
|
| 875 |
+
newheaders.append(self.policy.header_store_parse(h, val))
|
| 876 |
+
|
| 877 |
+
else:
|
| 878 |
+
newheaders.append((h, v))
|
| 879 |
+
self._headers = newheaders
|
| 880 |
+
|
| 881 |
+
def get_content_charset(self, failobj=None):
|
| 882 |
+
"""Return the charset parameter of the Content-Type header.
|
| 883 |
+
|
| 884 |
+
The returned string is always coerced to lower case. If there is no
|
| 885 |
+
Content-Type header, or if that header has no charset parameter,
|
| 886 |
+
failobj is returned.
|
| 887 |
+
"""
|
| 888 |
+
missing = object()
|
| 889 |
+
charset = self.get_param('charset', missing)
|
| 890 |
+
if charset is missing:
|
| 891 |
+
return failobj
|
| 892 |
+
if isinstance(charset, tuple):
|
| 893 |
+
# RFC 2231 encoded, so decode it, and it better end up as ascii.
|
| 894 |
+
pcharset = charset[0] or 'us-ascii'
|
| 895 |
+
try:
|
| 896 |
+
# LookupError will be raised if the charset isn't known to
|
| 897 |
+
# Python. UnicodeError will be raised if the encoded text
|
| 898 |
+
# contains a character not in the charset.
|
| 899 |
+
as_bytes = charset[2].encode('raw-unicode-escape')
|
| 900 |
+
charset = str(as_bytes, pcharset)
|
| 901 |
+
except (LookupError, UnicodeError):
|
| 902 |
+
charset = charset[2]
|
| 903 |
+
# charset characters must be in us-ascii range
|
| 904 |
+
try:
|
| 905 |
+
charset.encode('us-ascii')
|
| 906 |
+
except UnicodeError:
|
| 907 |
+
return failobj
|
| 908 |
+
# RFC 2046, $4.1.2 says charsets are not case sensitive
|
| 909 |
+
return charset.lower()
|
| 910 |
+
|
| 911 |
+
def get_charsets(self, failobj=None):
|
| 912 |
+
"""Return a list containing the charset(s) used in this message.
|
| 913 |
+
|
| 914 |
+
The returned list of items describes the Content-Type headers'
|
| 915 |
+
charset parameter for this message and all the subparts in its
|
| 916 |
+
payload.
|
| 917 |
+
|
| 918 |
+
Each item will either be a string (the value of the charset parameter
|
| 919 |
+
in the Content-Type header of that part) or the value of the
|
| 920 |
+
'failobj' parameter (defaults to None), if the part does not have a
|
| 921 |
+
main MIME type of "text", or the charset is not defined.
|
| 922 |
+
|
| 923 |
+
The list will contain one string for each part of the message, plus
|
| 924 |
+
one for the container message (i.e. self), so that a non-multipart
|
| 925 |
+
message will still return a list of length 1.
|
| 926 |
+
"""
|
| 927 |
+
return [part.get_content_charset(failobj) for part in self.walk()]
|
| 928 |
+
|
| 929 |
+
def get_content_disposition(self):
|
| 930 |
+
"""Return the message's content-disposition if it exists, or None.
|
| 931 |
+
|
| 932 |
+
The return values can be either 'inline', 'attachment' or None
|
| 933 |
+
according to the rfc2183.
|
| 934 |
+
"""
|
| 935 |
+
value = self.get('content-disposition')
|
| 936 |
+
if value is None:
|
| 937 |
+
return None
|
| 938 |
+
c_d = _splitparam(value)[0].lower()
|
| 939 |
+
return c_d
|
| 940 |
+
|
| 941 |
+
# I.e. def walk(self): ...
|
| 942 |
+
from email.iterators import walk
|
| 943 |
+
|
| 944 |
+
|
| 945 |
+
class MIMEPart(Message):
|
| 946 |
+
|
| 947 |
+
def __init__(self, policy=None):
|
| 948 |
+
if policy is None:
|
| 949 |
+
from email.policy import default
|
| 950 |
+
policy = default
|
| 951 |
+
Message.__init__(self, policy)
|
| 952 |
+
|
| 953 |
+
|
| 954 |
+
def as_string(self, unixfrom=False, maxheaderlen=None, policy=None):
|
| 955 |
+
"""Return the entire formatted message as a string.
|
| 956 |
+
|
| 957 |
+
Optional 'unixfrom', when true, means include the Unix From_ envelope
|
| 958 |
+
header. maxheaderlen is retained for backward compatibility with the
|
| 959 |
+
base Message class, but defaults to None, meaning that the policy value
|
| 960 |
+
for max_line_length controls the header maximum length. 'policy' is
|
| 961 |
+
passed to the Generator instance used to serialize the message; if it
|
| 962 |
+
is not specified the policy associated with the message instance is
|
| 963 |
+
used.
|
| 964 |
+
"""
|
| 965 |
+
policy = self.policy if policy is None else policy
|
| 966 |
+
if maxheaderlen is None:
|
| 967 |
+
maxheaderlen = policy.max_line_length
|
| 968 |
+
return super().as_string(maxheaderlen=maxheaderlen, policy=policy)
|
| 969 |
+
|
| 970 |
+
def __str__(self):
|
| 971 |
+
return self.as_string(policy=self.policy.clone(utf8=True))
|
| 972 |
+
|
| 973 |
+
def is_attachment(self):
|
| 974 |
+
c_d = self.get('content-disposition')
|
| 975 |
+
return False if c_d is None else c_d.content_disposition == 'attachment'
|
| 976 |
+
|
| 977 |
+
def _find_body(self, part, preferencelist):
|
| 978 |
+
if part.is_attachment():
|
| 979 |
+
return
|
| 980 |
+
maintype, subtype = part.get_content_type().split('/')
|
| 981 |
+
if maintype == 'text':
|
| 982 |
+
if subtype in preferencelist:
|
| 983 |
+
yield (preferencelist.index(subtype), part)
|
| 984 |
+
return
|
| 985 |
+
if maintype != 'multipart':
|
| 986 |
+
return
|
| 987 |
+
if subtype != 'related':
|
| 988 |
+
for subpart in part.iter_parts():
|
| 989 |
+
yield from self._find_body(subpart, preferencelist)
|
| 990 |
+
return
|
| 991 |
+
if 'related' in preferencelist:
|
| 992 |
+
yield (preferencelist.index('related'), part)
|
| 993 |
+
candidate = None
|
| 994 |
+
start = part.get_param('start')
|
| 995 |
+
if start:
|
| 996 |
+
for subpart in part.iter_parts():
|
| 997 |
+
if subpart['content-id'] == start:
|
| 998 |
+
candidate = subpart
|
| 999 |
+
break
|
| 1000 |
+
if candidate is None:
|
| 1001 |
+
subparts = part.get_payload()
|
| 1002 |
+
candidate = subparts[0] if subparts else None
|
| 1003 |
+
if candidate is not None:
|
| 1004 |
+
yield from self._find_body(candidate, preferencelist)
|
| 1005 |
+
|
| 1006 |
+
def get_body(self, preferencelist=('related', 'html', 'plain')):
|
| 1007 |
+
"""Return best candidate mime part for display as 'body' of message.
|
| 1008 |
+
|
| 1009 |
+
Do a depth first search, starting with self, looking for the first part
|
| 1010 |
+
matching each of the items in preferencelist, and return the part
|
| 1011 |
+
corresponding to the first item that has a match, or None if no items
|
| 1012 |
+
have a match. If 'related' is not included in preferencelist, consider
|
| 1013 |
+
the root part of any multipart/related encountered as a candidate
|
| 1014 |
+
match. Ignore parts with 'Content-Disposition: attachment'.
|
| 1015 |
+
"""
|
| 1016 |
+
best_prio = len(preferencelist)
|
| 1017 |
+
body = None
|
| 1018 |
+
for prio, part in self._find_body(self, preferencelist):
|
| 1019 |
+
if prio < best_prio:
|
| 1020 |
+
best_prio = prio
|
| 1021 |
+
body = part
|
| 1022 |
+
if prio == 0:
|
| 1023 |
+
break
|
| 1024 |
+
return body
|
| 1025 |
+
|
| 1026 |
+
_body_types = {('text', 'plain'),
|
| 1027 |
+
('text', 'html'),
|
| 1028 |
+
('multipart', 'related'),
|
| 1029 |
+
('multipart', 'alternative')}
|
| 1030 |
+
def iter_attachments(self):
|
| 1031 |
+
"""Return an iterator over the non-main parts of a multipart.
|
| 1032 |
+
|
| 1033 |
+
Skip the first of each occurrence of text/plain, text/html,
|
| 1034 |
+
multipart/related, or multipart/alternative in the multipart (unless
|
| 1035 |
+
they have a 'Content-Disposition: attachment' header) and include all
|
| 1036 |
+
remaining subparts in the returned iterator. When applied to a
|
| 1037 |
+
multipart/related, return all parts except the root part. Return an
|
| 1038 |
+
empty iterator when applied to a multipart/alternative or a
|
| 1039 |
+
non-multipart.
|
| 1040 |
+
"""
|
| 1041 |
+
maintype, subtype = self.get_content_type().split('/')
|
| 1042 |
+
if maintype != 'multipart' or subtype == 'alternative':
|
| 1043 |
+
return
|
| 1044 |
+
payload = self.get_payload()
|
| 1045 |
+
# Certain malformed messages can have content type set to `multipart/*`
|
| 1046 |
+
# but still have single part body, in which case payload.copy() can
|
| 1047 |
+
# fail with AttributeError.
|
| 1048 |
+
try:
|
| 1049 |
+
parts = payload.copy()
|
| 1050 |
+
except AttributeError:
|
| 1051 |
+
# payload is not a list, it is most probably a string.
|
| 1052 |
+
return
|
| 1053 |
+
|
| 1054 |
+
if maintype == 'multipart' and subtype == 'related':
|
| 1055 |
+
# For related, we treat everything but the root as an attachment.
|
| 1056 |
+
# The root may be indicated by 'start'; if there's no start or we
|
| 1057 |
+
# can't find the named start, treat the first subpart as the root.
|
| 1058 |
+
start = self.get_param('start')
|
| 1059 |
+
if start:
|
| 1060 |
+
found = False
|
| 1061 |
+
attachments = []
|
| 1062 |
+
for part in parts:
|
| 1063 |
+
if part.get('content-id') == start:
|
| 1064 |
+
found = True
|
| 1065 |
+
else:
|
| 1066 |
+
attachments.append(part)
|
| 1067 |
+
if found:
|
| 1068 |
+
yield from attachments
|
| 1069 |
+
return
|
| 1070 |
+
parts.pop(0)
|
| 1071 |
+
yield from parts
|
| 1072 |
+
return
|
| 1073 |
+
# Otherwise we more or less invert the remaining logic in get_body.
|
| 1074 |
+
# This only really works in edge cases (ex: non-text related or
|
| 1075 |
+
# alternatives) if the sending agent sets content-disposition.
|
| 1076 |
+
seen = [] # Only skip the first example of each candidate type.
|
| 1077 |
+
for part in parts:
|
| 1078 |
+
maintype, subtype = part.get_content_type().split('/')
|
| 1079 |
+
if ((maintype, subtype) in self._body_types and
|
| 1080 |
+
not part.is_attachment() and subtype not in seen):
|
| 1081 |
+
seen.append(subtype)
|
| 1082 |
+
continue
|
| 1083 |
+
yield part
|
| 1084 |
+
|
| 1085 |
+
def iter_parts(self):
|
| 1086 |
+
"""Return an iterator over all immediate subparts of a multipart.
|
| 1087 |
+
|
| 1088 |
+
Return an empty iterator for a non-multipart.
|
| 1089 |
+
"""
|
| 1090 |
+
if self.get_content_maintype() == 'multipart':
|
| 1091 |
+
yield from self.get_payload()
|
| 1092 |
+
|
| 1093 |
+
def get_content(self, *args, content_manager=None, **kw):
|
| 1094 |
+
if content_manager is None:
|
| 1095 |
+
content_manager = self.policy.content_manager
|
| 1096 |
+
return content_manager.get_content(self, *args, **kw)
|
| 1097 |
+
|
| 1098 |
+
def set_content(self, *args, content_manager=None, **kw):
|
| 1099 |
+
if content_manager is None:
|
| 1100 |
+
content_manager = self.policy.content_manager
|
| 1101 |
+
content_manager.set_content(self, *args, **kw)
|
| 1102 |
+
|
| 1103 |
+
def _make_multipart(self, subtype, disallowed_subtypes, boundary):
|
| 1104 |
+
if self.get_content_maintype() == 'multipart':
|
| 1105 |
+
existing_subtype = self.get_content_subtype()
|
| 1106 |
+
disallowed_subtypes = disallowed_subtypes + (subtype,)
|
| 1107 |
+
if existing_subtype in disallowed_subtypes:
|
| 1108 |
+
raise ValueError("Cannot convert {} to {}".format(
|
| 1109 |
+
existing_subtype, subtype))
|
| 1110 |
+
keep_headers = []
|
| 1111 |
+
part_headers = []
|
| 1112 |
+
for name, value in self._headers:
|
| 1113 |
+
if name.lower().startswith('content-'):
|
| 1114 |
+
part_headers.append((name, value))
|
| 1115 |
+
else:
|
| 1116 |
+
keep_headers.append((name, value))
|
| 1117 |
+
if part_headers:
|
| 1118 |
+
# There is existing content, move it to the first subpart.
|
| 1119 |
+
part = type(self)(policy=self.policy)
|
| 1120 |
+
part._headers = part_headers
|
| 1121 |
+
part._payload = self._payload
|
| 1122 |
+
self._payload = [part]
|
| 1123 |
+
else:
|
| 1124 |
+
self._payload = []
|
| 1125 |
+
self._headers = keep_headers
|
| 1126 |
+
self['Content-Type'] = 'multipart/' + subtype
|
| 1127 |
+
if boundary is not None:
|
| 1128 |
+
self.set_param('boundary', boundary)
|
| 1129 |
+
|
| 1130 |
+
def make_related(self, boundary=None):
|
| 1131 |
+
self._make_multipart('related', ('alternative', 'mixed'), boundary)
|
| 1132 |
+
|
| 1133 |
+
def make_alternative(self, boundary=None):
|
| 1134 |
+
self._make_multipart('alternative', ('mixed',), boundary)
|
| 1135 |
+
|
| 1136 |
+
def make_mixed(self, boundary=None):
|
| 1137 |
+
self._make_multipart('mixed', (), boundary)
|
| 1138 |
+
|
| 1139 |
+
def _add_multipart(self, _subtype, *args, _disp=None, **kw):
|
| 1140 |
+
if (self.get_content_maintype() != 'multipart' or
|
| 1141 |
+
self.get_content_subtype() != _subtype):
|
| 1142 |
+
getattr(self, 'make_' + _subtype)()
|
| 1143 |
+
part = type(self)(policy=self.policy)
|
| 1144 |
+
part.set_content(*args, **kw)
|
| 1145 |
+
if _disp and 'content-disposition' not in part:
|
| 1146 |
+
part['Content-Disposition'] = _disp
|
| 1147 |
+
self.attach(part)
|
| 1148 |
+
|
| 1149 |
+
def add_related(self, *args, **kw):
|
| 1150 |
+
self._add_multipart('related', *args, _disp='inline', **kw)
|
| 1151 |
+
|
| 1152 |
+
def add_alternative(self, *args, **kw):
|
| 1153 |
+
self._add_multipart('alternative', *args, **kw)
|
| 1154 |
+
|
| 1155 |
+
def add_attachment(self, *args, **kw):
|
| 1156 |
+
self._add_multipart('mixed', *args, _disp='attachment', **kw)
|
| 1157 |
+
|
| 1158 |
+
def clear(self):
|
| 1159 |
+
self._headers = []
|
| 1160 |
+
self._payload = None
|
| 1161 |
+
|
| 1162 |
+
def clear_content(self):
|
| 1163 |
+
self._headers = [(n, v) for n, v in self._headers
|
| 1164 |
+
if not n.lower().startswith('content-')]
|
| 1165 |
+
self._payload = None
|
| 1166 |
+
|
| 1167 |
+
|
| 1168 |
+
class EmailMessage(MIMEPart):
|
| 1169 |
+
|
| 1170 |
+
def set_content(self, *args, **kw):
|
| 1171 |
+
super().set_content(*args, **kw)
|
| 1172 |
+
if 'MIME-Version' not in self:
|
| 1173 |
+
self['MIME-Version'] = '1.0'
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/email/parser.py
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (C) 2001-2007 Python Software Foundation
|
| 2 |
+
# Author: Barry Warsaw, Thomas Wouters, Anthony Baxter
|
| 3 |
+
# Contact: email-sig@python.org
|
| 4 |
+
|
| 5 |
+
"""A parser of RFC 2822 and MIME email messages."""
|
| 6 |
+
|
| 7 |
+
__all__ = ['Parser', 'HeaderParser', 'BytesParser', 'BytesHeaderParser',
|
| 8 |
+
'FeedParser', 'BytesFeedParser']
|
| 9 |
+
|
| 10 |
+
from io import StringIO, TextIOWrapper
|
| 11 |
+
|
| 12 |
+
from email.feedparser import FeedParser, BytesFeedParser
|
| 13 |
+
from email._policybase import compat32
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class Parser:
|
| 17 |
+
def __init__(self, _class=None, *, policy=compat32):
|
| 18 |
+
"""Parser of RFC 2822 and MIME email messages.
|
| 19 |
+
|
| 20 |
+
Creates an in-memory object tree representing the email message, which
|
| 21 |
+
can then be manipulated and turned over to a Generator to return the
|
| 22 |
+
textual representation of the message.
|
| 23 |
+
|
| 24 |
+
The string must be formatted as a block of RFC 2822 headers and header
|
| 25 |
+
continuation lines, optionally preceded by a `Unix-from' header. The
|
| 26 |
+
header block is terminated either by the end of the string or by a
|
| 27 |
+
blank line.
|
| 28 |
+
|
| 29 |
+
_class is the class to instantiate for new message objects when they
|
| 30 |
+
must be created. This class must have a constructor that can take
|
| 31 |
+
zero arguments. Default is Message.Message.
|
| 32 |
+
|
| 33 |
+
The policy keyword specifies a policy object that controls a number of
|
| 34 |
+
aspects of the parser's operation. The default policy maintains
|
| 35 |
+
backward compatibility.
|
| 36 |
+
|
| 37 |
+
"""
|
| 38 |
+
self._class = _class
|
| 39 |
+
self.policy = policy
|
| 40 |
+
|
| 41 |
+
def parse(self, fp, headersonly=False):
|
| 42 |
+
"""Create a message structure from the data in a file.
|
| 43 |
+
|
| 44 |
+
Reads all the data from the file and returns the root of the message
|
| 45 |
+
structure. Optional headersonly is a flag specifying whether to stop
|
| 46 |
+
parsing after reading the headers or not. The default is False,
|
| 47 |
+
meaning it parses the entire contents of the file.
|
| 48 |
+
"""
|
| 49 |
+
feedparser = FeedParser(self._class, policy=self.policy)
|
| 50 |
+
if headersonly:
|
| 51 |
+
feedparser._set_headersonly()
|
| 52 |
+
while True:
|
| 53 |
+
data = fp.read(8192)
|
| 54 |
+
if not data:
|
| 55 |
+
break
|
| 56 |
+
feedparser.feed(data)
|
| 57 |
+
return feedparser.close()
|
| 58 |
+
|
| 59 |
+
def parsestr(self, text, headersonly=False):
|
| 60 |
+
"""Create a message structure from a string.
|
| 61 |
+
|
| 62 |
+
Returns the root of the message structure. Optional headersonly is a
|
| 63 |
+
flag specifying whether to stop parsing after reading the headers or
|
| 64 |
+
not. The default is False, meaning it parses the entire contents of
|
| 65 |
+
the file.
|
| 66 |
+
"""
|
| 67 |
+
return self.parse(StringIO(text), headersonly=headersonly)
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
class HeaderParser(Parser):
|
| 72 |
+
def parse(self, fp, headersonly=True):
|
| 73 |
+
return Parser.parse(self, fp, True)
|
| 74 |
+
|
| 75 |
+
def parsestr(self, text, headersonly=True):
|
| 76 |
+
return Parser.parsestr(self, text, True)
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
class BytesParser:
|
| 80 |
+
|
| 81 |
+
def __init__(self, *args, **kw):
|
| 82 |
+
"""Parser of binary RFC 2822 and MIME email messages.
|
| 83 |
+
|
| 84 |
+
Creates an in-memory object tree representing the email message, which
|
| 85 |
+
can then be manipulated and turned over to a Generator to return the
|
| 86 |
+
textual representation of the message.
|
| 87 |
+
|
| 88 |
+
The input must be formatted as a block of RFC 2822 headers and header
|
| 89 |
+
continuation lines, optionally preceded by a `Unix-from' header. The
|
| 90 |
+
header block is terminated either by the end of the input or by a
|
| 91 |
+
blank line.
|
| 92 |
+
|
| 93 |
+
_class is the class to instantiate for new message objects when they
|
| 94 |
+
must be created. This class must have a constructor that can take
|
| 95 |
+
zero arguments. Default is Message.Message.
|
| 96 |
+
"""
|
| 97 |
+
self.parser = Parser(*args, **kw)
|
| 98 |
+
|
| 99 |
+
def parse(self, fp, headersonly=False):
|
| 100 |
+
"""Create a message structure from the data in a binary file.
|
| 101 |
+
|
| 102 |
+
Reads all the data from the file and returns the root of the message
|
| 103 |
+
structure. Optional headersonly is a flag specifying whether to stop
|
| 104 |
+
parsing after reading the headers or not. The default is False,
|
| 105 |
+
meaning it parses the entire contents of the file.
|
| 106 |
+
"""
|
| 107 |
+
fp = TextIOWrapper(fp, encoding='ascii', errors='surrogateescape')
|
| 108 |
+
try:
|
| 109 |
+
return self.parser.parse(fp, headersonly)
|
| 110 |
+
finally:
|
| 111 |
+
fp.detach()
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
def parsebytes(self, text, headersonly=False):
|
| 115 |
+
"""Create a message structure from a byte string.
|
| 116 |
+
|
| 117 |
+
Returns the root of the message structure. Optional headersonly is a
|
| 118 |
+
flag specifying whether to stop parsing after reading the headers or
|
| 119 |
+
not. The default is False, meaning it parses the entire contents of
|
| 120 |
+
the file.
|
| 121 |
+
"""
|
| 122 |
+
text = text.decode('ASCII', errors='surrogateescape')
|
| 123 |
+
return self.parser.parsestr(text, headersonly)
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
class BytesHeaderParser(BytesParser):
|
| 127 |
+
def parse(self, fp, headersonly=True):
|
| 128 |
+
return BytesParser.parse(self, fp, headersonly=True)
|
| 129 |
+
|
| 130 |
+
def parsebytes(self, text, headersonly=True):
|
| 131 |
+
return BytesParser.parsebytes(self, text, headersonly=True)
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/email/policy.py
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""This will be the home for the policy that hooks in the new
|
| 2 |
+
code that adds all the email6 features.
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
import re
|
| 6 |
+
import sys
|
| 7 |
+
from email._policybase import Policy, Compat32, compat32, _extend_docstrings
|
| 8 |
+
from email.utils import _has_surrogates
|
| 9 |
+
from email.headerregistry import HeaderRegistry as HeaderRegistry
|
| 10 |
+
from email.contentmanager import raw_data_manager
|
| 11 |
+
from email.message import EmailMessage
|
| 12 |
+
|
| 13 |
+
__all__ = [
|
| 14 |
+
'Compat32',
|
| 15 |
+
'compat32',
|
| 16 |
+
'Policy',
|
| 17 |
+
'EmailPolicy',
|
| 18 |
+
'default',
|
| 19 |
+
'strict',
|
| 20 |
+
'SMTP',
|
| 21 |
+
'HTTP',
|
| 22 |
+
]
|
| 23 |
+
|
| 24 |
+
linesep_splitter = re.compile(r'\n|\r')
|
| 25 |
+
|
| 26 |
+
@_extend_docstrings
|
| 27 |
+
class EmailPolicy(Policy):
|
| 28 |
+
|
| 29 |
+
"""+
|
| 30 |
+
PROVISIONAL
|
| 31 |
+
|
| 32 |
+
The API extensions enabled by this policy are currently provisional.
|
| 33 |
+
Refer to the documentation for details.
|
| 34 |
+
|
| 35 |
+
This policy adds new header parsing and folding algorithms. Instead of
|
| 36 |
+
simple strings, headers are custom objects with custom attributes
|
| 37 |
+
depending on the type of the field. The folding algorithm fully
|
| 38 |
+
implements RFCs 2047 and 5322.
|
| 39 |
+
|
| 40 |
+
In addition to the settable attributes listed above that apply to
|
| 41 |
+
all Policies, this policy adds the following additional attributes:
|
| 42 |
+
|
| 43 |
+
utf8 -- if False (the default) message headers will be
|
| 44 |
+
serialized as ASCII, using encoded words to encode
|
| 45 |
+
any non-ASCII characters in the source strings. If
|
| 46 |
+
True, the message headers will be serialized using
|
| 47 |
+
utf8 and will not contain encoded words (see RFC
|
| 48 |
+
6532 for more on this serialization format).
|
| 49 |
+
|
| 50 |
+
refold_source -- if the value for a header in the Message object
|
| 51 |
+
came from the parsing of some source, this attribute
|
| 52 |
+
indicates whether or not a generator should refold
|
| 53 |
+
that value when transforming the message back into
|
| 54 |
+
stream form. The possible values are:
|
| 55 |
+
|
| 56 |
+
none -- all source values use original folding
|
| 57 |
+
long -- source values that have any line that is
|
| 58 |
+
longer than max_line_length will be
|
| 59 |
+
refolded
|
| 60 |
+
all -- all values are refolded.
|
| 61 |
+
|
| 62 |
+
The default is 'long'.
|
| 63 |
+
|
| 64 |
+
header_factory -- a callable that takes two arguments, 'name' and
|
| 65 |
+
'value', where 'name' is a header field name and
|
| 66 |
+
'value' is an unfolded header field value, and
|
| 67 |
+
returns a string-like object that represents that
|
| 68 |
+
header. A default header_factory is provided that
|
| 69 |
+
understands some of the RFC5322 header field types.
|
| 70 |
+
(Currently address fields and date fields have
|
| 71 |
+
special treatment, while all other fields are
|
| 72 |
+
treated as unstructured. This list will be
|
| 73 |
+
completed before the extension is marked stable.)
|
| 74 |
+
|
| 75 |
+
content_manager -- an object with at least two methods: get_content
|
| 76 |
+
and set_content. When the get_content or
|
| 77 |
+
set_content method of a Message object is called,
|
| 78 |
+
it calls the corresponding method of this object,
|
| 79 |
+
passing it the message object as its first argument,
|
| 80 |
+
and any arguments or keywords that were passed to
|
| 81 |
+
it as additional arguments. The default
|
| 82 |
+
content_manager is
|
| 83 |
+
:data:`~email.contentmanager.raw_data_manager`.
|
| 84 |
+
|
| 85 |
+
"""
|
| 86 |
+
|
| 87 |
+
message_factory = EmailMessage
|
| 88 |
+
utf8 = False
|
| 89 |
+
refold_source = 'long'
|
| 90 |
+
header_factory = HeaderRegistry()
|
| 91 |
+
content_manager = raw_data_manager
|
| 92 |
+
|
| 93 |
+
def __init__(self, **kw):
|
| 94 |
+
# Ensure that each new instance gets a unique header factory
|
| 95 |
+
# (as opposed to clones, which share the factory).
|
| 96 |
+
if 'header_factory' not in kw:
|
| 97 |
+
object.__setattr__(self, 'header_factory', HeaderRegistry())
|
| 98 |
+
super().__init__(**kw)
|
| 99 |
+
|
| 100 |
+
def header_max_count(self, name):
|
| 101 |
+
"""+
|
| 102 |
+
The implementation for this class returns the max_count attribute from
|
| 103 |
+
the specialized header class that would be used to construct a header
|
| 104 |
+
of type 'name'.
|
| 105 |
+
"""
|
| 106 |
+
return self.header_factory[name].max_count
|
| 107 |
+
|
| 108 |
+
# The logic of the next three methods is chosen such that it is possible to
|
| 109 |
+
# switch a Message object between a Compat32 policy and a policy derived
|
| 110 |
+
# from this class and have the results stay consistent. This allows a
|
| 111 |
+
# Message object constructed with this policy to be passed to a library
|
| 112 |
+
# that only handles Compat32 objects, or to receive such an object and
|
| 113 |
+
# convert it to use the newer style by just changing its policy. It is
|
| 114 |
+
# also chosen because it postpones the relatively expensive full rfc5322
|
| 115 |
+
# parse until as late as possible when parsing from source, since in many
|
| 116 |
+
# applications only a few headers will actually be inspected.
|
| 117 |
+
|
| 118 |
+
def header_source_parse(self, sourcelines):
|
| 119 |
+
"""+
|
| 120 |
+
The name is parsed as everything up to the ':' and returned unmodified.
|
| 121 |
+
The value is determined by stripping leading whitespace off the
|
| 122 |
+
remainder of the first line, joining all subsequent lines together, and
|
| 123 |
+
stripping any trailing carriage return or linefeed characters. (This
|
| 124 |
+
is the same as Compat32).
|
| 125 |
+
|
| 126 |
+
"""
|
| 127 |
+
name, value = sourcelines[0].split(':', 1)
|
| 128 |
+
value = value.lstrip(' \t') + ''.join(sourcelines[1:])
|
| 129 |
+
return (name, value.rstrip('\r\n'))
|
| 130 |
+
|
| 131 |
+
def header_store_parse(self, name, value):
|
| 132 |
+
"""+
|
| 133 |
+
The name is returned unchanged. If the input value has a 'name'
|
| 134 |
+
attribute and it matches the name ignoring case, the value is returned
|
| 135 |
+
unchanged. Otherwise the name and value are passed to header_factory
|
| 136 |
+
method, and the resulting custom header object is returned as the
|
| 137 |
+
value. In this case a ValueError is raised if the input value contains
|
| 138 |
+
CR or LF characters.
|
| 139 |
+
|
| 140 |
+
"""
|
| 141 |
+
if hasattr(value, 'name') and value.name.lower() == name.lower():
|
| 142 |
+
return (name, value)
|
| 143 |
+
if isinstance(value, str) and len(value.splitlines())>1:
|
| 144 |
+
# XXX this error message isn't quite right when we use splitlines
|
| 145 |
+
# (see issue 22233), but I'm not sure what should happen here.
|
| 146 |
+
raise ValueError("Header values may not contain linefeed "
|
| 147 |
+
"or carriage return characters")
|
| 148 |
+
return (name, self.header_factory(name, value))
|
| 149 |
+
|
| 150 |
+
def header_fetch_parse(self, name, value):
|
| 151 |
+
"""+
|
| 152 |
+
If the value has a 'name' attribute, it is returned to unmodified.
|
| 153 |
+
Otherwise the name and the value with any linesep characters removed
|
| 154 |
+
are passed to the header_factory method, and the resulting custom
|
| 155 |
+
header object is returned. Any surrogateescaped bytes get turned
|
| 156 |
+
into the unicode unknown-character glyph.
|
| 157 |
+
|
| 158 |
+
"""
|
| 159 |
+
if hasattr(value, 'name'):
|
| 160 |
+
return value
|
| 161 |
+
# We can't use splitlines here because it splits on more than \r and \n.
|
| 162 |
+
value = ''.join(linesep_splitter.split(value))
|
| 163 |
+
return self.header_factory(name, value)
|
| 164 |
+
|
| 165 |
+
def fold(self, name, value):
|
| 166 |
+
"""+
|
| 167 |
+
Header folding is controlled by the refold_source policy setting. A
|
| 168 |
+
value is considered to be a 'source value' if and only if it does not
|
| 169 |
+
have a 'name' attribute (having a 'name' attribute means it is a header
|
| 170 |
+
object of some sort). If a source value needs to be refolded according
|
| 171 |
+
to the policy, it is converted into a custom header object by passing
|
| 172 |
+
the name and the value with any linesep characters removed to the
|
| 173 |
+
header_factory method. Folding of a custom header object is done by
|
| 174 |
+
calling its fold method with the current policy.
|
| 175 |
+
|
| 176 |
+
Source values are split into lines using splitlines. If the value is
|
| 177 |
+
not to be refolded, the lines are rejoined using the linesep from the
|
| 178 |
+
policy and returned. The exception is lines containing non-ascii
|
| 179 |
+
binary data. In that case the value is refolded regardless of the
|
| 180 |
+
refold_source setting, which causes the binary data to be CTE encoded
|
| 181 |
+
using the unknown-8bit charset.
|
| 182 |
+
|
| 183 |
+
"""
|
| 184 |
+
return self._fold(name, value, refold_binary=True)
|
| 185 |
+
|
| 186 |
+
def fold_binary(self, name, value):
|
| 187 |
+
"""+
|
| 188 |
+
The same as fold if cte_type is 7bit, except that the returned value is
|
| 189 |
+
bytes.
|
| 190 |
+
|
| 191 |
+
If cte_type is 8bit, non-ASCII binary data is converted back into
|
| 192 |
+
bytes. Headers with binary data are not refolded, regardless of the
|
| 193 |
+
refold_header setting, since there is no way to know whether the binary
|
| 194 |
+
data consists of single byte characters or multibyte characters.
|
| 195 |
+
|
| 196 |
+
If utf8 is true, headers are encoded to utf8, otherwise to ascii with
|
| 197 |
+
non-ASCII unicode rendered as encoded words.
|
| 198 |
+
|
| 199 |
+
"""
|
| 200 |
+
folded = self._fold(name, value, refold_binary=self.cte_type=='7bit')
|
| 201 |
+
charset = 'utf8' if self.utf8 else 'ascii'
|
| 202 |
+
return folded.encode(charset, 'surrogateescape')
|
| 203 |
+
|
| 204 |
+
def _fold(self, name, value, refold_binary=False):
|
| 205 |
+
if hasattr(value, 'name'):
|
| 206 |
+
return value.fold(policy=self)
|
| 207 |
+
maxlen = self.max_line_length if self.max_line_length else sys.maxsize
|
| 208 |
+
lines = value.splitlines()
|
| 209 |
+
refold = (self.refold_source == 'all' or
|
| 210 |
+
self.refold_source == 'long' and
|
| 211 |
+
(lines and len(lines[0])+len(name)+2 > maxlen or
|
| 212 |
+
any(len(x) > maxlen for x in lines[1:])))
|
| 213 |
+
if refold or refold_binary and _has_surrogates(value):
|
| 214 |
+
return self.header_factory(name, ''.join(lines)).fold(policy=self)
|
| 215 |
+
return name + ': ' + self.linesep.join(lines) + self.linesep
|
| 216 |
+
|
| 217 |
+
|
| 218 |
+
default = EmailPolicy()
|
| 219 |
+
# Make the default policy use the class default header_factory
|
| 220 |
+
del default.header_factory
|
| 221 |
+
strict = default.clone(raise_on_defect=True)
|
| 222 |
+
SMTP = default.clone(linesep='\r\n')
|
| 223 |
+
HTTP = default.clone(linesep='\r\n', max_line_length=None)
|
| 224 |
+
SMTPUTF8 = SMTP.clone(utf8=True)
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/email/quoprimime.py
ADDED
|
@@ -0,0 +1,299 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (C) 2001-2006 Python Software Foundation
|
| 2 |
+
# Author: Ben Gertzfield
|
| 3 |
+
# Contact: email-sig@python.org
|
| 4 |
+
|
| 5 |
+
"""Quoted-printable content transfer encoding per RFCs 2045-2047.
|
| 6 |
+
|
| 7 |
+
This module handles the content transfer encoding method defined in RFC 2045
|
| 8 |
+
to encode US ASCII-like 8-bit data called `quoted-printable'. It is used to
|
| 9 |
+
safely encode text that is in a character set similar to the 7-bit US ASCII
|
| 10 |
+
character set, but that includes some 8-bit characters that are normally not
|
| 11 |
+
allowed in email bodies or headers.
|
| 12 |
+
|
| 13 |
+
Quoted-printable is very space-inefficient for encoding binary files; use the
|
| 14 |
+
email.base64mime module for that instead.
|
| 15 |
+
|
| 16 |
+
This module provides an interface to encode and decode both headers and bodies
|
| 17 |
+
with quoted-printable encoding.
|
| 18 |
+
|
| 19 |
+
RFC 2045 defines a method for including character set information in an
|
| 20 |
+
`encoded-word' in a header. This method is commonly used for 8-bit real names
|
| 21 |
+
in To:/From:/Cc: etc. fields, as well as Subject: lines.
|
| 22 |
+
|
| 23 |
+
This module does not do the line wrapping or end-of-line character
|
| 24 |
+
conversion necessary for proper internationalized headers; it only
|
| 25 |
+
does dumb encoding and decoding. To deal with the various line
|
| 26 |
+
wrapping issues, use the email.header module.
|
| 27 |
+
"""
|
| 28 |
+
|
| 29 |
+
__all__ = [
|
| 30 |
+
'body_decode',
|
| 31 |
+
'body_encode',
|
| 32 |
+
'body_length',
|
| 33 |
+
'decode',
|
| 34 |
+
'decodestring',
|
| 35 |
+
'header_decode',
|
| 36 |
+
'header_encode',
|
| 37 |
+
'header_length',
|
| 38 |
+
'quote',
|
| 39 |
+
'unquote',
|
| 40 |
+
]
|
| 41 |
+
|
| 42 |
+
import re
|
| 43 |
+
|
| 44 |
+
from string import ascii_letters, digits, hexdigits
|
| 45 |
+
|
| 46 |
+
CRLF = '\r\n'
|
| 47 |
+
NL = '\n'
|
| 48 |
+
EMPTYSTRING = ''
|
| 49 |
+
|
| 50 |
+
# Build a mapping of octets to the expansion of that octet. Since we're only
|
| 51 |
+
# going to have 256 of these things, this isn't terribly inefficient
|
| 52 |
+
# space-wise. Remember that headers and bodies have different sets of safe
|
| 53 |
+
# characters. Initialize both maps with the full expansion, and then override
|
| 54 |
+
# the safe bytes with the more compact form.
|
| 55 |
+
_QUOPRI_MAP = ['=%02X' % c for c in range(256)]
|
| 56 |
+
_QUOPRI_HEADER_MAP = _QUOPRI_MAP[:]
|
| 57 |
+
_QUOPRI_BODY_MAP = _QUOPRI_MAP[:]
|
| 58 |
+
|
| 59 |
+
# Safe header bytes which need no encoding.
|
| 60 |
+
for c in b'-!*+/' + ascii_letters.encode('ascii') + digits.encode('ascii'):
|
| 61 |
+
_QUOPRI_HEADER_MAP[c] = chr(c)
|
| 62 |
+
# Headers have one other special encoding; spaces become underscores.
|
| 63 |
+
_QUOPRI_HEADER_MAP[ord(' ')] = '_'
|
| 64 |
+
|
| 65 |
+
# Safe body bytes which need no encoding.
|
| 66 |
+
for c in (b' !"#$%&\'()*+,-./0123456789:;<>'
|
| 67 |
+
b'?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`'
|
| 68 |
+
b'abcdefghijklmnopqrstuvwxyz{|}~\t'):
|
| 69 |
+
_QUOPRI_BODY_MAP[c] = chr(c)
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
# Helpers
|
| 74 |
+
def header_check(octet):
|
| 75 |
+
"""Return True if the octet should be escaped with header quopri."""
|
| 76 |
+
return chr(octet) != _QUOPRI_HEADER_MAP[octet]
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
def body_check(octet):
|
| 80 |
+
"""Return True if the octet should be escaped with body quopri."""
|
| 81 |
+
return chr(octet) != _QUOPRI_BODY_MAP[octet]
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
def header_length(bytearray):
|
| 85 |
+
"""Return a header quoted-printable encoding length.
|
| 86 |
+
|
| 87 |
+
Note that this does not include any RFC 2047 chrome added by
|
| 88 |
+
`header_encode()`.
|
| 89 |
+
|
| 90 |
+
:param bytearray: An array of bytes (a.k.a. octets).
|
| 91 |
+
:return: The length in bytes of the byte array when it is encoded with
|
| 92 |
+
quoted-printable for headers.
|
| 93 |
+
"""
|
| 94 |
+
return sum(len(_QUOPRI_HEADER_MAP[octet]) for octet in bytearray)
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
def body_length(bytearray):
|
| 98 |
+
"""Return a body quoted-printable encoding length.
|
| 99 |
+
|
| 100 |
+
:param bytearray: An array of bytes (a.k.a. octets).
|
| 101 |
+
:return: The length in bytes of the byte array when it is encoded with
|
| 102 |
+
quoted-printable for bodies.
|
| 103 |
+
"""
|
| 104 |
+
return sum(len(_QUOPRI_BODY_MAP[octet]) for octet in bytearray)
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
def _max_append(L, s, maxlen, extra=''):
|
| 108 |
+
if not isinstance(s, str):
|
| 109 |
+
s = chr(s)
|
| 110 |
+
if not L:
|
| 111 |
+
L.append(s.lstrip())
|
| 112 |
+
elif len(L[-1]) + len(s) <= maxlen:
|
| 113 |
+
L[-1] += extra + s
|
| 114 |
+
else:
|
| 115 |
+
L.append(s.lstrip())
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
def unquote(s):
|
| 119 |
+
"""Turn a string in the form =AB to the ASCII character with value 0xab"""
|
| 120 |
+
return chr(int(s[1:3], 16))
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
def quote(c):
|
| 124 |
+
return _QUOPRI_MAP[ord(c)]
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
def header_encode(header_bytes, charset='iso-8859-1'):
|
| 128 |
+
"""Encode a single header line with quoted-printable (like) encoding.
|
| 129 |
+
|
| 130 |
+
Defined in RFC 2045, this `Q' encoding is similar to quoted-printable, but
|
| 131 |
+
used specifically for email header fields to allow charsets with mostly 7
|
| 132 |
+
bit characters (and some 8 bit) to remain more or less readable in non-RFC
|
| 133 |
+
2045 aware mail clients.
|
| 134 |
+
|
| 135 |
+
charset names the character set to use in the RFC 2046 header. It
|
| 136 |
+
defaults to iso-8859-1.
|
| 137 |
+
"""
|
| 138 |
+
# Return empty headers as an empty string.
|
| 139 |
+
if not header_bytes:
|
| 140 |
+
return ''
|
| 141 |
+
# Iterate over every byte, encoding if necessary.
|
| 142 |
+
encoded = header_bytes.decode('latin1').translate(_QUOPRI_HEADER_MAP)
|
| 143 |
+
# Now add the RFC chrome to each encoded chunk and glue the chunks
|
| 144 |
+
# together.
|
| 145 |
+
return '=?%s?q?%s?=' % (charset, encoded)
|
| 146 |
+
|
| 147 |
+
|
| 148 |
+
_QUOPRI_BODY_ENCODE_MAP = _QUOPRI_BODY_MAP[:]
|
| 149 |
+
for c in b'\r\n':
|
| 150 |
+
_QUOPRI_BODY_ENCODE_MAP[c] = chr(c)
|
| 151 |
+
|
| 152 |
+
def body_encode(body, maxlinelen=76, eol=NL):
|
| 153 |
+
"""Encode with quoted-printable, wrapping at maxlinelen characters.
|
| 154 |
+
|
| 155 |
+
Each line of encoded text will end with eol, which defaults to "\\n". Set
|
| 156 |
+
this to "\\r\\n" if you will be using the result of this function directly
|
| 157 |
+
in an email.
|
| 158 |
+
|
| 159 |
+
Each line will be wrapped at, at most, maxlinelen characters before the
|
| 160 |
+
eol string (maxlinelen defaults to 76 characters, the maximum value
|
| 161 |
+
permitted by RFC 2045). Long lines will have the 'soft line break'
|
| 162 |
+
quoted-printable character "=" appended to them, so the decoded text will
|
| 163 |
+
be identical to the original text.
|
| 164 |
+
|
| 165 |
+
The minimum maxlinelen is 4 to have room for a quoted character ("=XX")
|
| 166 |
+
followed by a soft line break. Smaller values will generate a
|
| 167 |
+
ValueError.
|
| 168 |
+
|
| 169 |
+
"""
|
| 170 |
+
|
| 171 |
+
if maxlinelen < 4:
|
| 172 |
+
raise ValueError("maxlinelen must be at least 4")
|
| 173 |
+
if not body:
|
| 174 |
+
return body
|
| 175 |
+
|
| 176 |
+
# quote special characters
|
| 177 |
+
body = body.translate(_QUOPRI_BODY_ENCODE_MAP)
|
| 178 |
+
|
| 179 |
+
soft_break = '=' + eol
|
| 180 |
+
# leave space for the '=' at the end of a line
|
| 181 |
+
maxlinelen1 = maxlinelen - 1
|
| 182 |
+
|
| 183 |
+
encoded_body = []
|
| 184 |
+
append = encoded_body.append
|
| 185 |
+
|
| 186 |
+
for line in body.splitlines():
|
| 187 |
+
# break up the line into pieces no longer than maxlinelen - 1
|
| 188 |
+
start = 0
|
| 189 |
+
laststart = len(line) - 1 - maxlinelen
|
| 190 |
+
while start <= laststart:
|
| 191 |
+
stop = start + maxlinelen1
|
| 192 |
+
# make sure we don't break up an escape sequence
|
| 193 |
+
if line[stop - 2] == '=':
|
| 194 |
+
append(line[start:stop - 1])
|
| 195 |
+
start = stop - 2
|
| 196 |
+
elif line[stop - 1] == '=':
|
| 197 |
+
append(line[start:stop])
|
| 198 |
+
start = stop - 1
|
| 199 |
+
else:
|
| 200 |
+
append(line[start:stop] + '=')
|
| 201 |
+
start = stop
|
| 202 |
+
|
| 203 |
+
# handle rest of line, special case if line ends in whitespace
|
| 204 |
+
if line and line[-1] in ' \t':
|
| 205 |
+
room = start - laststart
|
| 206 |
+
if room >= 3:
|
| 207 |
+
# It's a whitespace character at end-of-line, and we have room
|
| 208 |
+
# for the three-character quoted encoding.
|
| 209 |
+
q = quote(line[-1])
|
| 210 |
+
elif room == 2:
|
| 211 |
+
# There's room for the whitespace character and a soft break.
|
| 212 |
+
q = line[-1] + soft_break
|
| 213 |
+
else:
|
| 214 |
+
# There's room only for a soft break. The quoted whitespace
|
| 215 |
+
# will be the only content on the subsequent line.
|
| 216 |
+
q = soft_break + quote(line[-1])
|
| 217 |
+
append(line[start:-1] + q)
|
| 218 |
+
else:
|
| 219 |
+
append(line[start:])
|
| 220 |
+
|
| 221 |
+
# add back final newline if present
|
| 222 |
+
if body[-1] in CRLF:
|
| 223 |
+
append('')
|
| 224 |
+
|
| 225 |
+
return eol.join(encoded_body)
|
| 226 |
+
|
| 227 |
+
|
| 228 |
+
|
| 229 |
+
# BAW: I'm not sure if the intent was for the signature of this function to be
|
| 230 |
+
# the same as base64MIME.decode() or not...
|
| 231 |
+
def decode(encoded, eol=NL):
|
| 232 |
+
"""Decode a quoted-printable string.
|
| 233 |
+
|
| 234 |
+
Lines are separated with eol, which defaults to \\n.
|
| 235 |
+
"""
|
| 236 |
+
if not encoded:
|
| 237 |
+
return encoded
|
| 238 |
+
# BAW: see comment in encode() above. Again, we're building up the
|
| 239 |
+
# decoded string with string concatenation, which could be done much more
|
| 240 |
+
# efficiently.
|
| 241 |
+
decoded = ''
|
| 242 |
+
|
| 243 |
+
for line in encoded.splitlines():
|
| 244 |
+
line = line.rstrip()
|
| 245 |
+
if not line:
|
| 246 |
+
decoded += eol
|
| 247 |
+
continue
|
| 248 |
+
|
| 249 |
+
i = 0
|
| 250 |
+
n = len(line)
|
| 251 |
+
while i < n:
|
| 252 |
+
c = line[i]
|
| 253 |
+
if c != '=':
|
| 254 |
+
decoded += c
|
| 255 |
+
i += 1
|
| 256 |
+
# Otherwise, c == "=". Are we at the end of the line? If so, add
|
| 257 |
+
# a soft line break.
|
| 258 |
+
elif i+1 == n:
|
| 259 |
+
i += 1
|
| 260 |
+
continue
|
| 261 |
+
# Decode if in form =AB
|
| 262 |
+
elif i+2 < n and line[i+1] in hexdigits and line[i+2] in hexdigits:
|
| 263 |
+
decoded += unquote(line[i:i+3])
|
| 264 |
+
i += 3
|
| 265 |
+
# Otherwise, not in form =AB, pass literally
|
| 266 |
+
else:
|
| 267 |
+
decoded += c
|
| 268 |
+
i += 1
|
| 269 |
+
|
| 270 |
+
if i == n:
|
| 271 |
+
decoded += eol
|
| 272 |
+
# Special case if original string did not end with eol
|
| 273 |
+
if encoded[-1] not in '\r\n' and decoded.endswith(eol):
|
| 274 |
+
decoded = decoded[:-1]
|
| 275 |
+
return decoded
|
| 276 |
+
|
| 277 |
+
|
| 278 |
+
# For convenience and backwards compatibility w/ standard base64 module
|
| 279 |
+
body_decode = decode
|
| 280 |
+
decodestring = decode
|
| 281 |
+
|
| 282 |
+
|
| 283 |
+
|
| 284 |
+
def _unquote_match(match):
|
| 285 |
+
"""Turn a match in the form =AB to the ASCII character with value 0xab"""
|
| 286 |
+
s = match.group(0)
|
| 287 |
+
return unquote(s)
|
| 288 |
+
|
| 289 |
+
|
| 290 |
+
# Header decoding is done a bit differently
|
| 291 |
+
def header_decode(s):
|
| 292 |
+
"""Decode a string encoded with RFC 2045 MIME header `Q' encoding.
|
| 293 |
+
|
| 294 |
+
This function does not parse a full MIME header value encoded with
|
| 295 |
+
quoted-printable (like =?iso-8859-1?q?Hello_World?=) -- please use
|
| 296 |
+
the high level email.header class for that functionality.
|
| 297 |
+
"""
|
| 298 |
+
s = s.replace('_', ' ')
|
| 299 |
+
return re.sub(r'=[a-fA-F0-9]{2}', _unquote_match, s, flags=re.ASCII)
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/email/utils.py
ADDED
|
@@ -0,0 +1,376 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (C) 2001-2010 Python Software Foundation
|
| 2 |
+
# Author: Barry Warsaw
|
| 3 |
+
# Contact: email-sig@python.org
|
| 4 |
+
|
| 5 |
+
"""Miscellaneous utilities."""
|
| 6 |
+
|
| 7 |
+
__all__ = [
|
| 8 |
+
'collapse_rfc2231_value',
|
| 9 |
+
'decode_params',
|
| 10 |
+
'decode_rfc2231',
|
| 11 |
+
'encode_rfc2231',
|
| 12 |
+
'formataddr',
|
| 13 |
+
'formatdate',
|
| 14 |
+
'format_datetime',
|
| 15 |
+
'getaddresses',
|
| 16 |
+
'make_msgid',
|
| 17 |
+
'mktime_tz',
|
| 18 |
+
'parseaddr',
|
| 19 |
+
'parsedate',
|
| 20 |
+
'parsedate_tz',
|
| 21 |
+
'parsedate_to_datetime',
|
| 22 |
+
'unquote',
|
| 23 |
+
]
|
| 24 |
+
|
| 25 |
+
import os
|
| 26 |
+
import re
|
| 27 |
+
import time
|
| 28 |
+
import random
|
| 29 |
+
import socket
|
| 30 |
+
import datetime
|
| 31 |
+
import urllib.parse
|
| 32 |
+
|
| 33 |
+
from email._parseaddr import quote
|
| 34 |
+
from email._parseaddr import AddressList as _AddressList
|
| 35 |
+
from email._parseaddr import mktime_tz
|
| 36 |
+
|
| 37 |
+
from email._parseaddr import parsedate, parsedate_tz, _parsedate_tz
|
| 38 |
+
|
| 39 |
+
# Intrapackage imports
|
| 40 |
+
from email.charset import Charset
|
| 41 |
+
|
| 42 |
+
COMMASPACE = ', '
|
| 43 |
+
EMPTYSTRING = ''
|
| 44 |
+
UEMPTYSTRING = ''
|
| 45 |
+
CRLF = '\r\n'
|
| 46 |
+
TICK = "'"
|
| 47 |
+
|
| 48 |
+
specialsre = re.compile(r'[][\\()<>@,:;".]')
|
| 49 |
+
escapesre = re.compile(r'[\\"]')
|
| 50 |
+
|
| 51 |
+
def _has_surrogates(s):
|
| 52 |
+
"""Return True if s contains surrogate-escaped binary data."""
|
| 53 |
+
# This check is based on the fact that unless there are surrogates, utf8
|
| 54 |
+
# (Python's default encoding) can encode any string. This is the fastest
|
| 55 |
+
# way to check for surrogates, see issue 11454 for timings.
|
| 56 |
+
try:
|
| 57 |
+
s.encode()
|
| 58 |
+
return False
|
| 59 |
+
except UnicodeEncodeError:
|
| 60 |
+
return True
|
| 61 |
+
|
| 62 |
+
# How to deal with a string containing bytes before handing it to the
|
| 63 |
+
# application through the 'normal' interface.
|
| 64 |
+
def _sanitize(string):
|
| 65 |
+
# Turn any escaped bytes into unicode 'unknown' char. If the escaped
|
| 66 |
+
# bytes happen to be utf-8 they will instead get decoded, even if they
|
| 67 |
+
# were invalid in the charset the source was supposed to be in. This
|
| 68 |
+
# seems like it is not a bad thing; a defect was still registered.
|
| 69 |
+
original_bytes = string.encode('utf-8', 'surrogateescape')
|
| 70 |
+
return original_bytes.decode('utf-8', 'replace')
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
# Helpers
|
| 75 |
+
|
| 76 |
+
def formataddr(pair, charset='utf-8'):
|
| 77 |
+
"""The inverse of parseaddr(), this takes a 2-tuple of the form
|
| 78 |
+
(realname, email_address) and returns the string value suitable
|
| 79 |
+
for an RFC 2822 From, To or Cc header.
|
| 80 |
+
|
| 81 |
+
If the first element of pair is false, then the second element is
|
| 82 |
+
returned unmodified.
|
| 83 |
+
|
| 84 |
+
The optional charset is the character set that is used to encode
|
| 85 |
+
realname in case realname is not ASCII safe. Can be an instance of str or
|
| 86 |
+
a Charset-like object which has a header_encode method. Default is
|
| 87 |
+
'utf-8'.
|
| 88 |
+
"""
|
| 89 |
+
name, address = pair
|
| 90 |
+
# The address MUST (per RFC) be ascii, so raise a UnicodeError if it isn't.
|
| 91 |
+
address.encode('ascii')
|
| 92 |
+
if name:
|
| 93 |
+
try:
|
| 94 |
+
name.encode('ascii')
|
| 95 |
+
except UnicodeEncodeError:
|
| 96 |
+
if isinstance(charset, str):
|
| 97 |
+
charset = Charset(charset)
|
| 98 |
+
encoded_name = charset.header_encode(name)
|
| 99 |
+
return "%s <%s>" % (encoded_name, address)
|
| 100 |
+
else:
|
| 101 |
+
quotes = ''
|
| 102 |
+
if specialsre.search(name):
|
| 103 |
+
quotes = '"'
|
| 104 |
+
name = escapesre.sub(r'\\\g<0>', name)
|
| 105 |
+
return '%s%s%s <%s>' % (quotes, name, quotes, address)
|
| 106 |
+
return address
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
def getaddresses(fieldvalues):
|
| 111 |
+
"""Return a list of (REALNAME, EMAIL) for each fieldvalue."""
|
| 112 |
+
all = COMMASPACE.join(fieldvalues)
|
| 113 |
+
a = _AddressList(all)
|
| 114 |
+
return a.addresslist
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
def _format_timetuple_and_zone(timetuple, zone):
|
| 118 |
+
return '%s, %02d %s %04d %02d:%02d:%02d %s' % (
|
| 119 |
+
['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'][timetuple[6]],
|
| 120 |
+
timetuple[2],
|
| 121 |
+
['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
|
| 122 |
+
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'][timetuple[1] - 1],
|
| 123 |
+
timetuple[0], timetuple[3], timetuple[4], timetuple[5],
|
| 124 |
+
zone)
|
| 125 |
+
|
| 126 |
+
def formatdate(timeval=None, localtime=False, usegmt=False):
|
| 127 |
+
"""Returns a date string as specified by RFC 2822, e.g.:
|
| 128 |
+
|
| 129 |
+
Fri, 09 Nov 2001 01:08:47 -0000
|
| 130 |
+
|
| 131 |
+
Optional timeval if given is a floating point time value as accepted by
|
| 132 |
+
gmtime() and localtime(), otherwise the current time is used.
|
| 133 |
+
|
| 134 |
+
Optional localtime is a flag that when True, interprets timeval, and
|
| 135 |
+
returns a date relative to the local timezone instead of UTC, properly
|
| 136 |
+
taking daylight savings time into account.
|
| 137 |
+
|
| 138 |
+
Optional argument usegmt means that the timezone is written out as
|
| 139 |
+
an ascii string, not numeric one (so "GMT" instead of "+0000"). This
|
| 140 |
+
is needed for HTTP, and is only used when localtime==False.
|
| 141 |
+
"""
|
| 142 |
+
# Note: we cannot use strftime() because that honors the locale and RFC
|
| 143 |
+
# 2822 requires that day and month names be the English abbreviations.
|
| 144 |
+
if timeval is None:
|
| 145 |
+
timeval = time.time()
|
| 146 |
+
if localtime or usegmt:
|
| 147 |
+
dt = datetime.datetime.fromtimestamp(timeval, datetime.timezone.utc)
|
| 148 |
+
else:
|
| 149 |
+
dt = datetime.datetime.utcfromtimestamp(timeval)
|
| 150 |
+
if localtime:
|
| 151 |
+
dt = dt.astimezone()
|
| 152 |
+
usegmt = False
|
| 153 |
+
return format_datetime(dt, usegmt)
|
| 154 |
+
|
| 155 |
+
def format_datetime(dt, usegmt=False):
|
| 156 |
+
"""Turn a datetime into a date string as specified in RFC 2822.
|
| 157 |
+
|
| 158 |
+
If usegmt is True, dt must be an aware datetime with an offset of zero. In
|
| 159 |
+
this case 'GMT' will be rendered instead of the normal +0000 required by
|
| 160 |
+
RFC2822. This is to support HTTP headers involving date stamps.
|
| 161 |
+
"""
|
| 162 |
+
now = dt.timetuple()
|
| 163 |
+
if usegmt:
|
| 164 |
+
if dt.tzinfo is None or dt.tzinfo != datetime.timezone.utc:
|
| 165 |
+
raise ValueError("usegmt option requires a UTC datetime")
|
| 166 |
+
zone = 'GMT'
|
| 167 |
+
elif dt.tzinfo is None:
|
| 168 |
+
zone = '-0000'
|
| 169 |
+
else:
|
| 170 |
+
zone = dt.strftime("%z")
|
| 171 |
+
return _format_timetuple_and_zone(now, zone)
|
| 172 |
+
|
| 173 |
+
|
| 174 |
+
def make_msgid(idstring=None, domain=None):
|
| 175 |
+
"""Returns a string suitable for RFC 2822 compliant Message-ID, e.g:
|
| 176 |
+
|
| 177 |
+
<142480216486.20800.16526388040877946887@nightshade.la.mastaler.com>
|
| 178 |
+
|
| 179 |
+
Optional idstring if given is a string used to strengthen the
|
| 180 |
+
uniqueness of the message id. Optional domain if given provides the
|
| 181 |
+
portion of the message id after the '@'. It defaults to the locally
|
| 182 |
+
defined hostname.
|
| 183 |
+
"""
|
| 184 |
+
timeval = int(time.time()*100)
|
| 185 |
+
pid = os.getpid()
|
| 186 |
+
randint = random.getrandbits(64)
|
| 187 |
+
if idstring is None:
|
| 188 |
+
idstring = ''
|
| 189 |
+
else:
|
| 190 |
+
idstring = '.' + idstring
|
| 191 |
+
if domain is None:
|
| 192 |
+
domain = socket.getfqdn()
|
| 193 |
+
msgid = '<%d.%d.%d%s@%s>' % (timeval, pid, randint, idstring, domain)
|
| 194 |
+
return msgid
|
| 195 |
+
|
| 196 |
+
|
| 197 |
+
def parsedate_to_datetime(data):
|
| 198 |
+
*dtuple, tz = _parsedate_tz(data)
|
| 199 |
+
if tz is None:
|
| 200 |
+
return datetime.datetime(*dtuple[:6])
|
| 201 |
+
return datetime.datetime(*dtuple[:6],
|
| 202 |
+
tzinfo=datetime.timezone(datetime.timedelta(seconds=tz)))
|
| 203 |
+
|
| 204 |
+
|
| 205 |
+
def parseaddr(addr):
|
| 206 |
+
"""
|
| 207 |
+
Parse addr into its constituent realname and email address parts.
|
| 208 |
+
|
| 209 |
+
Return a tuple of realname and email address, unless the parse fails, in
|
| 210 |
+
which case return a 2-tuple of ('', '').
|
| 211 |
+
"""
|
| 212 |
+
addrs = _AddressList(addr).addresslist
|
| 213 |
+
if not addrs:
|
| 214 |
+
return '', ''
|
| 215 |
+
return addrs[0]
|
| 216 |
+
|
| 217 |
+
|
| 218 |
+
# rfc822.unquote() doesn't properly de-backslash-ify in Python pre-2.3.
|
| 219 |
+
def unquote(str):
|
| 220 |
+
"""Remove quotes from a string."""
|
| 221 |
+
if len(str) > 1:
|
| 222 |
+
if str.startswith('"') and str.endswith('"'):
|
| 223 |
+
return str[1:-1].replace('\\\\', '\\').replace('\\"', '"')
|
| 224 |
+
if str.startswith('<') and str.endswith('>'):
|
| 225 |
+
return str[1:-1]
|
| 226 |
+
return str
|
| 227 |
+
|
| 228 |
+
|
| 229 |
+
|
| 230 |
+
# RFC2231-related functions - parameter encoding and decoding
|
| 231 |
+
def decode_rfc2231(s):
|
| 232 |
+
"""Decode string according to RFC 2231"""
|
| 233 |
+
parts = s.split(TICK, 2)
|
| 234 |
+
if len(parts) <= 2:
|
| 235 |
+
return None, None, s
|
| 236 |
+
return parts
|
| 237 |
+
|
| 238 |
+
|
| 239 |
+
def encode_rfc2231(s, charset=None, language=None):
|
| 240 |
+
"""Encode string according to RFC 2231.
|
| 241 |
+
|
| 242 |
+
If neither charset nor language is given, then s is returned as-is. If
|
| 243 |
+
charset is given but not language, the string is encoded using the empty
|
| 244 |
+
string for language.
|
| 245 |
+
"""
|
| 246 |
+
s = urllib.parse.quote(s, safe='', encoding=charset or 'ascii')
|
| 247 |
+
if charset is None and language is None:
|
| 248 |
+
return s
|
| 249 |
+
if language is None:
|
| 250 |
+
language = ''
|
| 251 |
+
return "%s'%s'%s" % (charset, language, s)
|
| 252 |
+
|
| 253 |
+
|
| 254 |
+
rfc2231_continuation = re.compile(r'^(?P<name>\w+)\*((?P<num>[0-9]+)\*?)?$',
|
| 255 |
+
re.ASCII)
|
| 256 |
+
|
| 257 |
+
def decode_params(params):
|
| 258 |
+
"""Decode parameters list according to RFC 2231.
|
| 259 |
+
|
| 260 |
+
params is a sequence of 2-tuples containing (param name, string value).
|
| 261 |
+
"""
|
| 262 |
+
# Copy params so we don't mess with the original
|
| 263 |
+
params = params[:]
|
| 264 |
+
new_params = []
|
| 265 |
+
# Map parameter's name to a list of continuations. The values are a
|
| 266 |
+
# 3-tuple of the continuation number, the string value, and a flag
|
| 267 |
+
# specifying whether a particular segment is %-encoded.
|
| 268 |
+
rfc2231_params = {}
|
| 269 |
+
name, value = params.pop(0)
|
| 270 |
+
new_params.append((name, value))
|
| 271 |
+
while params:
|
| 272 |
+
name, value = params.pop(0)
|
| 273 |
+
if name.endswith('*'):
|
| 274 |
+
encoded = True
|
| 275 |
+
else:
|
| 276 |
+
encoded = False
|
| 277 |
+
value = unquote(value)
|
| 278 |
+
mo = rfc2231_continuation.match(name)
|
| 279 |
+
if mo:
|
| 280 |
+
name, num = mo.group('name', 'num')
|
| 281 |
+
if num is not None:
|
| 282 |
+
num = int(num)
|
| 283 |
+
rfc2231_params.setdefault(name, []).append((num, value, encoded))
|
| 284 |
+
else:
|
| 285 |
+
new_params.append((name, '"%s"' % quote(value)))
|
| 286 |
+
if rfc2231_params:
|
| 287 |
+
for name, continuations in rfc2231_params.items():
|
| 288 |
+
value = []
|
| 289 |
+
extended = False
|
| 290 |
+
# Sort by number
|
| 291 |
+
continuations.sort()
|
| 292 |
+
# And now append all values in numerical order, converting
|
| 293 |
+
# %-encodings for the encoded segments. If any of the
|
| 294 |
+
# continuation names ends in a *, then the entire string, after
|
| 295 |
+
# decoding segments and concatenating, must have the charset and
|
| 296 |
+
# language specifiers at the beginning of the string.
|
| 297 |
+
for num, s, encoded in continuations:
|
| 298 |
+
if encoded:
|
| 299 |
+
# Decode as "latin-1", so the characters in s directly
|
| 300 |
+
# represent the percent-encoded octet values.
|
| 301 |
+
# collapse_rfc2231_value treats this as an octet sequence.
|
| 302 |
+
s = urllib.parse.unquote(s, encoding="latin-1")
|
| 303 |
+
extended = True
|
| 304 |
+
value.append(s)
|
| 305 |
+
value = quote(EMPTYSTRING.join(value))
|
| 306 |
+
if extended:
|
| 307 |
+
charset, language, value = decode_rfc2231(value)
|
| 308 |
+
new_params.append((name, (charset, language, '"%s"' % value)))
|
| 309 |
+
else:
|
| 310 |
+
new_params.append((name, '"%s"' % value))
|
| 311 |
+
return new_params
|
| 312 |
+
|
| 313 |
+
def collapse_rfc2231_value(value, errors='replace',
|
| 314 |
+
fallback_charset='us-ascii'):
|
| 315 |
+
if not isinstance(value, tuple) or len(value) != 3:
|
| 316 |
+
return unquote(value)
|
| 317 |
+
# While value comes to us as a unicode string, we need it to be a bytes
|
| 318 |
+
# object. We do not want bytes() normal utf-8 decoder, we want a straight
|
| 319 |
+
# interpretation of the string as character bytes.
|
| 320 |
+
charset, language, text = value
|
| 321 |
+
if charset is None:
|
| 322 |
+
# Issue 17369: if charset/lang is None, decode_rfc2231 couldn't parse
|
| 323 |
+
# the value, so use the fallback_charset.
|
| 324 |
+
charset = fallback_charset
|
| 325 |
+
rawbytes = bytes(text, 'raw-unicode-escape')
|
| 326 |
+
try:
|
| 327 |
+
return str(rawbytes, charset, errors)
|
| 328 |
+
except LookupError:
|
| 329 |
+
# charset is not a known codec.
|
| 330 |
+
return unquote(text)
|
| 331 |
+
|
| 332 |
+
|
| 333 |
+
#
|
| 334 |
+
# datetime doesn't provide a localtime function yet, so provide one. Code
|
| 335 |
+
# adapted from the patch in issue 9527. This may not be perfect, but it is
|
| 336 |
+
# better than not having it.
|
| 337 |
+
#
|
| 338 |
+
|
| 339 |
+
def localtime(dt=None, isdst=-1):
|
| 340 |
+
"""Return local time as an aware datetime object.
|
| 341 |
+
|
| 342 |
+
If called without arguments, return current time. Otherwise *dt*
|
| 343 |
+
argument should be a datetime instance, and it is converted to the
|
| 344 |
+
local time zone according to the system time zone database. If *dt* is
|
| 345 |
+
naive (that is, dt.tzinfo is None), it is assumed to be in local time.
|
| 346 |
+
In this case, a positive or zero value for *isdst* causes localtime to
|
| 347 |
+
presume initially that summer time (for example, Daylight Saving Time)
|
| 348 |
+
is or is not (respectively) in effect for the specified time. A
|
| 349 |
+
negative value for *isdst* causes the localtime() function to attempt
|
| 350 |
+
to divine whether summer time is in effect for the specified time.
|
| 351 |
+
|
| 352 |
+
"""
|
| 353 |
+
if dt is None:
|
| 354 |
+
return datetime.datetime.now(datetime.timezone.utc).astimezone()
|
| 355 |
+
if dt.tzinfo is not None:
|
| 356 |
+
return dt.astimezone()
|
| 357 |
+
# We have a naive datetime. Convert to a (localtime) timetuple and pass to
|
| 358 |
+
# system mktime together with the isdst hint. System mktime will return
|
| 359 |
+
# seconds since epoch.
|
| 360 |
+
tm = dt.timetuple()[:-1] + (isdst,)
|
| 361 |
+
seconds = time.mktime(tm)
|
| 362 |
+
localtm = time.localtime(seconds)
|
| 363 |
+
try:
|
| 364 |
+
delta = datetime.timedelta(seconds=localtm.tm_gmtoff)
|
| 365 |
+
tz = datetime.timezone(delta, localtm.tm_zone)
|
| 366 |
+
except AttributeError:
|
| 367 |
+
# Compute UTC offset and compare with the value implied by tm_isdst.
|
| 368 |
+
# If the values match, use the zone name implied by tm_isdst.
|
| 369 |
+
delta = dt - datetime.datetime(*time.gmtime(seconds)[:6])
|
| 370 |
+
dst = time.daylight and localtm.tm_isdst > 0
|
| 371 |
+
gmtoff = -(time.altzone if dst else time.timezone)
|
| 372 |
+
if delta == datetime.timedelta(seconds=gmtoff):
|
| 373 |
+
tz = datetime.timezone(delta, time.tzname[dst])
|
| 374 |
+
else:
|
| 375 |
+
tz = datetime.timezone(delta)
|
| 376 |
+
return dt.replace(tzinfo=tz)
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/json/__init__.py
ADDED
|
@@ -0,0 +1,370 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
r"""JSON (JavaScript Object Notation) <http://json.org> is a subset of
|
| 2 |
+
JavaScript syntax (ECMA-262 3rd edition) used as a lightweight data
|
| 3 |
+
interchange format.
|
| 4 |
+
|
| 5 |
+
:mod:`json` exposes an API familiar to users of the standard library
|
| 6 |
+
:mod:`marshal` and :mod:`pickle` modules. It is derived from a
|
| 7 |
+
version of the externally maintained simplejson library.
|
| 8 |
+
|
| 9 |
+
Encoding basic Python object hierarchies::
|
| 10 |
+
|
| 11 |
+
>>> import json
|
| 12 |
+
>>> json.dumps(['foo', {'bar': ('baz', None, 1.0, 2)}])
|
| 13 |
+
'["foo", {"bar": ["baz", null, 1.0, 2]}]'
|
| 14 |
+
>>> print(json.dumps("\"foo\bar"))
|
| 15 |
+
"\"foo\bar"
|
| 16 |
+
>>> print(json.dumps('\u1234'))
|
| 17 |
+
"\u1234"
|
| 18 |
+
>>> print(json.dumps('\\'))
|
| 19 |
+
"\\"
|
| 20 |
+
>>> print(json.dumps({"c": 0, "b": 0, "a": 0}, sort_keys=True))
|
| 21 |
+
{"a": 0, "b": 0, "c": 0}
|
| 22 |
+
>>> from io import StringIO
|
| 23 |
+
>>> io = StringIO()
|
| 24 |
+
>>> json.dump(['streaming API'], io)
|
| 25 |
+
>>> io.getvalue()
|
| 26 |
+
'["streaming API"]'
|
| 27 |
+
|
| 28 |
+
Compact encoding::
|
| 29 |
+
|
| 30 |
+
>>> import json
|
| 31 |
+
>>> mydict = {'4': 5, '6': 7}
|
| 32 |
+
>>> json.dumps([1,2,3,mydict], separators=(',', ':'))
|
| 33 |
+
'[1,2,3,{"4":5,"6":7}]'
|
| 34 |
+
|
| 35 |
+
Pretty printing::
|
| 36 |
+
|
| 37 |
+
>>> import json
|
| 38 |
+
>>> print(json.dumps({'4': 5, '6': 7}, sort_keys=True, indent=4))
|
| 39 |
+
{
|
| 40 |
+
"4": 5,
|
| 41 |
+
"6": 7
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
Decoding JSON::
|
| 45 |
+
|
| 46 |
+
>>> import json
|
| 47 |
+
>>> obj = ['foo', {'bar': ['baz', None, 1.0, 2]}]
|
| 48 |
+
>>> json.loads('["foo", {"bar":["baz", null, 1.0, 2]}]') == obj
|
| 49 |
+
True
|
| 50 |
+
>>> json.loads('"\\"foo\\bar"') == '"foo\x08ar'
|
| 51 |
+
True
|
| 52 |
+
>>> from io import StringIO
|
| 53 |
+
>>> io = StringIO('["streaming API"]')
|
| 54 |
+
>>> json.load(io)[0] == 'streaming API'
|
| 55 |
+
True
|
| 56 |
+
|
| 57 |
+
Specializing JSON object decoding::
|
| 58 |
+
|
| 59 |
+
>>> import json
|
| 60 |
+
>>> def as_complex(dct):
|
| 61 |
+
... if '__complex__' in dct:
|
| 62 |
+
... return complex(dct['real'], dct['imag'])
|
| 63 |
+
... return dct
|
| 64 |
+
...
|
| 65 |
+
>>> json.loads('{"__complex__": true, "real": 1, "imag": 2}',
|
| 66 |
+
... object_hook=as_complex)
|
| 67 |
+
(1+2j)
|
| 68 |
+
>>> from decimal import Decimal
|
| 69 |
+
>>> json.loads('1.1', parse_float=Decimal) == Decimal('1.1')
|
| 70 |
+
True
|
| 71 |
+
|
| 72 |
+
Specializing JSON object encoding::
|
| 73 |
+
|
| 74 |
+
>>> import json
|
| 75 |
+
>>> def encode_complex(obj):
|
| 76 |
+
... if isinstance(obj, complex):
|
| 77 |
+
... return [obj.real, obj.imag]
|
| 78 |
+
... raise TypeError(f'Object of type {obj.__class__.__name__} '
|
| 79 |
+
... f'is not JSON serializable')
|
| 80 |
+
...
|
| 81 |
+
>>> json.dumps(2 + 1j, default=encode_complex)
|
| 82 |
+
'[2.0, 1.0]'
|
| 83 |
+
>>> json.JSONEncoder(default=encode_complex).encode(2 + 1j)
|
| 84 |
+
'[2.0, 1.0]'
|
| 85 |
+
>>> ''.join(json.JSONEncoder(default=encode_complex).iterencode(2 + 1j))
|
| 86 |
+
'[2.0, 1.0]'
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
Using json.tool from the shell to validate and pretty-print::
|
| 90 |
+
|
| 91 |
+
$ echo '{"json":"obj"}' | python -m json.tool
|
| 92 |
+
{
|
| 93 |
+
"json": "obj"
|
| 94 |
+
}
|
| 95 |
+
$ echo '{ 1.2:3.4}' | python -m json.tool
|
| 96 |
+
Expecting property name enclosed in double quotes: line 1 column 3 (char 2)
|
| 97 |
+
"""
|
| 98 |
+
__version__ = '2.0.9'
|
| 99 |
+
__all__ = [
|
| 100 |
+
'dump', 'dumps', 'load', 'loads',
|
| 101 |
+
'JSONDecoder', 'JSONDecodeError', 'JSONEncoder',
|
| 102 |
+
]
|
| 103 |
+
|
| 104 |
+
__author__ = 'Bob Ippolito <bob@redivi.com>'
|
| 105 |
+
|
| 106 |
+
from .decoder import JSONDecoder, JSONDecodeError
|
| 107 |
+
from .encoder import JSONEncoder
|
| 108 |
+
import codecs
|
| 109 |
+
|
| 110 |
+
_default_encoder = JSONEncoder(
|
| 111 |
+
skipkeys=False,
|
| 112 |
+
ensure_ascii=True,
|
| 113 |
+
check_circular=True,
|
| 114 |
+
allow_nan=True,
|
| 115 |
+
indent=None,
|
| 116 |
+
separators=None,
|
| 117 |
+
default=None,
|
| 118 |
+
)
|
| 119 |
+
|
| 120 |
+
def dump(obj, fp, *, skipkeys=False, ensure_ascii=True, check_circular=True,
|
| 121 |
+
allow_nan=True, cls=None, indent=None, separators=None,
|
| 122 |
+
default=None, sort_keys=False, **kw):
|
| 123 |
+
"""Serialize ``obj`` as a JSON formatted stream to ``fp`` (a
|
| 124 |
+
``.write()``-supporting file-like object).
|
| 125 |
+
|
| 126 |
+
If ``skipkeys`` is true then ``dict`` keys that are not basic types
|
| 127 |
+
(``str``, ``int``, ``float``, ``bool``, ``None``) will be skipped
|
| 128 |
+
instead of raising a ``TypeError``.
|
| 129 |
+
|
| 130 |
+
If ``ensure_ascii`` is false, then the strings written to ``fp`` can
|
| 131 |
+
contain non-ASCII characters if they appear in strings contained in
|
| 132 |
+
``obj``. Otherwise, all such characters are escaped in JSON strings.
|
| 133 |
+
|
| 134 |
+
If ``check_circular`` is false, then the circular reference check
|
| 135 |
+
for container types will be skipped and a circular reference will
|
| 136 |
+
result in an ``OverflowError`` (or worse).
|
| 137 |
+
|
| 138 |
+
If ``allow_nan`` is false, then it will be a ``ValueError`` to
|
| 139 |
+
serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``)
|
| 140 |
+
in strict compliance of the JSON specification, instead of using the
|
| 141 |
+
JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``).
|
| 142 |
+
|
| 143 |
+
If ``indent`` is a non-negative integer, then JSON array elements and
|
| 144 |
+
object members will be pretty-printed with that indent level. An indent
|
| 145 |
+
level of 0 will only insert newlines. ``None`` is the most compact
|
| 146 |
+
representation.
|
| 147 |
+
|
| 148 |
+
If specified, ``separators`` should be an ``(item_separator, key_separator)``
|
| 149 |
+
tuple. The default is ``(', ', ': ')`` if *indent* is ``None`` and
|
| 150 |
+
``(',', ': ')`` otherwise. To get the most compact JSON representation,
|
| 151 |
+
you should specify ``(',', ':')`` to eliminate whitespace.
|
| 152 |
+
|
| 153 |
+
``default(obj)`` is a function that should return a serializable version
|
| 154 |
+
of obj or raise TypeError. The default simply raises TypeError.
|
| 155 |
+
|
| 156 |
+
If *sort_keys* is true (default: ``False``), then the output of
|
| 157 |
+
dictionaries will be sorted by key.
|
| 158 |
+
|
| 159 |
+
To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the
|
| 160 |
+
``.default()`` method to serialize additional types), specify it with
|
| 161 |
+
the ``cls`` kwarg; otherwise ``JSONEncoder`` is used.
|
| 162 |
+
|
| 163 |
+
"""
|
| 164 |
+
# cached encoder
|
| 165 |
+
if (not skipkeys and ensure_ascii and
|
| 166 |
+
check_circular and allow_nan and
|
| 167 |
+
cls is None and indent is None and separators is None and
|
| 168 |
+
default is None and not sort_keys and not kw):
|
| 169 |
+
iterable = _default_encoder.iterencode(obj)
|
| 170 |
+
else:
|
| 171 |
+
if cls is None:
|
| 172 |
+
cls = JSONEncoder
|
| 173 |
+
iterable = cls(skipkeys=skipkeys, ensure_ascii=ensure_ascii,
|
| 174 |
+
check_circular=check_circular, allow_nan=allow_nan, indent=indent,
|
| 175 |
+
separators=separators,
|
| 176 |
+
default=default, sort_keys=sort_keys, **kw).iterencode(obj)
|
| 177 |
+
# could accelerate with writelines in some versions of Python, at
|
| 178 |
+
# a debuggability cost
|
| 179 |
+
for chunk in iterable:
|
| 180 |
+
fp.write(chunk)
|
| 181 |
+
|
| 182 |
+
|
| 183 |
+
def dumps(obj, *, skipkeys=False, ensure_ascii=True, check_circular=True,
|
| 184 |
+
allow_nan=True, cls=None, indent=None, separators=None,
|
| 185 |
+
default=None, sort_keys=False, **kw):
|
| 186 |
+
"""Serialize ``obj`` to a JSON formatted ``str``.
|
| 187 |
+
|
| 188 |
+
If ``skipkeys`` is true then ``dict`` keys that are not basic types
|
| 189 |
+
(``str``, ``int``, ``float``, ``bool``, ``None``) will be skipped
|
| 190 |
+
instead of raising a ``TypeError``.
|
| 191 |
+
|
| 192 |
+
If ``ensure_ascii`` is false, then the return value can contain non-ASCII
|
| 193 |
+
characters if they appear in strings contained in ``obj``. Otherwise, all
|
| 194 |
+
such characters are escaped in JSON strings.
|
| 195 |
+
|
| 196 |
+
If ``check_circular`` is false, then the circular reference check
|
| 197 |
+
for container types will be skipped and a circular reference will
|
| 198 |
+
result in an ``OverflowError`` (or worse).
|
| 199 |
+
|
| 200 |
+
If ``allow_nan`` is false, then it will be a ``ValueError`` to
|
| 201 |
+
serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``) in
|
| 202 |
+
strict compliance of the JSON specification, instead of using the
|
| 203 |
+
JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``).
|
| 204 |
+
|
| 205 |
+
If ``indent`` is a non-negative integer, then JSON array elements and
|
| 206 |
+
object members will be pretty-printed with that indent level. An indent
|
| 207 |
+
level of 0 will only insert newlines. ``None`` is the most compact
|
| 208 |
+
representation.
|
| 209 |
+
|
| 210 |
+
If specified, ``separators`` should be an ``(item_separator, key_separator)``
|
| 211 |
+
tuple. The default is ``(', ', ': ')`` if *indent* is ``None`` and
|
| 212 |
+
``(',', ': ')`` otherwise. To get the most compact JSON representation,
|
| 213 |
+
you should specify ``(',', ':')`` to eliminate whitespace.
|
| 214 |
+
|
| 215 |
+
``default(obj)`` is a function that should return a serializable version
|
| 216 |
+
of obj or raise TypeError. The default simply raises TypeError.
|
| 217 |
+
|
| 218 |
+
If *sort_keys* is true (default: ``False``), then the output of
|
| 219 |
+
dictionaries will be sorted by key.
|
| 220 |
+
|
| 221 |
+
To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the
|
| 222 |
+
``.default()`` method to serialize additional types), specify it with
|
| 223 |
+
the ``cls`` kwarg; otherwise ``JSONEncoder`` is used.
|
| 224 |
+
|
| 225 |
+
"""
|
| 226 |
+
# cached encoder
|
| 227 |
+
if (not skipkeys and ensure_ascii and
|
| 228 |
+
check_circular and allow_nan and
|
| 229 |
+
cls is None and indent is None and separators is None and
|
| 230 |
+
default is None and not sort_keys and not kw):
|
| 231 |
+
return _default_encoder.encode(obj)
|
| 232 |
+
if cls is None:
|
| 233 |
+
cls = JSONEncoder
|
| 234 |
+
return cls(
|
| 235 |
+
skipkeys=skipkeys, ensure_ascii=ensure_ascii,
|
| 236 |
+
check_circular=check_circular, allow_nan=allow_nan, indent=indent,
|
| 237 |
+
separators=separators, default=default, sort_keys=sort_keys,
|
| 238 |
+
**kw).encode(obj)
|
| 239 |
+
|
| 240 |
+
|
| 241 |
+
_default_decoder = JSONDecoder(object_hook=None, object_pairs_hook=None)
|
| 242 |
+
|
| 243 |
+
|
| 244 |
+
def detect_encoding(b):
|
| 245 |
+
bstartswith = b.startswith
|
| 246 |
+
if bstartswith((codecs.BOM_UTF32_BE, codecs.BOM_UTF32_LE)):
|
| 247 |
+
return 'utf-32'
|
| 248 |
+
if bstartswith((codecs.BOM_UTF16_BE, codecs.BOM_UTF16_LE)):
|
| 249 |
+
return 'utf-16'
|
| 250 |
+
if bstartswith(codecs.BOM_UTF8):
|
| 251 |
+
return 'utf-8-sig'
|
| 252 |
+
|
| 253 |
+
if len(b) >= 4:
|
| 254 |
+
if not b[0]:
|
| 255 |
+
# 00 00 -- -- - utf-32-be
|
| 256 |
+
# 00 XX -- -- - utf-16-be
|
| 257 |
+
return 'utf-16-be' if b[1] else 'utf-32-be'
|
| 258 |
+
if not b[1]:
|
| 259 |
+
# XX 00 00 00 - utf-32-le
|
| 260 |
+
# XX 00 00 XX - utf-16-le
|
| 261 |
+
# XX 00 XX -- - utf-16-le
|
| 262 |
+
return 'utf-16-le' if b[2] or b[3] else 'utf-32-le'
|
| 263 |
+
elif len(b) == 2:
|
| 264 |
+
if not b[0]:
|
| 265 |
+
# 00 XX - utf-16-be
|
| 266 |
+
return 'utf-16-be'
|
| 267 |
+
if not b[1]:
|
| 268 |
+
# XX 00 - utf-16-le
|
| 269 |
+
return 'utf-16-le'
|
| 270 |
+
# default
|
| 271 |
+
return 'utf-8'
|
| 272 |
+
|
| 273 |
+
|
| 274 |
+
def load(fp, *, cls=None, object_hook=None, parse_float=None,
|
| 275 |
+
parse_int=None, parse_constant=None, object_pairs_hook=None, **kw):
|
| 276 |
+
"""Deserialize ``fp`` (a ``.read()``-supporting file-like object containing
|
| 277 |
+
a JSON document) to a Python object.
|
| 278 |
+
|
| 279 |
+
``object_hook`` is an optional function that will be called with the
|
| 280 |
+
result of any object literal decode (a ``dict``). The return value of
|
| 281 |
+
``object_hook`` will be used instead of the ``dict``. This feature
|
| 282 |
+
can be used to implement custom decoders (e.g. JSON-RPC class hinting).
|
| 283 |
+
|
| 284 |
+
``object_pairs_hook`` is an optional function that will be called with the
|
| 285 |
+
result of any object literal decoded with an ordered list of pairs. The
|
| 286 |
+
return value of ``object_pairs_hook`` will be used instead of the ``dict``.
|
| 287 |
+
This feature can be used to implement custom decoders. If ``object_hook``
|
| 288 |
+
is also defined, the ``object_pairs_hook`` takes priority.
|
| 289 |
+
|
| 290 |
+
To use a custom ``JSONDecoder`` subclass, specify it with the ``cls``
|
| 291 |
+
kwarg; otherwise ``JSONDecoder`` is used.
|
| 292 |
+
"""
|
| 293 |
+
return loads(fp.read(),
|
| 294 |
+
cls=cls, object_hook=object_hook,
|
| 295 |
+
parse_float=parse_float, parse_int=parse_int,
|
| 296 |
+
parse_constant=parse_constant, object_pairs_hook=object_pairs_hook, **kw)
|
| 297 |
+
|
| 298 |
+
|
| 299 |
+
def loads(s, *, cls=None, object_hook=None, parse_float=None,
|
| 300 |
+
parse_int=None, parse_constant=None, object_pairs_hook=None, **kw):
|
| 301 |
+
"""Deserialize ``s`` (a ``str``, ``bytes`` or ``bytearray`` instance
|
| 302 |
+
containing a JSON document) to a Python object.
|
| 303 |
+
|
| 304 |
+
``object_hook`` is an optional function that will be called with the
|
| 305 |
+
result of any object literal decode (a ``dict``). The return value of
|
| 306 |
+
``object_hook`` will be used instead of the ``dict``. This feature
|
| 307 |
+
can be used to implement custom decoders (e.g. JSON-RPC class hinting).
|
| 308 |
+
|
| 309 |
+
``object_pairs_hook`` is an optional function that will be called with the
|
| 310 |
+
result of any object literal decoded with an ordered list of pairs. The
|
| 311 |
+
return value of ``object_pairs_hook`` will be used instead of the ``dict``.
|
| 312 |
+
This feature can be used to implement custom decoders. If ``object_hook``
|
| 313 |
+
is also defined, the ``object_pairs_hook`` takes priority.
|
| 314 |
+
|
| 315 |
+
``parse_float``, if specified, will be called with the string
|
| 316 |
+
of every JSON float to be decoded. By default this is equivalent to
|
| 317 |
+
float(num_str). This can be used to use another datatype or parser
|
| 318 |
+
for JSON floats (e.g. decimal.Decimal).
|
| 319 |
+
|
| 320 |
+
``parse_int``, if specified, will be called with the string
|
| 321 |
+
of every JSON int to be decoded. By default this is equivalent to
|
| 322 |
+
int(num_str). This can be used to use another datatype or parser
|
| 323 |
+
for JSON integers (e.g. float).
|
| 324 |
+
|
| 325 |
+
``parse_constant``, if specified, will be called with one of the
|
| 326 |
+
following strings: -Infinity, Infinity, NaN.
|
| 327 |
+
This can be used to raise an exception if invalid JSON numbers
|
| 328 |
+
are encountered.
|
| 329 |
+
|
| 330 |
+
To use a custom ``JSONDecoder`` subclass, specify it with the ``cls``
|
| 331 |
+
kwarg; otherwise ``JSONDecoder`` is used.
|
| 332 |
+
|
| 333 |
+
The ``encoding`` argument is ignored and deprecated since Python 3.1.
|
| 334 |
+
"""
|
| 335 |
+
if isinstance(s, str):
|
| 336 |
+
if s.startswith('\ufeff'):
|
| 337 |
+
raise JSONDecodeError("Unexpected UTF-8 BOM (decode using utf-8-sig)",
|
| 338 |
+
s, 0)
|
| 339 |
+
else:
|
| 340 |
+
if not isinstance(s, (bytes, bytearray)):
|
| 341 |
+
raise TypeError(f'the JSON object must be str, bytes or bytearray, '
|
| 342 |
+
f'not {s.__class__.__name__}')
|
| 343 |
+
s = s.decode(detect_encoding(s), 'surrogatepass')
|
| 344 |
+
|
| 345 |
+
if "encoding" in kw:
|
| 346 |
+
import warnings
|
| 347 |
+
warnings.warn(
|
| 348 |
+
"'encoding' is ignored and deprecated. It will be removed in Python 3.9",
|
| 349 |
+
DeprecationWarning,
|
| 350 |
+
stacklevel=2
|
| 351 |
+
)
|
| 352 |
+
del kw['encoding']
|
| 353 |
+
|
| 354 |
+
if (cls is None and object_hook is None and
|
| 355 |
+
parse_int is None and parse_float is None and
|
| 356 |
+
parse_constant is None and object_pairs_hook is None and not kw):
|
| 357 |
+
return _default_decoder.decode(s)
|
| 358 |
+
if cls is None:
|
| 359 |
+
cls = JSONDecoder
|
| 360 |
+
if object_hook is not None:
|
| 361 |
+
kw['object_hook'] = object_hook
|
| 362 |
+
if object_pairs_hook is not None:
|
| 363 |
+
kw['object_pairs_hook'] = object_pairs_hook
|
| 364 |
+
if parse_float is not None:
|
| 365 |
+
kw['parse_float'] = parse_float
|
| 366 |
+
if parse_int is not None:
|
| 367 |
+
kw['parse_int'] = parse_int
|
| 368 |
+
if parse_constant is not None:
|
| 369 |
+
kw['parse_constant'] = parse_constant
|
| 370 |
+
return cls(**kw).decode(s)
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/json/decoder.py
ADDED
|
@@ -0,0 +1,356 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Implementation of JSONDecoder
|
| 2 |
+
"""
|
| 3 |
+
import re
|
| 4 |
+
|
| 5 |
+
from json import scanner
|
| 6 |
+
try:
|
| 7 |
+
from _json import scanstring as c_scanstring
|
| 8 |
+
except ImportError:
|
| 9 |
+
c_scanstring = None
|
| 10 |
+
|
| 11 |
+
__all__ = ['JSONDecoder', 'JSONDecodeError']
|
| 12 |
+
|
| 13 |
+
FLAGS = re.VERBOSE | re.MULTILINE | re.DOTALL
|
| 14 |
+
|
| 15 |
+
NaN = float('nan')
|
| 16 |
+
PosInf = float('inf')
|
| 17 |
+
NegInf = float('-inf')
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
class JSONDecodeError(ValueError):
|
| 21 |
+
"""Subclass of ValueError with the following additional properties:
|
| 22 |
+
|
| 23 |
+
msg: The unformatted error message
|
| 24 |
+
doc: The JSON document being parsed
|
| 25 |
+
pos: The start index of doc where parsing failed
|
| 26 |
+
lineno: The line corresponding to pos
|
| 27 |
+
colno: The column corresponding to pos
|
| 28 |
+
|
| 29 |
+
"""
|
| 30 |
+
# Note that this exception is used from _json
|
| 31 |
+
def __init__(self, msg, doc, pos):
|
| 32 |
+
lineno = doc.count('\n', 0, pos) + 1
|
| 33 |
+
colno = pos - doc.rfind('\n', 0, pos)
|
| 34 |
+
errmsg = '%s: line %d column %d (char %d)' % (msg, lineno, colno, pos)
|
| 35 |
+
ValueError.__init__(self, errmsg)
|
| 36 |
+
self.msg = msg
|
| 37 |
+
self.doc = doc
|
| 38 |
+
self.pos = pos
|
| 39 |
+
self.lineno = lineno
|
| 40 |
+
self.colno = colno
|
| 41 |
+
|
| 42 |
+
def __reduce__(self):
|
| 43 |
+
return self.__class__, (self.msg, self.doc, self.pos)
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
_CONSTANTS = {
|
| 47 |
+
'-Infinity': NegInf,
|
| 48 |
+
'Infinity': PosInf,
|
| 49 |
+
'NaN': NaN,
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
STRINGCHUNK = re.compile(r'(.*?)(["\\\x00-\x1f])', FLAGS)
|
| 54 |
+
BACKSLASH = {
|
| 55 |
+
'"': '"', '\\': '\\', '/': '/',
|
| 56 |
+
'b': '\b', 'f': '\f', 'n': '\n', 'r': '\r', 't': '\t',
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
def _decode_uXXXX(s, pos):
|
| 60 |
+
esc = s[pos + 1:pos + 5]
|
| 61 |
+
if len(esc) == 4 and esc[1] not in 'xX':
|
| 62 |
+
try:
|
| 63 |
+
return int(esc, 16)
|
| 64 |
+
except ValueError:
|
| 65 |
+
pass
|
| 66 |
+
msg = "Invalid \\uXXXX escape"
|
| 67 |
+
raise JSONDecodeError(msg, s, pos)
|
| 68 |
+
|
| 69 |
+
def py_scanstring(s, end, strict=True,
|
| 70 |
+
_b=BACKSLASH, _m=STRINGCHUNK.match):
|
| 71 |
+
"""Scan the string s for a JSON string. End is the index of the
|
| 72 |
+
character in s after the quote that started the JSON string.
|
| 73 |
+
Unescapes all valid JSON string escape sequences and raises ValueError
|
| 74 |
+
on attempt to decode an invalid string. If strict is False then literal
|
| 75 |
+
control characters are allowed in the string.
|
| 76 |
+
|
| 77 |
+
Returns a tuple of the decoded string and the index of the character in s
|
| 78 |
+
after the end quote."""
|
| 79 |
+
chunks = []
|
| 80 |
+
_append = chunks.append
|
| 81 |
+
begin = end - 1
|
| 82 |
+
while 1:
|
| 83 |
+
chunk = _m(s, end)
|
| 84 |
+
if chunk is None:
|
| 85 |
+
raise JSONDecodeError("Unterminated string starting at", s, begin)
|
| 86 |
+
end = chunk.end()
|
| 87 |
+
content, terminator = chunk.groups()
|
| 88 |
+
# Content is contains zero or more unescaped string characters
|
| 89 |
+
if content:
|
| 90 |
+
_append(content)
|
| 91 |
+
# Terminator is the end of string, a literal control character,
|
| 92 |
+
# or a backslash denoting that an escape sequence follows
|
| 93 |
+
if terminator == '"':
|
| 94 |
+
break
|
| 95 |
+
elif terminator != '\\':
|
| 96 |
+
if strict:
|
| 97 |
+
#msg = "Invalid control character %r at" % (terminator,)
|
| 98 |
+
msg = "Invalid control character {0!r} at".format(terminator)
|
| 99 |
+
raise JSONDecodeError(msg, s, end)
|
| 100 |
+
else:
|
| 101 |
+
_append(terminator)
|
| 102 |
+
continue
|
| 103 |
+
try:
|
| 104 |
+
esc = s[end]
|
| 105 |
+
except IndexError:
|
| 106 |
+
raise JSONDecodeError("Unterminated string starting at",
|
| 107 |
+
s, begin) from None
|
| 108 |
+
# If not a unicode escape sequence, must be in the lookup table
|
| 109 |
+
if esc != 'u':
|
| 110 |
+
try:
|
| 111 |
+
char = _b[esc]
|
| 112 |
+
except KeyError:
|
| 113 |
+
msg = "Invalid \\escape: {0!r}".format(esc)
|
| 114 |
+
raise JSONDecodeError(msg, s, end)
|
| 115 |
+
end += 1
|
| 116 |
+
else:
|
| 117 |
+
uni = _decode_uXXXX(s, end)
|
| 118 |
+
end += 5
|
| 119 |
+
if 0xd800 <= uni <= 0xdbff and s[end:end + 2] == '\\u':
|
| 120 |
+
uni2 = _decode_uXXXX(s, end + 1)
|
| 121 |
+
if 0xdc00 <= uni2 <= 0xdfff:
|
| 122 |
+
uni = 0x10000 + (((uni - 0xd800) << 10) | (uni2 - 0xdc00))
|
| 123 |
+
end += 6
|
| 124 |
+
char = chr(uni)
|
| 125 |
+
_append(char)
|
| 126 |
+
return ''.join(chunks), end
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
# Use speedup if available
|
| 130 |
+
scanstring = c_scanstring or py_scanstring
|
| 131 |
+
|
| 132 |
+
WHITESPACE = re.compile(r'[ \t\n\r]*', FLAGS)
|
| 133 |
+
WHITESPACE_STR = ' \t\n\r'
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
def JSONObject(s_and_end, strict, scan_once, object_hook, object_pairs_hook,
|
| 137 |
+
memo=None, _w=WHITESPACE.match, _ws=WHITESPACE_STR):
|
| 138 |
+
s, end = s_and_end
|
| 139 |
+
pairs = []
|
| 140 |
+
pairs_append = pairs.append
|
| 141 |
+
# Backwards compatibility
|
| 142 |
+
if memo is None:
|
| 143 |
+
memo = {}
|
| 144 |
+
memo_get = memo.setdefault
|
| 145 |
+
# Use a slice to prevent IndexError from being raised, the following
|
| 146 |
+
# check will raise a more specific ValueError if the string is empty
|
| 147 |
+
nextchar = s[end:end + 1]
|
| 148 |
+
# Normally we expect nextchar == '"'
|
| 149 |
+
if nextchar != '"':
|
| 150 |
+
if nextchar in _ws:
|
| 151 |
+
end = _w(s, end).end()
|
| 152 |
+
nextchar = s[end:end + 1]
|
| 153 |
+
# Trivial empty object
|
| 154 |
+
if nextchar == '}':
|
| 155 |
+
if object_pairs_hook is not None:
|
| 156 |
+
result = object_pairs_hook(pairs)
|
| 157 |
+
return result, end + 1
|
| 158 |
+
pairs = {}
|
| 159 |
+
if object_hook is not None:
|
| 160 |
+
pairs = object_hook(pairs)
|
| 161 |
+
return pairs, end + 1
|
| 162 |
+
elif nextchar != '"':
|
| 163 |
+
raise JSONDecodeError(
|
| 164 |
+
"Expecting property name enclosed in double quotes", s, end)
|
| 165 |
+
end += 1
|
| 166 |
+
while True:
|
| 167 |
+
key, end = scanstring(s, end, strict)
|
| 168 |
+
key = memo_get(key, key)
|
| 169 |
+
# To skip some function call overhead we optimize the fast paths where
|
| 170 |
+
# the JSON key separator is ": " or just ":".
|
| 171 |
+
if s[end:end + 1] != ':':
|
| 172 |
+
end = _w(s, end).end()
|
| 173 |
+
if s[end:end + 1] != ':':
|
| 174 |
+
raise JSONDecodeError("Expecting ':' delimiter", s, end)
|
| 175 |
+
end += 1
|
| 176 |
+
|
| 177 |
+
try:
|
| 178 |
+
if s[end] in _ws:
|
| 179 |
+
end += 1
|
| 180 |
+
if s[end] in _ws:
|
| 181 |
+
end = _w(s, end + 1).end()
|
| 182 |
+
except IndexError:
|
| 183 |
+
pass
|
| 184 |
+
|
| 185 |
+
try:
|
| 186 |
+
value, end = scan_once(s, end)
|
| 187 |
+
except StopIteration as err:
|
| 188 |
+
raise JSONDecodeError("Expecting value", s, err.value) from None
|
| 189 |
+
pairs_append((key, value))
|
| 190 |
+
try:
|
| 191 |
+
nextchar = s[end]
|
| 192 |
+
if nextchar in _ws:
|
| 193 |
+
end = _w(s, end + 1).end()
|
| 194 |
+
nextchar = s[end]
|
| 195 |
+
except IndexError:
|
| 196 |
+
nextchar = ''
|
| 197 |
+
end += 1
|
| 198 |
+
|
| 199 |
+
if nextchar == '}':
|
| 200 |
+
break
|
| 201 |
+
elif nextchar != ',':
|
| 202 |
+
raise JSONDecodeError("Expecting ',' delimiter", s, end - 1)
|
| 203 |
+
end = _w(s, end).end()
|
| 204 |
+
nextchar = s[end:end + 1]
|
| 205 |
+
end += 1
|
| 206 |
+
if nextchar != '"':
|
| 207 |
+
raise JSONDecodeError(
|
| 208 |
+
"Expecting property name enclosed in double quotes", s, end - 1)
|
| 209 |
+
if object_pairs_hook is not None:
|
| 210 |
+
result = object_pairs_hook(pairs)
|
| 211 |
+
return result, end
|
| 212 |
+
pairs = dict(pairs)
|
| 213 |
+
if object_hook is not None:
|
| 214 |
+
pairs = object_hook(pairs)
|
| 215 |
+
return pairs, end
|
| 216 |
+
|
| 217 |
+
def JSONArray(s_and_end, scan_once, _w=WHITESPACE.match, _ws=WHITESPACE_STR):
|
| 218 |
+
s, end = s_and_end
|
| 219 |
+
values = []
|
| 220 |
+
nextchar = s[end:end + 1]
|
| 221 |
+
if nextchar in _ws:
|
| 222 |
+
end = _w(s, end + 1).end()
|
| 223 |
+
nextchar = s[end:end + 1]
|
| 224 |
+
# Look-ahead for trivial empty array
|
| 225 |
+
if nextchar == ']':
|
| 226 |
+
return values, end + 1
|
| 227 |
+
_append = values.append
|
| 228 |
+
while True:
|
| 229 |
+
try:
|
| 230 |
+
value, end = scan_once(s, end)
|
| 231 |
+
except StopIteration as err:
|
| 232 |
+
raise JSONDecodeError("Expecting value", s, err.value) from None
|
| 233 |
+
_append(value)
|
| 234 |
+
nextchar = s[end:end + 1]
|
| 235 |
+
if nextchar in _ws:
|
| 236 |
+
end = _w(s, end + 1).end()
|
| 237 |
+
nextchar = s[end:end + 1]
|
| 238 |
+
end += 1
|
| 239 |
+
if nextchar == ']':
|
| 240 |
+
break
|
| 241 |
+
elif nextchar != ',':
|
| 242 |
+
raise JSONDecodeError("Expecting ',' delimiter", s, end - 1)
|
| 243 |
+
try:
|
| 244 |
+
if s[end] in _ws:
|
| 245 |
+
end += 1
|
| 246 |
+
if s[end] in _ws:
|
| 247 |
+
end = _w(s, end + 1).end()
|
| 248 |
+
except IndexError:
|
| 249 |
+
pass
|
| 250 |
+
|
| 251 |
+
return values, end
|
| 252 |
+
|
| 253 |
+
|
| 254 |
+
class JSONDecoder(object):
|
| 255 |
+
"""Simple JSON <http://json.org> decoder
|
| 256 |
+
|
| 257 |
+
Performs the following translations in decoding by default:
|
| 258 |
+
|
| 259 |
+
+---------------+-------------------+
|
| 260 |
+
| JSON | Python |
|
| 261 |
+
+===============+===================+
|
| 262 |
+
| object | dict |
|
| 263 |
+
+---------------+-------------------+
|
| 264 |
+
| array | list |
|
| 265 |
+
+---------------+-------------------+
|
| 266 |
+
| string | str |
|
| 267 |
+
+---------------+-------------------+
|
| 268 |
+
| number (int) | int |
|
| 269 |
+
+---------------+-------------------+
|
| 270 |
+
| number (real) | float |
|
| 271 |
+
+---------------+-------------------+
|
| 272 |
+
| true | True |
|
| 273 |
+
+---------------+-------------------+
|
| 274 |
+
| false | False |
|
| 275 |
+
+---------------+-------------------+
|
| 276 |
+
| null | None |
|
| 277 |
+
+---------------+-------------------+
|
| 278 |
+
|
| 279 |
+
It also understands ``NaN``, ``Infinity``, and ``-Infinity`` as
|
| 280 |
+
their corresponding ``float`` values, which is outside the JSON spec.
|
| 281 |
+
|
| 282 |
+
"""
|
| 283 |
+
|
| 284 |
+
def __init__(self, *, object_hook=None, parse_float=None,
|
| 285 |
+
parse_int=None, parse_constant=None, strict=True,
|
| 286 |
+
object_pairs_hook=None):
|
| 287 |
+
"""``object_hook``, if specified, will be called with the result
|
| 288 |
+
of every JSON object decoded and its return value will be used in
|
| 289 |
+
place of the given ``dict``. This can be used to provide custom
|
| 290 |
+
deserializations (e.g. to support JSON-RPC class hinting).
|
| 291 |
+
|
| 292 |
+
``object_pairs_hook``, if specified will be called with the result of
|
| 293 |
+
every JSON object decoded with an ordered list of pairs. The return
|
| 294 |
+
value of ``object_pairs_hook`` will be used instead of the ``dict``.
|
| 295 |
+
This feature can be used to implement custom decoders.
|
| 296 |
+
If ``object_hook`` is also defined, the ``object_pairs_hook`` takes
|
| 297 |
+
priority.
|
| 298 |
+
|
| 299 |
+
``parse_float``, if specified, will be called with the string
|
| 300 |
+
of every JSON float to be decoded. By default this is equivalent to
|
| 301 |
+
float(num_str). This can be used to use another datatype or parser
|
| 302 |
+
for JSON floats (e.g. decimal.Decimal).
|
| 303 |
+
|
| 304 |
+
``parse_int``, if specified, will be called with the string
|
| 305 |
+
of every JSON int to be decoded. By default this is equivalent to
|
| 306 |
+
int(num_str). This can be used to use another datatype or parser
|
| 307 |
+
for JSON integers (e.g. float).
|
| 308 |
+
|
| 309 |
+
``parse_constant``, if specified, will be called with one of the
|
| 310 |
+
following strings: -Infinity, Infinity, NaN.
|
| 311 |
+
This can be used to raise an exception if invalid JSON numbers
|
| 312 |
+
are encountered.
|
| 313 |
+
|
| 314 |
+
If ``strict`` is false (true is the default), then control
|
| 315 |
+
characters will be allowed inside strings. Control characters in
|
| 316 |
+
this context are those with character codes in the 0-31 range,
|
| 317 |
+
including ``'\\t'`` (tab), ``'\\n'``, ``'\\r'`` and ``'\\0'``.
|
| 318 |
+
"""
|
| 319 |
+
self.object_hook = object_hook
|
| 320 |
+
self.parse_float = parse_float or float
|
| 321 |
+
self.parse_int = parse_int or int
|
| 322 |
+
self.parse_constant = parse_constant or _CONSTANTS.__getitem__
|
| 323 |
+
self.strict = strict
|
| 324 |
+
self.object_pairs_hook = object_pairs_hook
|
| 325 |
+
self.parse_object = JSONObject
|
| 326 |
+
self.parse_array = JSONArray
|
| 327 |
+
self.parse_string = scanstring
|
| 328 |
+
self.memo = {}
|
| 329 |
+
self.scan_once = scanner.make_scanner(self)
|
| 330 |
+
|
| 331 |
+
|
| 332 |
+
def decode(self, s, _w=WHITESPACE.match):
|
| 333 |
+
"""Return the Python representation of ``s`` (a ``str`` instance
|
| 334 |
+
containing a JSON document).
|
| 335 |
+
|
| 336 |
+
"""
|
| 337 |
+
obj, end = self.raw_decode(s, idx=_w(s, 0).end())
|
| 338 |
+
end = _w(s, end).end()
|
| 339 |
+
if end != len(s):
|
| 340 |
+
raise JSONDecodeError("Extra data", s, end)
|
| 341 |
+
return obj
|
| 342 |
+
|
| 343 |
+
def raw_decode(self, s, idx=0):
|
| 344 |
+
"""Decode a JSON document from ``s`` (a ``str`` beginning with
|
| 345 |
+
a JSON document) and return a 2-tuple of the Python
|
| 346 |
+
representation and the index in ``s`` where the document ended.
|
| 347 |
+
|
| 348 |
+
This can be used to decode a JSON document from a string that may
|
| 349 |
+
have extraneous data at the end.
|
| 350 |
+
|
| 351 |
+
"""
|
| 352 |
+
try:
|
| 353 |
+
obj, end = self.scan_once(s, idx)
|
| 354 |
+
except StopIteration as err:
|
| 355 |
+
raise JSONDecodeError("Expecting value", s, err.value) from None
|
| 356 |
+
return obj, end
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/json/encoder.py
ADDED
|
@@ -0,0 +1,442 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Implementation of JSONEncoder
|
| 2 |
+
"""
|
| 3 |
+
import re
|
| 4 |
+
|
| 5 |
+
try:
|
| 6 |
+
from _json import encode_basestring_ascii as c_encode_basestring_ascii
|
| 7 |
+
except ImportError:
|
| 8 |
+
c_encode_basestring_ascii = None
|
| 9 |
+
try:
|
| 10 |
+
from _json import encode_basestring as c_encode_basestring
|
| 11 |
+
except ImportError:
|
| 12 |
+
c_encode_basestring = None
|
| 13 |
+
try:
|
| 14 |
+
from _json import make_encoder as c_make_encoder
|
| 15 |
+
except ImportError:
|
| 16 |
+
c_make_encoder = None
|
| 17 |
+
|
| 18 |
+
ESCAPE = re.compile(r'[\x00-\x1f\\"\b\f\n\r\t]')
|
| 19 |
+
ESCAPE_ASCII = re.compile(r'([\\"]|[^\ -~])')
|
| 20 |
+
HAS_UTF8 = re.compile(b'[\x80-\xff]')
|
| 21 |
+
ESCAPE_DCT = {
|
| 22 |
+
'\\': '\\\\',
|
| 23 |
+
'"': '\\"',
|
| 24 |
+
'\b': '\\b',
|
| 25 |
+
'\f': '\\f',
|
| 26 |
+
'\n': '\\n',
|
| 27 |
+
'\r': '\\r',
|
| 28 |
+
'\t': '\\t',
|
| 29 |
+
}
|
| 30 |
+
for i in range(0x20):
|
| 31 |
+
ESCAPE_DCT.setdefault(chr(i), '\\u{0:04x}'.format(i))
|
| 32 |
+
#ESCAPE_DCT.setdefault(chr(i), '\\u%04x' % (i,))
|
| 33 |
+
|
| 34 |
+
INFINITY = float('inf')
|
| 35 |
+
|
| 36 |
+
def py_encode_basestring(s):
|
| 37 |
+
"""Return a JSON representation of a Python string
|
| 38 |
+
|
| 39 |
+
"""
|
| 40 |
+
def replace(match):
|
| 41 |
+
return ESCAPE_DCT[match.group(0)]
|
| 42 |
+
return '"' + ESCAPE.sub(replace, s) + '"'
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
encode_basestring = (c_encode_basestring or py_encode_basestring)
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def py_encode_basestring_ascii(s):
|
| 49 |
+
"""Return an ASCII-only JSON representation of a Python string
|
| 50 |
+
|
| 51 |
+
"""
|
| 52 |
+
def replace(match):
|
| 53 |
+
s = match.group(0)
|
| 54 |
+
try:
|
| 55 |
+
return ESCAPE_DCT[s]
|
| 56 |
+
except KeyError:
|
| 57 |
+
n = ord(s)
|
| 58 |
+
if n < 0x10000:
|
| 59 |
+
return '\\u{0:04x}'.format(n)
|
| 60 |
+
#return '\\u%04x' % (n,)
|
| 61 |
+
else:
|
| 62 |
+
# surrogate pair
|
| 63 |
+
n -= 0x10000
|
| 64 |
+
s1 = 0xd800 | ((n >> 10) & 0x3ff)
|
| 65 |
+
s2 = 0xdc00 | (n & 0x3ff)
|
| 66 |
+
return '\\u{0:04x}\\u{1:04x}'.format(s1, s2)
|
| 67 |
+
return '"' + ESCAPE_ASCII.sub(replace, s) + '"'
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
encode_basestring_ascii = (
|
| 71 |
+
c_encode_basestring_ascii or py_encode_basestring_ascii)
|
| 72 |
+
|
| 73 |
+
class JSONEncoder(object):
|
| 74 |
+
"""Extensible JSON <http://json.org> encoder for Python data structures.
|
| 75 |
+
|
| 76 |
+
Supports the following objects and types by default:
|
| 77 |
+
|
| 78 |
+
+-------------------+---------------+
|
| 79 |
+
| Python | JSON |
|
| 80 |
+
+===================+===============+
|
| 81 |
+
| dict | object |
|
| 82 |
+
+-------------------+---------------+
|
| 83 |
+
| list, tuple | array |
|
| 84 |
+
+-------------------+---------------+
|
| 85 |
+
| str | string |
|
| 86 |
+
+-------------------+---------------+
|
| 87 |
+
| int, float | number |
|
| 88 |
+
+-------------------+---------------+
|
| 89 |
+
| True | true |
|
| 90 |
+
+-------------------+---------------+
|
| 91 |
+
| False | false |
|
| 92 |
+
+-------------------+---------------+
|
| 93 |
+
| None | null |
|
| 94 |
+
+-------------------+---------------+
|
| 95 |
+
|
| 96 |
+
To extend this to recognize other objects, subclass and implement a
|
| 97 |
+
``.default()`` method with another method that returns a serializable
|
| 98 |
+
object for ``o`` if possible, otherwise it should call the superclass
|
| 99 |
+
implementation (to raise ``TypeError``).
|
| 100 |
+
|
| 101 |
+
"""
|
| 102 |
+
item_separator = ', '
|
| 103 |
+
key_separator = ': '
|
| 104 |
+
def __init__(self, *, skipkeys=False, ensure_ascii=True,
|
| 105 |
+
check_circular=True, allow_nan=True, sort_keys=False,
|
| 106 |
+
indent=None, separators=None, default=None):
|
| 107 |
+
"""Constructor for JSONEncoder, with sensible defaults.
|
| 108 |
+
|
| 109 |
+
If skipkeys is false, then it is a TypeError to attempt
|
| 110 |
+
encoding of keys that are not str, int, float or None. If
|
| 111 |
+
skipkeys is True, such items are simply skipped.
|
| 112 |
+
|
| 113 |
+
If ensure_ascii is true, the output is guaranteed to be str
|
| 114 |
+
objects with all incoming non-ASCII characters escaped. If
|
| 115 |
+
ensure_ascii is false, the output can contain non-ASCII characters.
|
| 116 |
+
|
| 117 |
+
If check_circular is true, then lists, dicts, and custom encoded
|
| 118 |
+
objects will be checked for circular references during encoding to
|
| 119 |
+
prevent an infinite recursion (which would cause an OverflowError).
|
| 120 |
+
Otherwise, no such check takes place.
|
| 121 |
+
|
| 122 |
+
If allow_nan is true, then NaN, Infinity, and -Infinity will be
|
| 123 |
+
encoded as such. This behavior is not JSON specification compliant,
|
| 124 |
+
but is consistent with most JavaScript based encoders and decoders.
|
| 125 |
+
Otherwise, it will be a ValueError to encode such floats.
|
| 126 |
+
|
| 127 |
+
If sort_keys is true, then the output of dictionaries will be
|
| 128 |
+
sorted by key; this is useful for regression tests to ensure
|
| 129 |
+
that JSON serializations can be compared on a day-to-day basis.
|
| 130 |
+
|
| 131 |
+
If indent is a non-negative integer, then JSON array
|
| 132 |
+
elements and object members will be pretty-printed with that
|
| 133 |
+
indent level. An indent level of 0 will only insert newlines.
|
| 134 |
+
None is the most compact representation.
|
| 135 |
+
|
| 136 |
+
If specified, separators should be an (item_separator, key_separator)
|
| 137 |
+
tuple. The default is (', ', ': ') if *indent* is ``None`` and
|
| 138 |
+
(',', ': ') otherwise. To get the most compact JSON representation,
|
| 139 |
+
you should specify (',', ':') to eliminate whitespace.
|
| 140 |
+
|
| 141 |
+
If specified, default is a function that gets called for objects
|
| 142 |
+
that can't otherwise be serialized. It should return a JSON encodable
|
| 143 |
+
version of the object or raise a ``TypeError``.
|
| 144 |
+
|
| 145 |
+
"""
|
| 146 |
+
|
| 147 |
+
self.skipkeys = skipkeys
|
| 148 |
+
self.ensure_ascii = ensure_ascii
|
| 149 |
+
self.check_circular = check_circular
|
| 150 |
+
self.allow_nan = allow_nan
|
| 151 |
+
self.sort_keys = sort_keys
|
| 152 |
+
self.indent = indent
|
| 153 |
+
if separators is not None:
|
| 154 |
+
self.item_separator, self.key_separator = separators
|
| 155 |
+
elif indent is not None:
|
| 156 |
+
self.item_separator = ','
|
| 157 |
+
if default is not None:
|
| 158 |
+
self.default = default
|
| 159 |
+
|
| 160 |
+
def default(self, o):
|
| 161 |
+
"""Implement this method in a subclass such that it returns
|
| 162 |
+
a serializable object for ``o``, or calls the base implementation
|
| 163 |
+
(to raise a ``TypeError``).
|
| 164 |
+
|
| 165 |
+
For example, to support arbitrary iterators, you could
|
| 166 |
+
implement default like this::
|
| 167 |
+
|
| 168 |
+
def default(self, o):
|
| 169 |
+
try:
|
| 170 |
+
iterable = iter(o)
|
| 171 |
+
except TypeError:
|
| 172 |
+
pass
|
| 173 |
+
else:
|
| 174 |
+
return list(iterable)
|
| 175 |
+
# Let the base class default method raise the TypeError
|
| 176 |
+
return JSONEncoder.default(self, o)
|
| 177 |
+
|
| 178 |
+
"""
|
| 179 |
+
raise TypeError(f'Object of type {o.__class__.__name__} '
|
| 180 |
+
f'is not JSON serializable')
|
| 181 |
+
|
| 182 |
+
def encode(self, o):
|
| 183 |
+
"""Return a JSON string representation of a Python data structure.
|
| 184 |
+
|
| 185 |
+
>>> from json.encoder import JSONEncoder
|
| 186 |
+
>>> JSONEncoder().encode({"foo": ["bar", "baz"]})
|
| 187 |
+
'{"foo": ["bar", "baz"]}'
|
| 188 |
+
|
| 189 |
+
"""
|
| 190 |
+
# This is for extremely simple cases and benchmarks.
|
| 191 |
+
if isinstance(o, str):
|
| 192 |
+
if self.ensure_ascii:
|
| 193 |
+
return encode_basestring_ascii(o)
|
| 194 |
+
else:
|
| 195 |
+
return encode_basestring(o)
|
| 196 |
+
# This doesn't pass the iterator directly to ''.join() because the
|
| 197 |
+
# exceptions aren't as detailed. The list call should be roughly
|
| 198 |
+
# equivalent to the PySequence_Fast that ''.join() would do.
|
| 199 |
+
chunks = self.iterencode(o, _one_shot=True)
|
| 200 |
+
if not isinstance(chunks, (list, tuple)):
|
| 201 |
+
chunks = list(chunks)
|
| 202 |
+
return ''.join(chunks)
|
| 203 |
+
|
| 204 |
+
def iterencode(self, o, _one_shot=False):
|
| 205 |
+
"""Encode the given object and yield each string
|
| 206 |
+
representation as available.
|
| 207 |
+
|
| 208 |
+
For example::
|
| 209 |
+
|
| 210 |
+
for chunk in JSONEncoder().iterencode(bigobject):
|
| 211 |
+
mysocket.write(chunk)
|
| 212 |
+
|
| 213 |
+
"""
|
| 214 |
+
if self.check_circular:
|
| 215 |
+
markers = {}
|
| 216 |
+
else:
|
| 217 |
+
markers = None
|
| 218 |
+
if self.ensure_ascii:
|
| 219 |
+
_encoder = encode_basestring_ascii
|
| 220 |
+
else:
|
| 221 |
+
_encoder = encode_basestring
|
| 222 |
+
|
| 223 |
+
def floatstr(o, allow_nan=self.allow_nan,
|
| 224 |
+
_repr=float.__repr__, _inf=INFINITY, _neginf=-INFINITY):
|
| 225 |
+
# Check for specials. Note that this type of test is processor
|
| 226 |
+
# and/or platform-specific, so do tests which don't depend on the
|
| 227 |
+
# internals.
|
| 228 |
+
|
| 229 |
+
if o != o:
|
| 230 |
+
text = 'NaN'
|
| 231 |
+
elif o == _inf:
|
| 232 |
+
text = 'Infinity'
|
| 233 |
+
elif o == _neginf:
|
| 234 |
+
text = '-Infinity'
|
| 235 |
+
else:
|
| 236 |
+
return _repr(o)
|
| 237 |
+
|
| 238 |
+
if not allow_nan:
|
| 239 |
+
raise ValueError(
|
| 240 |
+
"Out of range float values are not JSON compliant: " +
|
| 241 |
+
repr(o))
|
| 242 |
+
|
| 243 |
+
return text
|
| 244 |
+
|
| 245 |
+
|
| 246 |
+
if (_one_shot and c_make_encoder is not None
|
| 247 |
+
and self.indent is None):
|
| 248 |
+
_iterencode = c_make_encoder(
|
| 249 |
+
markers, self.default, _encoder, self.indent,
|
| 250 |
+
self.key_separator, self.item_separator, self.sort_keys,
|
| 251 |
+
self.skipkeys, self.allow_nan)
|
| 252 |
+
else:
|
| 253 |
+
_iterencode = _make_iterencode(
|
| 254 |
+
markers, self.default, _encoder, self.indent, floatstr,
|
| 255 |
+
self.key_separator, self.item_separator, self.sort_keys,
|
| 256 |
+
self.skipkeys, _one_shot)
|
| 257 |
+
return _iterencode(o, 0)
|
| 258 |
+
|
| 259 |
+
def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,
|
| 260 |
+
_key_separator, _item_separator, _sort_keys, _skipkeys, _one_shot,
|
| 261 |
+
## HACK: hand-optimized bytecode; turn globals into locals
|
| 262 |
+
ValueError=ValueError,
|
| 263 |
+
dict=dict,
|
| 264 |
+
float=float,
|
| 265 |
+
id=id,
|
| 266 |
+
int=int,
|
| 267 |
+
isinstance=isinstance,
|
| 268 |
+
list=list,
|
| 269 |
+
str=str,
|
| 270 |
+
tuple=tuple,
|
| 271 |
+
_intstr=int.__repr__,
|
| 272 |
+
):
|
| 273 |
+
|
| 274 |
+
if _indent is not None and not isinstance(_indent, str):
|
| 275 |
+
_indent = ' ' * _indent
|
| 276 |
+
|
| 277 |
+
def _iterencode_list(lst, _current_indent_level):
|
| 278 |
+
if not lst:
|
| 279 |
+
yield '[]'
|
| 280 |
+
return
|
| 281 |
+
if markers is not None:
|
| 282 |
+
markerid = id(lst)
|
| 283 |
+
if markerid in markers:
|
| 284 |
+
raise ValueError("Circular reference detected")
|
| 285 |
+
markers[markerid] = lst
|
| 286 |
+
buf = '['
|
| 287 |
+
if _indent is not None:
|
| 288 |
+
_current_indent_level += 1
|
| 289 |
+
newline_indent = '\n' + _indent * _current_indent_level
|
| 290 |
+
separator = _item_separator + newline_indent
|
| 291 |
+
buf += newline_indent
|
| 292 |
+
else:
|
| 293 |
+
newline_indent = None
|
| 294 |
+
separator = _item_separator
|
| 295 |
+
first = True
|
| 296 |
+
for value in lst:
|
| 297 |
+
if first:
|
| 298 |
+
first = False
|
| 299 |
+
else:
|
| 300 |
+
buf = separator
|
| 301 |
+
if isinstance(value, str):
|
| 302 |
+
yield buf + _encoder(value)
|
| 303 |
+
elif value is None:
|
| 304 |
+
yield buf + 'null'
|
| 305 |
+
elif value is True:
|
| 306 |
+
yield buf + 'true'
|
| 307 |
+
elif value is False:
|
| 308 |
+
yield buf + 'false'
|
| 309 |
+
elif isinstance(value, int):
|
| 310 |
+
# Subclasses of int/float may override __repr__, but we still
|
| 311 |
+
# want to encode them as integers/floats in JSON. One example
|
| 312 |
+
# within the standard library is IntEnum.
|
| 313 |
+
yield buf + _intstr(value)
|
| 314 |
+
elif isinstance(value, float):
|
| 315 |
+
# see comment above for int
|
| 316 |
+
yield buf + _floatstr(value)
|
| 317 |
+
else:
|
| 318 |
+
yield buf
|
| 319 |
+
if isinstance(value, (list, tuple)):
|
| 320 |
+
chunks = _iterencode_list(value, _current_indent_level)
|
| 321 |
+
elif isinstance(value, dict):
|
| 322 |
+
chunks = _iterencode_dict(value, _current_indent_level)
|
| 323 |
+
else:
|
| 324 |
+
chunks = _iterencode(value, _current_indent_level)
|
| 325 |
+
yield from chunks
|
| 326 |
+
if newline_indent is not None:
|
| 327 |
+
_current_indent_level -= 1
|
| 328 |
+
yield '\n' + _indent * _current_indent_level
|
| 329 |
+
yield ']'
|
| 330 |
+
if markers is not None:
|
| 331 |
+
del markers[markerid]
|
| 332 |
+
|
| 333 |
+
def _iterencode_dict(dct, _current_indent_level):
|
| 334 |
+
if not dct:
|
| 335 |
+
yield '{}'
|
| 336 |
+
return
|
| 337 |
+
if markers is not None:
|
| 338 |
+
markerid = id(dct)
|
| 339 |
+
if markerid in markers:
|
| 340 |
+
raise ValueError("Circular reference detected")
|
| 341 |
+
markers[markerid] = dct
|
| 342 |
+
yield '{'
|
| 343 |
+
if _indent is not None:
|
| 344 |
+
_current_indent_level += 1
|
| 345 |
+
newline_indent = '\n' + _indent * _current_indent_level
|
| 346 |
+
item_separator = _item_separator + newline_indent
|
| 347 |
+
yield newline_indent
|
| 348 |
+
else:
|
| 349 |
+
newline_indent = None
|
| 350 |
+
item_separator = _item_separator
|
| 351 |
+
first = True
|
| 352 |
+
if _sort_keys:
|
| 353 |
+
items = sorted(dct.items())
|
| 354 |
+
else:
|
| 355 |
+
items = dct.items()
|
| 356 |
+
for key, value in items:
|
| 357 |
+
if isinstance(key, str):
|
| 358 |
+
pass
|
| 359 |
+
# JavaScript is weakly typed for these, so it makes sense to
|
| 360 |
+
# also allow them. Many encoders seem to do something like this.
|
| 361 |
+
elif isinstance(key, float):
|
| 362 |
+
# see comment for int/float in _make_iterencode
|
| 363 |
+
key = _floatstr(key)
|
| 364 |
+
elif key is True:
|
| 365 |
+
key = 'true'
|
| 366 |
+
elif key is False:
|
| 367 |
+
key = 'false'
|
| 368 |
+
elif key is None:
|
| 369 |
+
key = 'null'
|
| 370 |
+
elif isinstance(key, int):
|
| 371 |
+
# see comment for int/float in _make_iterencode
|
| 372 |
+
key = _intstr(key)
|
| 373 |
+
elif _skipkeys:
|
| 374 |
+
continue
|
| 375 |
+
else:
|
| 376 |
+
raise TypeError(f'keys must be str, int, float, bool or None, '
|
| 377 |
+
f'not {key.__class__.__name__}')
|
| 378 |
+
if first:
|
| 379 |
+
first = False
|
| 380 |
+
else:
|
| 381 |
+
yield item_separator
|
| 382 |
+
yield _encoder(key)
|
| 383 |
+
yield _key_separator
|
| 384 |
+
if isinstance(value, str):
|
| 385 |
+
yield _encoder(value)
|
| 386 |
+
elif value is None:
|
| 387 |
+
yield 'null'
|
| 388 |
+
elif value is True:
|
| 389 |
+
yield 'true'
|
| 390 |
+
elif value is False:
|
| 391 |
+
yield 'false'
|
| 392 |
+
elif isinstance(value, int):
|
| 393 |
+
# see comment for int/float in _make_iterencode
|
| 394 |
+
yield _intstr(value)
|
| 395 |
+
elif isinstance(value, float):
|
| 396 |
+
# see comment for int/float in _make_iterencode
|
| 397 |
+
yield _floatstr(value)
|
| 398 |
+
else:
|
| 399 |
+
if isinstance(value, (list, tuple)):
|
| 400 |
+
chunks = _iterencode_list(value, _current_indent_level)
|
| 401 |
+
elif isinstance(value, dict):
|
| 402 |
+
chunks = _iterencode_dict(value, _current_indent_level)
|
| 403 |
+
else:
|
| 404 |
+
chunks = _iterencode(value, _current_indent_level)
|
| 405 |
+
yield from chunks
|
| 406 |
+
if newline_indent is not None:
|
| 407 |
+
_current_indent_level -= 1
|
| 408 |
+
yield '\n' + _indent * _current_indent_level
|
| 409 |
+
yield '}'
|
| 410 |
+
if markers is not None:
|
| 411 |
+
del markers[markerid]
|
| 412 |
+
|
| 413 |
+
def _iterencode(o, _current_indent_level):
|
| 414 |
+
if isinstance(o, str):
|
| 415 |
+
yield _encoder(o)
|
| 416 |
+
elif o is None:
|
| 417 |
+
yield 'null'
|
| 418 |
+
elif o is True:
|
| 419 |
+
yield 'true'
|
| 420 |
+
elif o is False:
|
| 421 |
+
yield 'false'
|
| 422 |
+
elif isinstance(o, int):
|
| 423 |
+
# see comment for int/float in _make_iterencode
|
| 424 |
+
yield _intstr(o)
|
| 425 |
+
elif isinstance(o, float):
|
| 426 |
+
# see comment for int/float in _make_iterencode
|
| 427 |
+
yield _floatstr(o)
|
| 428 |
+
elif isinstance(o, (list, tuple)):
|
| 429 |
+
yield from _iterencode_list(o, _current_indent_level)
|
| 430 |
+
elif isinstance(o, dict):
|
| 431 |
+
yield from _iterencode_dict(o, _current_indent_level)
|
| 432 |
+
else:
|
| 433 |
+
if markers is not None:
|
| 434 |
+
markerid = id(o)
|
| 435 |
+
if markerid in markers:
|
| 436 |
+
raise ValueError("Circular reference detected")
|
| 437 |
+
markers[markerid] = o
|
| 438 |
+
o = _default(o)
|
| 439 |
+
yield from _iterencode(o, _current_indent_level)
|
| 440 |
+
if markers is not None:
|
| 441 |
+
del markers[markerid]
|
| 442 |
+
return _iterencode
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/json/scanner.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""JSON token scanner
|
| 2 |
+
"""
|
| 3 |
+
import re
|
| 4 |
+
try:
|
| 5 |
+
from _json import make_scanner as c_make_scanner
|
| 6 |
+
except ImportError:
|
| 7 |
+
c_make_scanner = None
|
| 8 |
+
|
| 9 |
+
__all__ = ['make_scanner']
|
| 10 |
+
|
| 11 |
+
NUMBER_RE = re.compile(
|
| 12 |
+
r'(-?(?:0|[1-9]\d*))(\.\d+)?([eE][-+]?\d+)?',
|
| 13 |
+
(re.VERBOSE | re.MULTILINE | re.DOTALL))
|
| 14 |
+
|
| 15 |
+
def py_make_scanner(context):
|
| 16 |
+
parse_object = context.parse_object
|
| 17 |
+
parse_array = context.parse_array
|
| 18 |
+
parse_string = context.parse_string
|
| 19 |
+
match_number = NUMBER_RE.match
|
| 20 |
+
strict = context.strict
|
| 21 |
+
parse_float = context.parse_float
|
| 22 |
+
parse_int = context.parse_int
|
| 23 |
+
parse_constant = context.parse_constant
|
| 24 |
+
object_hook = context.object_hook
|
| 25 |
+
object_pairs_hook = context.object_pairs_hook
|
| 26 |
+
memo = context.memo
|
| 27 |
+
|
| 28 |
+
def _scan_once(string, idx):
|
| 29 |
+
try:
|
| 30 |
+
nextchar = string[idx]
|
| 31 |
+
except IndexError:
|
| 32 |
+
raise StopIteration(idx) from None
|
| 33 |
+
|
| 34 |
+
if nextchar == '"':
|
| 35 |
+
return parse_string(string, idx + 1, strict)
|
| 36 |
+
elif nextchar == '{':
|
| 37 |
+
return parse_object((string, idx + 1), strict,
|
| 38 |
+
_scan_once, object_hook, object_pairs_hook, memo)
|
| 39 |
+
elif nextchar == '[':
|
| 40 |
+
return parse_array((string, idx + 1), _scan_once)
|
| 41 |
+
elif nextchar == 'n' and string[idx:idx + 4] == 'null':
|
| 42 |
+
return None, idx + 4
|
| 43 |
+
elif nextchar == 't' and string[idx:idx + 4] == 'true':
|
| 44 |
+
return True, idx + 4
|
| 45 |
+
elif nextchar == 'f' and string[idx:idx + 5] == 'false':
|
| 46 |
+
return False, idx + 5
|
| 47 |
+
|
| 48 |
+
m = match_number(string, idx)
|
| 49 |
+
if m is not None:
|
| 50 |
+
integer, frac, exp = m.groups()
|
| 51 |
+
if frac or exp:
|
| 52 |
+
res = parse_float(integer + (frac or '') + (exp or ''))
|
| 53 |
+
else:
|
| 54 |
+
res = parse_int(integer)
|
| 55 |
+
return res, m.end()
|
| 56 |
+
elif nextchar == 'N' and string[idx:idx + 3] == 'NaN':
|
| 57 |
+
return parse_constant('NaN'), idx + 3
|
| 58 |
+
elif nextchar == 'I' and string[idx:idx + 8] == 'Infinity':
|
| 59 |
+
return parse_constant('Infinity'), idx + 8
|
| 60 |
+
elif nextchar == '-' and string[idx:idx + 9] == '-Infinity':
|
| 61 |
+
return parse_constant('-Infinity'), idx + 9
|
| 62 |
+
else:
|
| 63 |
+
raise StopIteration(idx)
|
| 64 |
+
|
| 65 |
+
def scan_once(string, idx):
|
| 66 |
+
try:
|
| 67 |
+
return _scan_once(string, idx)
|
| 68 |
+
finally:
|
| 69 |
+
memo.clear()
|
| 70 |
+
|
| 71 |
+
return scan_once
|
| 72 |
+
|
| 73 |
+
make_scanner = c_make_scanner or py_make_scanner
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/json/tool.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
r"""Command-line tool to validate and pretty-print JSON
|
| 2 |
+
|
| 3 |
+
Usage::
|
| 4 |
+
|
| 5 |
+
$ echo '{"json":"obj"}' | python -m json.tool
|
| 6 |
+
{
|
| 7 |
+
"json": "obj"
|
| 8 |
+
}
|
| 9 |
+
$ echo '{ 1.2:3.4}' | python -m json.tool
|
| 10 |
+
Expecting property name enclosed in double quotes: line 1 column 3 (char 2)
|
| 11 |
+
|
| 12 |
+
"""
|
| 13 |
+
import argparse
|
| 14 |
+
import json
|
| 15 |
+
import sys
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def main():
|
| 19 |
+
prog = 'python -m json.tool'
|
| 20 |
+
description = ('A simple command line interface for json module '
|
| 21 |
+
'to validate and pretty-print JSON objects.')
|
| 22 |
+
parser = argparse.ArgumentParser(prog=prog, description=description)
|
| 23 |
+
parser.add_argument('infile', nargs='?',
|
| 24 |
+
type=argparse.FileType(encoding="utf-8"),
|
| 25 |
+
help='a JSON file to be validated or pretty-printed',
|
| 26 |
+
default=sys.stdin)
|
| 27 |
+
parser.add_argument('outfile', nargs='?',
|
| 28 |
+
type=argparse.FileType('w', encoding="utf-8"),
|
| 29 |
+
help='write the output of infile to outfile',
|
| 30 |
+
default=sys.stdout)
|
| 31 |
+
parser.add_argument('--sort-keys', action='store_true', default=False,
|
| 32 |
+
help='sort the output of dictionaries alphabetically by key')
|
| 33 |
+
parser.add_argument('--json-lines', action='store_true', default=False,
|
| 34 |
+
help='parse input using the jsonlines format')
|
| 35 |
+
options = parser.parse_args()
|
| 36 |
+
|
| 37 |
+
infile = options.infile
|
| 38 |
+
outfile = options.outfile
|
| 39 |
+
sort_keys = options.sort_keys
|
| 40 |
+
json_lines = options.json_lines
|
| 41 |
+
with infile, outfile:
|
| 42 |
+
try:
|
| 43 |
+
if json_lines:
|
| 44 |
+
objs = (json.loads(line) for line in infile)
|
| 45 |
+
else:
|
| 46 |
+
objs = (json.load(infile), )
|
| 47 |
+
for obj in objs:
|
| 48 |
+
json.dump(obj, outfile, sort_keys=sort_keys, indent=4)
|
| 49 |
+
outfile.write('\n')
|
| 50 |
+
except ValueError as e:
|
| 51 |
+
raise SystemExit(e)
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
if __name__ == '__main__':
|
| 55 |
+
try:
|
| 56 |
+
main()
|
| 57 |
+
except BrokenPipeError as exc:
|
| 58 |
+
sys.exit(exc.errno)
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/sqlite3/dbapi2.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# pysqlite2/dbapi2.py: the DB-API 2.0 interface
|
| 2 |
+
#
|
| 3 |
+
# Copyright (C) 2004-2005 Gerhard Häring <gh@ghaering.de>
|
| 4 |
+
#
|
| 5 |
+
# This file is part of pysqlite.
|
| 6 |
+
#
|
| 7 |
+
# This software is provided 'as-is', without any express or implied
|
| 8 |
+
# warranty. In no event will the authors be held liable for any damages
|
| 9 |
+
# arising from the use of this software.
|
| 10 |
+
#
|
| 11 |
+
# Permission is granted to anyone to use this software for any purpose,
|
| 12 |
+
# including commercial applications, and to alter it and redistribute it
|
| 13 |
+
# freely, subject to the following restrictions:
|
| 14 |
+
#
|
| 15 |
+
# 1. The origin of this software must not be misrepresented; you must not
|
| 16 |
+
# claim that you wrote the original software. If you use this software
|
| 17 |
+
# in a product, an acknowledgment in the product documentation would be
|
| 18 |
+
# appreciated but is not required.
|
| 19 |
+
# 2. Altered source versions must be plainly marked as such, and must not be
|
| 20 |
+
# misrepresented as being the original software.
|
| 21 |
+
# 3. This notice may not be removed or altered from any source distribution.
|
| 22 |
+
|
| 23 |
+
import datetime
|
| 24 |
+
import time
|
| 25 |
+
import collections.abc
|
| 26 |
+
|
| 27 |
+
from _sqlite3 import *
|
| 28 |
+
|
| 29 |
+
paramstyle = "qmark"
|
| 30 |
+
|
| 31 |
+
threadsafety = 1
|
| 32 |
+
|
| 33 |
+
apilevel = "2.0"
|
| 34 |
+
|
| 35 |
+
Date = datetime.date
|
| 36 |
+
|
| 37 |
+
Time = datetime.time
|
| 38 |
+
|
| 39 |
+
Timestamp = datetime.datetime
|
| 40 |
+
|
| 41 |
+
def DateFromTicks(ticks):
|
| 42 |
+
return Date(*time.localtime(ticks)[:3])
|
| 43 |
+
|
| 44 |
+
def TimeFromTicks(ticks):
|
| 45 |
+
return Time(*time.localtime(ticks)[3:6])
|
| 46 |
+
|
| 47 |
+
def TimestampFromTicks(ticks):
|
| 48 |
+
return Timestamp(*time.localtime(ticks)[:6])
|
| 49 |
+
|
| 50 |
+
version_info = tuple([int(x) for x in version.split(".")])
|
| 51 |
+
sqlite_version_info = tuple([int(x) for x in sqlite_version.split(".")])
|
| 52 |
+
|
| 53 |
+
Binary = memoryview
|
| 54 |
+
collections.abc.Sequence.register(Row)
|
| 55 |
+
|
| 56 |
+
def register_adapters_and_converters():
|
| 57 |
+
def adapt_date(val):
|
| 58 |
+
return val.isoformat()
|
| 59 |
+
|
| 60 |
+
def adapt_datetime(val):
|
| 61 |
+
return val.isoformat(" ")
|
| 62 |
+
|
| 63 |
+
def convert_date(val):
|
| 64 |
+
return datetime.date(*map(int, val.split(b"-")))
|
| 65 |
+
|
| 66 |
+
def convert_timestamp(val):
|
| 67 |
+
datepart, timepart = val.split(b" ")
|
| 68 |
+
year, month, day = map(int, datepart.split(b"-"))
|
| 69 |
+
timepart_full = timepart.split(b".")
|
| 70 |
+
hours, minutes, seconds = map(int, timepart_full[0].split(b":"))
|
| 71 |
+
if len(timepart_full) == 2:
|
| 72 |
+
microseconds = int('{:0<6.6}'.format(timepart_full[1].decode()))
|
| 73 |
+
else:
|
| 74 |
+
microseconds = 0
|
| 75 |
+
|
| 76 |
+
val = datetime.datetime(year, month, day, hours, minutes, seconds, microseconds)
|
| 77 |
+
return val
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
register_adapter(datetime.date, adapt_date)
|
| 81 |
+
register_adapter(datetime.datetime, adapt_datetime)
|
| 82 |
+
register_converter("date", convert_date)
|
| 83 |
+
register_converter("timestamp", convert_timestamp)
|
| 84 |
+
|
| 85 |
+
register_adapters_and_converters()
|
| 86 |
+
|
| 87 |
+
# Clean up namespace
|
| 88 |
+
|
| 89 |
+
del(register_adapters_and_converters)
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/test/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
# Dummy file to make this directory a package.
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/test/test_script_helper.py
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Unittests for test.support.script_helper. Who tests the test helper?"""
|
| 2 |
+
|
| 3 |
+
import subprocess
|
| 4 |
+
import sys
|
| 5 |
+
import os
|
| 6 |
+
from test.support import script_helper
|
| 7 |
+
import unittest
|
| 8 |
+
from unittest import mock
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class TestScriptHelper(unittest.TestCase):
|
| 12 |
+
|
| 13 |
+
def test_assert_python_ok(self):
|
| 14 |
+
t = script_helper.assert_python_ok('-c', 'import sys; sys.exit(0)')
|
| 15 |
+
self.assertEqual(0, t[0], 'return code was not 0')
|
| 16 |
+
|
| 17 |
+
def test_assert_python_failure(self):
|
| 18 |
+
# I didn't import the sys module so this child will fail.
|
| 19 |
+
rc, out, err = script_helper.assert_python_failure('-c', 'sys.exit(0)')
|
| 20 |
+
self.assertNotEqual(0, rc, 'return code should not be 0')
|
| 21 |
+
|
| 22 |
+
def test_assert_python_ok_raises(self):
|
| 23 |
+
# I didn't import the sys module so this child will fail.
|
| 24 |
+
with self.assertRaises(AssertionError) as error_context:
|
| 25 |
+
script_helper.assert_python_ok('-c', 'sys.exit(0)')
|
| 26 |
+
error_msg = str(error_context.exception)
|
| 27 |
+
self.assertIn('command line:', error_msg)
|
| 28 |
+
self.assertIn('sys.exit(0)', error_msg, msg='unexpected command line')
|
| 29 |
+
|
| 30 |
+
def test_assert_python_failure_raises(self):
|
| 31 |
+
with self.assertRaises(AssertionError) as error_context:
|
| 32 |
+
script_helper.assert_python_failure('-c', 'import sys; sys.exit(0)')
|
| 33 |
+
error_msg = str(error_context.exception)
|
| 34 |
+
self.assertIn('Process return code is 0\n', error_msg)
|
| 35 |
+
self.assertIn('import sys; sys.exit(0)', error_msg,
|
| 36 |
+
msg='unexpected command line.')
|
| 37 |
+
|
| 38 |
+
@mock.patch('subprocess.Popen')
|
| 39 |
+
def test_assert_python_isolated_when_env_not_required(self, mock_popen):
|
| 40 |
+
with mock.patch.object(script_helper,
|
| 41 |
+
'interpreter_requires_environment',
|
| 42 |
+
return_value=False) as mock_ire_func:
|
| 43 |
+
mock_popen.side_effect = RuntimeError('bail out of unittest')
|
| 44 |
+
try:
|
| 45 |
+
script_helper._assert_python(True, '-c', 'None')
|
| 46 |
+
except RuntimeError as err:
|
| 47 |
+
self.assertEqual('bail out of unittest', err.args[0])
|
| 48 |
+
self.assertEqual(1, mock_popen.call_count)
|
| 49 |
+
self.assertEqual(1, mock_ire_func.call_count)
|
| 50 |
+
popen_command = mock_popen.call_args[0][0]
|
| 51 |
+
self.assertEqual(sys.executable, popen_command[0])
|
| 52 |
+
self.assertIn('None', popen_command)
|
| 53 |
+
self.assertIn('-I', popen_command)
|
| 54 |
+
self.assertNotIn('-E', popen_command) # -I overrides this
|
| 55 |
+
|
| 56 |
+
@mock.patch('subprocess.Popen')
|
| 57 |
+
def test_assert_python_not_isolated_when_env_is_required(self, mock_popen):
|
| 58 |
+
"""Ensure that -I is not passed when the environment is required."""
|
| 59 |
+
with mock.patch.object(script_helper,
|
| 60 |
+
'interpreter_requires_environment',
|
| 61 |
+
return_value=True) as mock_ire_func:
|
| 62 |
+
mock_popen.side_effect = RuntimeError('bail out of unittest')
|
| 63 |
+
try:
|
| 64 |
+
script_helper._assert_python(True, '-c', 'None')
|
| 65 |
+
except RuntimeError as err:
|
| 66 |
+
self.assertEqual('bail out of unittest', err.args[0])
|
| 67 |
+
popen_command = mock_popen.call_args[0][0]
|
| 68 |
+
self.assertNotIn('-I', popen_command)
|
| 69 |
+
self.assertNotIn('-E', popen_command)
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
class TestScriptHelperEnvironment(unittest.TestCase):
|
| 73 |
+
"""Code coverage for interpreter_requires_environment()."""
|
| 74 |
+
|
| 75 |
+
def setUp(self):
|
| 76 |
+
self.assertTrue(
|
| 77 |
+
hasattr(script_helper, '__cached_interp_requires_environment'))
|
| 78 |
+
# Reset the private cached state.
|
| 79 |
+
script_helper.__dict__['__cached_interp_requires_environment'] = None
|
| 80 |
+
|
| 81 |
+
def tearDown(self):
|
| 82 |
+
# Reset the private cached state.
|
| 83 |
+
script_helper.__dict__['__cached_interp_requires_environment'] = None
|
| 84 |
+
|
| 85 |
+
@mock.patch('subprocess.check_call')
|
| 86 |
+
def test_interpreter_requires_environment_true(self, mock_check_call):
|
| 87 |
+
with mock.patch.dict(os.environ):
|
| 88 |
+
os.environ.pop('PYTHONHOME', None)
|
| 89 |
+
mock_check_call.side_effect = subprocess.CalledProcessError('', '')
|
| 90 |
+
self.assertTrue(script_helper.interpreter_requires_environment())
|
| 91 |
+
self.assertTrue(script_helper.interpreter_requires_environment())
|
| 92 |
+
self.assertEqual(1, mock_check_call.call_count)
|
| 93 |
+
|
| 94 |
+
@mock.patch('subprocess.check_call')
|
| 95 |
+
def test_interpreter_requires_environment_false(self, mock_check_call):
|
| 96 |
+
with mock.patch.dict(os.environ):
|
| 97 |
+
os.environ.pop('PYTHONHOME', None)
|
| 98 |
+
# The mocked subprocess.check_call fakes a no-error process.
|
| 99 |
+
script_helper.interpreter_requires_environment()
|
| 100 |
+
self.assertFalse(script_helper.interpreter_requires_environment())
|
| 101 |
+
self.assertEqual(1, mock_check_call.call_count)
|
| 102 |
+
|
| 103 |
+
@mock.patch('subprocess.check_call')
|
| 104 |
+
def test_interpreter_requires_environment_details(self, mock_check_call):
|
| 105 |
+
with mock.patch.dict(os.environ):
|
| 106 |
+
os.environ.pop('PYTHONHOME', None)
|
| 107 |
+
script_helper.interpreter_requires_environment()
|
| 108 |
+
self.assertFalse(script_helper.interpreter_requires_environment())
|
| 109 |
+
self.assertFalse(script_helper.interpreter_requires_environment())
|
| 110 |
+
self.assertEqual(1, mock_check_call.call_count)
|
| 111 |
+
check_call_command = mock_check_call.call_args[0][0]
|
| 112 |
+
self.assertEqual(sys.executable, check_call_command[0])
|
| 113 |
+
self.assertIn('-E', check_call_command)
|
| 114 |
+
|
| 115 |
+
@mock.patch('subprocess.check_call')
|
| 116 |
+
def test_interpreter_requires_environment_with_pythonhome(self, mock_check_call):
|
| 117 |
+
with mock.patch.dict(os.environ):
|
| 118 |
+
os.environ['PYTHONHOME'] = 'MockedHome'
|
| 119 |
+
self.assertTrue(script_helper.interpreter_requires_environment())
|
| 120 |
+
self.assertTrue(script_helper.interpreter_requires_environment())
|
| 121 |
+
self.assertEqual(0, mock_check_call.call_count)
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
if __name__ == '__main__':
|
| 125 |
+
unittest.main()
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/test/test_support.py
ADDED
|
@@ -0,0 +1,689 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import contextlib
|
| 2 |
+
import errno
|
| 3 |
+
import importlib
|
| 4 |
+
import io
|
| 5 |
+
import os
|
| 6 |
+
import shutil
|
| 7 |
+
import socket
|
| 8 |
+
import stat
|
| 9 |
+
import subprocess
|
| 10 |
+
import sys
|
| 11 |
+
import tempfile
|
| 12 |
+
import textwrap
|
| 13 |
+
import time
|
| 14 |
+
import unittest
|
| 15 |
+
from test import support
|
| 16 |
+
from test.support import script_helper
|
| 17 |
+
|
| 18 |
+
TESTFN = support.TESTFN
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
class TestSupport(unittest.TestCase):
|
| 22 |
+
|
| 23 |
+
def test_import_module(self):
|
| 24 |
+
support.import_module("ftplib")
|
| 25 |
+
self.assertRaises(unittest.SkipTest, support.import_module, "foo")
|
| 26 |
+
|
| 27 |
+
def test_import_fresh_module(self):
|
| 28 |
+
support.import_fresh_module("ftplib")
|
| 29 |
+
|
| 30 |
+
def test_get_attribute(self):
|
| 31 |
+
self.assertEqual(support.get_attribute(self, "test_get_attribute"),
|
| 32 |
+
self.test_get_attribute)
|
| 33 |
+
self.assertRaises(unittest.SkipTest, support.get_attribute, self, "foo")
|
| 34 |
+
|
| 35 |
+
@unittest.skip("failing buildbots")
|
| 36 |
+
def test_get_original_stdout(self):
|
| 37 |
+
self.assertEqual(support.get_original_stdout(), sys.stdout)
|
| 38 |
+
|
| 39 |
+
def test_unload(self):
|
| 40 |
+
import sched
|
| 41 |
+
self.assertIn("sched", sys.modules)
|
| 42 |
+
support.unload("sched")
|
| 43 |
+
self.assertNotIn("sched", sys.modules)
|
| 44 |
+
|
| 45 |
+
def test_unlink(self):
|
| 46 |
+
with open(TESTFN, "w") as f:
|
| 47 |
+
pass
|
| 48 |
+
support.unlink(TESTFN)
|
| 49 |
+
self.assertFalse(os.path.exists(TESTFN))
|
| 50 |
+
support.unlink(TESTFN)
|
| 51 |
+
|
| 52 |
+
def test_rmtree(self):
|
| 53 |
+
dirpath = support.TESTFN + 'd'
|
| 54 |
+
subdirpath = os.path.join(dirpath, 'subdir')
|
| 55 |
+
os.mkdir(dirpath)
|
| 56 |
+
os.mkdir(subdirpath)
|
| 57 |
+
support.rmtree(dirpath)
|
| 58 |
+
self.assertFalse(os.path.exists(dirpath))
|
| 59 |
+
with support.swap_attr(support, 'verbose', 0):
|
| 60 |
+
support.rmtree(dirpath)
|
| 61 |
+
|
| 62 |
+
os.mkdir(dirpath)
|
| 63 |
+
os.mkdir(subdirpath)
|
| 64 |
+
os.chmod(dirpath, stat.S_IRUSR|stat.S_IXUSR)
|
| 65 |
+
with support.swap_attr(support, 'verbose', 0):
|
| 66 |
+
support.rmtree(dirpath)
|
| 67 |
+
self.assertFalse(os.path.exists(dirpath))
|
| 68 |
+
|
| 69 |
+
os.mkdir(dirpath)
|
| 70 |
+
os.mkdir(subdirpath)
|
| 71 |
+
os.chmod(dirpath, 0)
|
| 72 |
+
with support.swap_attr(support, 'verbose', 0):
|
| 73 |
+
support.rmtree(dirpath)
|
| 74 |
+
self.assertFalse(os.path.exists(dirpath))
|
| 75 |
+
|
| 76 |
+
def test_forget(self):
|
| 77 |
+
mod_filename = TESTFN + '.py'
|
| 78 |
+
with open(mod_filename, 'w') as f:
|
| 79 |
+
print('foo = 1', file=f)
|
| 80 |
+
sys.path.insert(0, os.curdir)
|
| 81 |
+
importlib.invalidate_caches()
|
| 82 |
+
try:
|
| 83 |
+
mod = __import__(TESTFN)
|
| 84 |
+
self.assertIn(TESTFN, sys.modules)
|
| 85 |
+
|
| 86 |
+
support.forget(TESTFN)
|
| 87 |
+
self.assertNotIn(TESTFN, sys.modules)
|
| 88 |
+
finally:
|
| 89 |
+
del sys.path[0]
|
| 90 |
+
support.unlink(mod_filename)
|
| 91 |
+
support.rmtree('__pycache__')
|
| 92 |
+
|
| 93 |
+
def test_HOST(self):
|
| 94 |
+
s = socket.create_server((support.HOST, 0))
|
| 95 |
+
s.close()
|
| 96 |
+
|
| 97 |
+
def test_find_unused_port(self):
|
| 98 |
+
port = support.find_unused_port()
|
| 99 |
+
s = socket.create_server((support.HOST, port))
|
| 100 |
+
s.close()
|
| 101 |
+
|
| 102 |
+
def test_bind_port(self):
|
| 103 |
+
s = socket.socket()
|
| 104 |
+
support.bind_port(s)
|
| 105 |
+
s.listen()
|
| 106 |
+
s.close()
|
| 107 |
+
|
| 108 |
+
# Tests for temp_dir()
|
| 109 |
+
|
| 110 |
+
def test_temp_dir(self):
|
| 111 |
+
"""Test that temp_dir() creates and destroys its directory."""
|
| 112 |
+
parent_dir = tempfile.mkdtemp()
|
| 113 |
+
parent_dir = os.path.realpath(parent_dir)
|
| 114 |
+
|
| 115 |
+
try:
|
| 116 |
+
path = os.path.join(parent_dir, 'temp')
|
| 117 |
+
self.assertFalse(os.path.isdir(path))
|
| 118 |
+
with support.temp_dir(path) as temp_path:
|
| 119 |
+
self.assertEqual(temp_path, path)
|
| 120 |
+
self.assertTrue(os.path.isdir(path))
|
| 121 |
+
self.assertFalse(os.path.isdir(path))
|
| 122 |
+
finally:
|
| 123 |
+
support.rmtree(parent_dir)
|
| 124 |
+
|
| 125 |
+
def test_temp_dir__path_none(self):
|
| 126 |
+
"""Test passing no path."""
|
| 127 |
+
with support.temp_dir() as temp_path:
|
| 128 |
+
self.assertTrue(os.path.isdir(temp_path))
|
| 129 |
+
self.assertFalse(os.path.isdir(temp_path))
|
| 130 |
+
|
| 131 |
+
def test_temp_dir__existing_dir__quiet_default(self):
|
| 132 |
+
"""Test passing a directory that already exists."""
|
| 133 |
+
def call_temp_dir(path):
|
| 134 |
+
with support.temp_dir(path) as temp_path:
|
| 135 |
+
raise Exception("should not get here")
|
| 136 |
+
|
| 137 |
+
path = tempfile.mkdtemp()
|
| 138 |
+
path = os.path.realpath(path)
|
| 139 |
+
try:
|
| 140 |
+
self.assertTrue(os.path.isdir(path))
|
| 141 |
+
self.assertRaises(FileExistsError, call_temp_dir, path)
|
| 142 |
+
# Make sure temp_dir did not delete the original directory.
|
| 143 |
+
self.assertTrue(os.path.isdir(path))
|
| 144 |
+
finally:
|
| 145 |
+
shutil.rmtree(path)
|
| 146 |
+
|
| 147 |
+
def test_temp_dir__existing_dir__quiet_true(self):
|
| 148 |
+
"""Test passing a directory that already exists with quiet=True."""
|
| 149 |
+
path = tempfile.mkdtemp()
|
| 150 |
+
path = os.path.realpath(path)
|
| 151 |
+
|
| 152 |
+
try:
|
| 153 |
+
with support.check_warnings() as recorder:
|
| 154 |
+
with support.temp_dir(path, quiet=True) as temp_path:
|
| 155 |
+
self.assertEqual(path, temp_path)
|
| 156 |
+
warnings = [str(w.message) for w in recorder.warnings]
|
| 157 |
+
# Make sure temp_dir did not delete the original directory.
|
| 158 |
+
self.assertTrue(os.path.isdir(path))
|
| 159 |
+
finally:
|
| 160 |
+
shutil.rmtree(path)
|
| 161 |
+
|
| 162 |
+
self.assertEqual(len(warnings), 1, warnings)
|
| 163 |
+
warn = warnings[0]
|
| 164 |
+
self.assertTrue(warn.startswith(f'tests may fail, unable to create '
|
| 165 |
+
f'temporary directory {path!r}: '),
|
| 166 |
+
warn)
|
| 167 |
+
|
| 168 |
+
@unittest.skipUnless(hasattr(os, "fork"), "test requires os.fork")
|
| 169 |
+
def test_temp_dir__forked_child(self):
|
| 170 |
+
"""Test that a forked child process does not remove the directory."""
|
| 171 |
+
# See bpo-30028 for details.
|
| 172 |
+
# Run the test as an external script, because it uses fork.
|
| 173 |
+
script_helper.assert_python_ok("-c", textwrap.dedent("""
|
| 174 |
+
import os
|
| 175 |
+
from test import support
|
| 176 |
+
with support.temp_cwd() as temp_path:
|
| 177 |
+
pid = os.fork()
|
| 178 |
+
if pid != 0:
|
| 179 |
+
# parent process (child has pid == 0)
|
| 180 |
+
|
| 181 |
+
# wait for the child to terminate
|
| 182 |
+
(pid, status) = os.waitpid(pid, 0)
|
| 183 |
+
if status != 0:
|
| 184 |
+
raise AssertionError(f"Child process failed with exit "
|
| 185 |
+
f"status indication 0x{status:x}.")
|
| 186 |
+
|
| 187 |
+
# Make sure that temp_path is still present. When the child
|
| 188 |
+
# process leaves the 'temp_cwd'-context, the __exit__()-
|
| 189 |
+
# method of the context must not remove the temporary
|
| 190 |
+
# directory.
|
| 191 |
+
if not os.path.isdir(temp_path):
|
| 192 |
+
raise AssertionError("Child removed temp_path.")
|
| 193 |
+
"""))
|
| 194 |
+
|
| 195 |
+
# Tests for change_cwd()
|
| 196 |
+
|
| 197 |
+
def test_change_cwd(self):
|
| 198 |
+
original_cwd = os.getcwd()
|
| 199 |
+
|
| 200 |
+
with support.temp_dir() as temp_path:
|
| 201 |
+
with support.change_cwd(temp_path) as new_cwd:
|
| 202 |
+
self.assertEqual(new_cwd, temp_path)
|
| 203 |
+
self.assertEqual(os.getcwd(), new_cwd)
|
| 204 |
+
|
| 205 |
+
self.assertEqual(os.getcwd(), original_cwd)
|
| 206 |
+
|
| 207 |
+
def test_change_cwd__non_existent_dir(self):
|
| 208 |
+
"""Test passing a non-existent directory."""
|
| 209 |
+
original_cwd = os.getcwd()
|
| 210 |
+
|
| 211 |
+
def call_change_cwd(path):
|
| 212 |
+
with support.change_cwd(path) as new_cwd:
|
| 213 |
+
raise Exception("should not get here")
|
| 214 |
+
|
| 215 |
+
with support.temp_dir() as parent_dir:
|
| 216 |
+
non_existent_dir = os.path.join(parent_dir, 'does_not_exist')
|
| 217 |
+
self.assertRaises(FileNotFoundError, call_change_cwd,
|
| 218 |
+
non_existent_dir)
|
| 219 |
+
|
| 220 |
+
self.assertEqual(os.getcwd(), original_cwd)
|
| 221 |
+
|
| 222 |
+
def test_change_cwd__non_existent_dir__quiet_true(self):
|
| 223 |
+
"""Test passing a non-existent directory with quiet=True."""
|
| 224 |
+
original_cwd = os.getcwd()
|
| 225 |
+
|
| 226 |
+
with support.temp_dir() as parent_dir:
|
| 227 |
+
bad_dir = os.path.join(parent_dir, 'does_not_exist')
|
| 228 |
+
with support.check_warnings() as recorder:
|
| 229 |
+
with support.change_cwd(bad_dir, quiet=True) as new_cwd:
|
| 230 |
+
self.assertEqual(new_cwd, original_cwd)
|
| 231 |
+
self.assertEqual(os.getcwd(), new_cwd)
|
| 232 |
+
warnings = [str(w.message) for w in recorder.warnings]
|
| 233 |
+
|
| 234 |
+
self.assertEqual(len(warnings), 1, warnings)
|
| 235 |
+
warn = warnings[0]
|
| 236 |
+
self.assertTrue(warn.startswith(f'tests may fail, unable to change '
|
| 237 |
+
f'the current working directory '
|
| 238 |
+
f'to {bad_dir!r}: '),
|
| 239 |
+
warn)
|
| 240 |
+
|
| 241 |
+
# Tests for change_cwd()
|
| 242 |
+
|
| 243 |
+
def test_change_cwd__chdir_warning(self):
|
| 244 |
+
"""Check the warning message when os.chdir() fails."""
|
| 245 |
+
path = TESTFN + '_does_not_exist'
|
| 246 |
+
with support.check_warnings() as recorder:
|
| 247 |
+
with support.change_cwd(path=path, quiet=True):
|
| 248 |
+
pass
|
| 249 |
+
messages = [str(w.message) for w in recorder.warnings]
|
| 250 |
+
|
| 251 |
+
self.assertEqual(len(messages), 1, messages)
|
| 252 |
+
msg = messages[0]
|
| 253 |
+
self.assertTrue(msg.startswith(f'tests may fail, unable to change '
|
| 254 |
+
f'the current working directory '
|
| 255 |
+
f'to {path!r}: '),
|
| 256 |
+
msg)
|
| 257 |
+
|
| 258 |
+
# Tests for temp_cwd()
|
| 259 |
+
|
| 260 |
+
def test_temp_cwd(self):
|
| 261 |
+
here = os.getcwd()
|
| 262 |
+
with support.temp_cwd(name=TESTFN):
|
| 263 |
+
self.assertEqual(os.path.basename(os.getcwd()), TESTFN)
|
| 264 |
+
self.assertFalse(os.path.exists(TESTFN))
|
| 265 |
+
self.assertEqual(os.getcwd(), here)
|
| 266 |
+
|
| 267 |
+
|
| 268 |
+
def test_temp_cwd__name_none(self):
|
| 269 |
+
"""Test passing None to temp_cwd()."""
|
| 270 |
+
original_cwd = os.getcwd()
|
| 271 |
+
with support.temp_cwd(name=None) as new_cwd:
|
| 272 |
+
self.assertNotEqual(new_cwd, original_cwd)
|
| 273 |
+
self.assertTrue(os.path.isdir(new_cwd))
|
| 274 |
+
self.assertEqual(os.getcwd(), new_cwd)
|
| 275 |
+
self.assertEqual(os.getcwd(), original_cwd)
|
| 276 |
+
|
| 277 |
+
def test_sortdict(self):
|
| 278 |
+
self.assertEqual(support.sortdict({3:3, 2:2, 1:1}), "{1: 1, 2: 2, 3: 3}")
|
| 279 |
+
|
| 280 |
+
def test_make_bad_fd(self):
|
| 281 |
+
fd = support.make_bad_fd()
|
| 282 |
+
with self.assertRaises(OSError) as cm:
|
| 283 |
+
os.write(fd, b"foo")
|
| 284 |
+
self.assertEqual(cm.exception.errno, errno.EBADF)
|
| 285 |
+
|
| 286 |
+
def test_check_syntax_error(self):
|
| 287 |
+
support.check_syntax_error(self, "def class", lineno=1, offset=5)
|
| 288 |
+
with self.assertRaises(AssertionError):
|
| 289 |
+
support.check_syntax_error(self, "x=1")
|
| 290 |
+
|
| 291 |
+
def test_CleanImport(self):
|
| 292 |
+
import importlib
|
| 293 |
+
with support.CleanImport("asyncore"):
|
| 294 |
+
importlib.import_module("asyncore")
|
| 295 |
+
|
| 296 |
+
def test_DirsOnSysPath(self):
|
| 297 |
+
with support.DirsOnSysPath('foo', 'bar'):
|
| 298 |
+
self.assertIn("foo", sys.path)
|
| 299 |
+
self.assertIn("bar", sys.path)
|
| 300 |
+
self.assertNotIn("foo", sys.path)
|
| 301 |
+
self.assertNotIn("bar", sys.path)
|
| 302 |
+
|
| 303 |
+
def test_captured_stdout(self):
|
| 304 |
+
with support.captured_stdout() as stdout:
|
| 305 |
+
print("hello")
|
| 306 |
+
self.assertEqual(stdout.getvalue(), "hello\n")
|
| 307 |
+
|
| 308 |
+
def test_captured_stderr(self):
|
| 309 |
+
with support.captured_stderr() as stderr:
|
| 310 |
+
print("hello", file=sys.stderr)
|
| 311 |
+
self.assertEqual(stderr.getvalue(), "hello\n")
|
| 312 |
+
|
| 313 |
+
def test_captured_stdin(self):
|
| 314 |
+
with support.captured_stdin() as stdin:
|
| 315 |
+
stdin.write('hello\n')
|
| 316 |
+
stdin.seek(0)
|
| 317 |
+
# call test code that consumes from sys.stdin
|
| 318 |
+
captured = input()
|
| 319 |
+
self.assertEqual(captured, "hello")
|
| 320 |
+
|
| 321 |
+
def test_gc_collect(self):
|
| 322 |
+
support.gc_collect()
|
| 323 |
+
|
| 324 |
+
def test_python_is_optimized(self):
|
| 325 |
+
self.assertIsInstance(support.python_is_optimized(), bool)
|
| 326 |
+
|
| 327 |
+
def test_swap_attr(self):
|
| 328 |
+
class Obj:
|
| 329 |
+
pass
|
| 330 |
+
obj = Obj()
|
| 331 |
+
obj.x = 1
|
| 332 |
+
with support.swap_attr(obj, "x", 5) as x:
|
| 333 |
+
self.assertEqual(obj.x, 5)
|
| 334 |
+
self.assertEqual(x, 1)
|
| 335 |
+
self.assertEqual(obj.x, 1)
|
| 336 |
+
with support.swap_attr(obj, "y", 5) as y:
|
| 337 |
+
self.assertEqual(obj.y, 5)
|
| 338 |
+
self.assertIsNone(y)
|
| 339 |
+
self.assertFalse(hasattr(obj, 'y'))
|
| 340 |
+
with support.swap_attr(obj, "y", 5):
|
| 341 |
+
del obj.y
|
| 342 |
+
self.assertFalse(hasattr(obj, 'y'))
|
| 343 |
+
|
| 344 |
+
def test_swap_item(self):
|
| 345 |
+
D = {"x":1}
|
| 346 |
+
with support.swap_item(D, "x", 5) as x:
|
| 347 |
+
self.assertEqual(D["x"], 5)
|
| 348 |
+
self.assertEqual(x, 1)
|
| 349 |
+
self.assertEqual(D["x"], 1)
|
| 350 |
+
with support.swap_item(D, "y", 5) as y:
|
| 351 |
+
self.assertEqual(D["y"], 5)
|
| 352 |
+
self.assertIsNone(y)
|
| 353 |
+
self.assertNotIn("y", D)
|
| 354 |
+
with support.swap_item(D, "y", 5):
|
| 355 |
+
del D["y"]
|
| 356 |
+
self.assertNotIn("y", D)
|
| 357 |
+
|
| 358 |
+
class RefClass:
|
| 359 |
+
attribute1 = None
|
| 360 |
+
attribute2 = None
|
| 361 |
+
_hidden_attribute1 = None
|
| 362 |
+
__magic_1__ = None
|
| 363 |
+
|
| 364 |
+
class OtherClass:
|
| 365 |
+
attribute2 = None
|
| 366 |
+
attribute3 = None
|
| 367 |
+
__magic_1__ = None
|
| 368 |
+
__magic_2__ = None
|
| 369 |
+
|
| 370 |
+
def test_detect_api_mismatch(self):
|
| 371 |
+
missing_items = support.detect_api_mismatch(self.RefClass,
|
| 372 |
+
self.OtherClass)
|
| 373 |
+
self.assertEqual({'attribute1'}, missing_items)
|
| 374 |
+
|
| 375 |
+
missing_items = support.detect_api_mismatch(self.OtherClass,
|
| 376 |
+
self.RefClass)
|
| 377 |
+
self.assertEqual({'attribute3', '__magic_2__'}, missing_items)
|
| 378 |
+
|
| 379 |
+
def test_detect_api_mismatch__ignore(self):
|
| 380 |
+
ignore = ['attribute1', 'attribute3', '__magic_2__', 'not_in_either']
|
| 381 |
+
|
| 382 |
+
missing_items = support.detect_api_mismatch(
|
| 383 |
+
self.RefClass, self.OtherClass, ignore=ignore)
|
| 384 |
+
self.assertEqual(set(), missing_items)
|
| 385 |
+
|
| 386 |
+
missing_items = support.detect_api_mismatch(
|
| 387 |
+
self.OtherClass, self.RefClass, ignore=ignore)
|
| 388 |
+
self.assertEqual(set(), missing_items)
|
| 389 |
+
|
| 390 |
+
def test_check__all__(self):
|
| 391 |
+
extra = {'tempdir'}
|
| 392 |
+
blacklist = {'template'}
|
| 393 |
+
support.check__all__(self,
|
| 394 |
+
tempfile,
|
| 395 |
+
extra=extra,
|
| 396 |
+
blacklist=blacklist)
|
| 397 |
+
|
| 398 |
+
extra = {'TextTestResult', 'installHandler'}
|
| 399 |
+
blacklist = {'load_tests', "TestProgram", "BaseTestSuite"}
|
| 400 |
+
|
| 401 |
+
support.check__all__(self,
|
| 402 |
+
unittest,
|
| 403 |
+
("unittest.result", "unittest.case",
|
| 404 |
+
"unittest.suite", "unittest.loader",
|
| 405 |
+
"unittest.main", "unittest.runner",
|
| 406 |
+
"unittest.signals", "unittest.async_case"),
|
| 407 |
+
extra=extra,
|
| 408 |
+
blacklist=blacklist)
|
| 409 |
+
|
| 410 |
+
self.assertRaises(AssertionError, support.check__all__, self, unittest)
|
| 411 |
+
|
| 412 |
+
@unittest.skipUnless(hasattr(os, 'waitpid') and hasattr(os, 'WNOHANG'),
|
| 413 |
+
'need os.waitpid() and os.WNOHANG')
|
| 414 |
+
def test_reap_children(self):
|
| 415 |
+
# Make sure that there is no other pending child process
|
| 416 |
+
support.reap_children()
|
| 417 |
+
|
| 418 |
+
# Create a child process
|
| 419 |
+
pid = os.fork()
|
| 420 |
+
if pid == 0:
|
| 421 |
+
# child process: do nothing, just exit
|
| 422 |
+
os._exit(0)
|
| 423 |
+
|
| 424 |
+
t0 = time.monotonic()
|
| 425 |
+
deadline = time.monotonic() + 60.0
|
| 426 |
+
|
| 427 |
+
was_altered = support.environment_altered
|
| 428 |
+
try:
|
| 429 |
+
support.environment_altered = False
|
| 430 |
+
stderr = io.StringIO()
|
| 431 |
+
|
| 432 |
+
while True:
|
| 433 |
+
if time.monotonic() > deadline:
|
| 434 |
+
self.fail("timeout")
|
| 435 |
+
|
| 436 |
+
old_stderr = sys.__stderr__
|
| 437 |
+
try:
|
| 438 |
+
sys.__stderr__ = stderr
|
| 439 |
+
support.reap_children()
|
| 440 |
+
finally:
|
| 441 |
+
sys.__stderr__ = old_stderr
|
| 442 |
+
|
| 443 |
+
# Use environment_altered to check if reap_children() found
|
| 444 |
+
# the child process
|
| 445 |
+
if support.environment_altered:
|
| 446 |
+
break
|
| 447 |
+
|
| 448 |
+
# loop until the child process completed
|
| 449 |
+
time.sleep(0.100)
|
| 450 |
+
|
| 451 |
+
msg = "Warning -- reap_children() reaped child process %s" % pid
|
| 452 |
+
self.assertIn(msg, stderr.getvalue())
|
| 453 |
+
self.assertTrue(support.environment_altered)
|
| 454 |
+
finally:
|
| 455 |
+
support.environment_altered = was_altered
|
| 456 |
+
|
| 457 |
+
# Just in case, check again that there is no other
|
| 458 |
+
# pending child process
|
| 459 |
+
support.reap_children()
|
| 460 |
+
|
| 461 |
+
def check_options(self, args, func, expected=None):
|
| 462 |
+
code = f'from test.support import {func}; print(repr({func}()))'
|
| 463 |
+
cmd = [sys.executable, *args, '-c', code]
|
| 464 |
+
env = {key: value for key, value in os.environ.items()
|
| 465 |
+
if not key.startswith('PYTHON')}
|
| 466 |
+
proc = subprocess.run(cmd,
|
| 467 |
+
stdout=subprocess.PIPE,
|
| 468 |
+
stderr=subprocess.DEVNULL,
|
| 469 |
+
universal_newlines=True,
|
| 470 |
+
env=env)
|
| 471 |
+
if expected is None:
|
| 472 |
+
expected = args
|
| 473 |
+
self.assertEqual(proc.stdout.rstrip(), repr(expected))
|
| 474 |
+
self.assertEqual(proc.returncode, 0)
|
| 475 |
+
|
| 476 |
+
def test_args_from_interpreter_flags(self):
|
| 477 |
+
# Test test.support.args_from_interpreter_flags()
|
| 478 |
+
for opts in (
|
| 479 |
+
# no option
|
| 480 |
+
[],
|
| 481 |
+
# single option
|
| 482 |
+
['-B'],
|
| 483 |
+
['-s'],
|
| 484 |
+
['-S'],
|
| 485 |
+
['-E'],
|
| 486 |
+
['-v'],
|
| 487 |
+
['-b'],
|
| 488 |
+
['-q'],
|
| 489 |
+
['-I'],
|
| 490 |
+
# same option multiple times
|
| 491 |
+
['-bb'],
|
| 492 |
+
['-vvv'],
|
| 493 |
+
# -W options
|
| 494 |
+
['-Wignore'],
|
| 495 |
+
# -X options
|
| 496 |
+
['-X', 'dev'],
|
| 497 |
+
['-Wignore', '-X', 'dev'],
|
| 498 |
+
['-X', 'faulthandler'],
|
| 499 |
+
['-X', 'importtime'],
|
| 500 |
+
['-X', 'showalloccount'],
|
| 501 |
+
['-X', 'showrefcount'],
|
| 502 |
+
['-X', 'tracemalloc'],
|
| 503 |
+
['-X', 'tracemalloc=3'],
|
| 504 |
+
):
|
| 505 |
+
with self.subTest(opts=opts):
|
| 506 |
+
self.check_options(opts, 'args_from_interpreter_flags')
|
| 507 |
+
|
| 508 |
+
self.check_options(['-I', '-E', '-s'], 'args_from_interpreter_flags',
|
| 509 |
+
['-I'])
|
| 510 |
+
|
| 511 |
+
def test_optim_args_from_interpreter_flags(self):
|
| 512 |
+
# Test test.support.optim_args_from_interpreter_flags()
|
| 513 |
+
for opts in (
|
| 514 |
+
# no option
|
| 515 |
+
[],
|
| 516 |
+
['-O'],
|
| 517 |
+
['-OO'],
|
| 518 |
+
['-OOOO'],
|
| 519 |
+
):
|
| 520 |
+
with self.subTest(opts=opts):
|
| 521 |
+
self.check_options(opts, 'optim_args_from_interpreter_flags')
|
| 522 |
+
|
| 523 |
+
def test_match_test(self):
|
| 524 |
+
class Test:
|
| 525 |
+
def __init__(self, test_id):
|
| 526 |
+
self.test_id = test_id
|
| 527 |
+
|
| 528 |
+
def id(self):
|
| 529 |
+
return self.test_id
|
| 530 |
+
|
| 531 |
+
test_access = Test('test.test_os.FileTests.test_access')
|
| 532 |
+
test_chdir = Test('test.test_os.Win32ErrorTests.test_chdir')
|
| 533 |
+
|
| 534 |
+
# Test acceptance
|
| 535 |
+
with support.swap_attr(support, '_match_test_func', None):
|
| 536 |
+
# match all
|
| 537 |
+
support.set_match_tests([])
|
| 538 |
+
self.assertTrue(support.match_test(test_access))
|
| 539 |
+
self.assertTrue(support.match_test(test_chdir))
|
| 540 |
+
|
| 541 |
+
# match all using None
|
| 542 |
+
support.set_match_tests(None, None)
|
| 543 |
+
self.assertTrue(support.match_test(test_access))
|
| 544 |
+
self.assertTrue(support.match_test(test_chdir))
|
| 545 |
+
|
| 546 |
+
# match the full test identifier
|
| 547 |
+
support.set_match_tests([test_access.id()], None)
|
| 548 |
+
self.assertTrue(support.match_test(test_access))
|
| 549 |
+
self.assertFalse(support.match_test(test_chdir))
|
| 550 |
+
|
| 551 |
+
# match the module name
|
| 552 |
+
support.set_match_tests(['test_os'], None)
|
| 553 |
+
self.assertTrue(support.match_test(test_access))
|
| 554 |
+
self.assertTrue(support.match_test(test_chdir))
|
| 555 |
+
|
| 556 |
+
# Test '*' pattern
|
| 557 |
+
support.set_match_tests(['test_*'], None)
|
| 558 |
+
self.assertTrue(support.match_test(test_access))
|
| 559 |
+
self.assertTrue(support.match_test(test_chdir))
|
| 560 |
+
|
| 561 |
+
# Test case sensitivity
|
| 562 |
+
support.set_match_tests(['filetests'], None)
|
| 563 |
+
self.assertFalse(support.match_test(test_access))
|
| 564 |
+
support.set_match_tests(['FileTests'], None)
|
| 565 |
+
self.assertTrue(support.match_test(test_access))
|
| 566 |
+
|
| 567 |
+
# Test pattern containing '.' and a '*' metacharacter
|
| 568 |
+
support.set_match_tests(['*test_os.*.test_*'], None)
|
| 569 |
+
self.assertTrue(support.match_test(test_access))
|
| 570 |
+
self.assertTrue(support.match_test(test_chdir))
|
| 571 |
+
|
| 572 |
+
# Multiple patterns
|
| 573 |
+
support.set_match_tests([test_access.id(), test_chdir.id()], None)
|
| 574 |
+
self.assertTrue(support.match_test(test_access))
|
| 575 |
+
self.assertTrue(support.match_test(test_chdir))
|
| 576 |
+
|
| 577 |
+
support.set_match_tests(['test_access', 'DONTMATCH'], None)
|
| 578 |
+
self.assertTrue(support.match_test(test_access))
|
| 579 |
+
self.assertFalse(support.match_test(test_chdir))
|
| 580 |
+
|
| 581 |
+
# Test rejection
|
| 582 |
+
with support.swap_attr(support, '_match_test_func', None):
|
| 583 |
+
# match all
|
| 584 |
+
support.set_match_tests(ignore_patterns=[])
|
| 585 |
+
self.assertTrue(support.match_test(test_access))
|
| 586 |
+
self.assertTrue(support.match_test(test_chdir))
|
| 587 |
+
|
| 588 |
+
# match all using None
|
| 589 |
+
support.set_match_tests(None, None)
|
| 590 |
+
self.assertTrue(support.match_test(test_access))
|
| 591 |
+
self.assertTrue(support.match_test(test_chdir))
|
| 592 |
+
|
| 593 |
+
# match the full test identifier
|
| 594 |
+
support.set_match_tests(None, [test_access.id()])
|
| 595 |
+
self.assertFalse(support.match_test(test_access))
|
| 596 |
+
self.assertTrue(support.match_test(test_chdir))
|
| 597 |
+
|
| 598 |
+
# match the module name
|
| 599 |
+
support.set_match_tests(None, ['test_os'])
|
| 600 |
+
self.assertFalse(support.match_test(test_access))
|
| 601 |
+
self.assertFalse(support.match_test(test_chdir))
|
| 602 |
+
|
| 603 |
+
# Test '*' pattern
|
| 604 |
+
support.set_match_tests(None, ['test_*'])
|
| 605 |
+
self.assertFalse(support.match_test(test_access))
|
| 606 |
+
self.assertFalse(support.match_test(test_chdir))
|
| 607 |
+
|
| 608 |
+
# Test case sensitivity
|
| 609 |
+
support.set_match_tests(None, ['filetests'])
|
| 610 |
+
self.assertTrue(support.match_test(test_access))
|
| 611 |
+
support.set_match_tests(None, ['FileTests'])
|
| 612 |
+
self.assertFalse(support.match_test(test_access))
|
| 613 |
+
|
| 614 |
+
# Test pattern containing '.' and a '*' metacharacter
|
| 615 |
+
support.set_match_tests(None, ['*test_os.*.test_*'])
|
| 616 |
+
self.assertFalse(support.match_test(test_access))
|
| 617 |
+
self.assertFalse(support.match_test(test_chdir))
|
| 618 |
+
|
| 619 |
+
# Multiple patterns
|
| 620 |
+
support.set_match_tests(None, [test_access.id(), test_chdir.id()])
|
| 621 |
+
self.assertFalse(support.match_test(test_access))
|
| 622 |
+
self.assertFalse(support.match_test(test_chdir))
|
| 623 |
+
|
| 624 |
+
support.set_match_tests(None, ['test_access', 'DONTMATCH'])
|
| 625 |
+
self.assertFalse(support.match_test(test_access))
|
| 626 |
+
self.assertTrue(support.match_test(test_chdir))
|
| 627 |
+
|
| 628 |
+
def test_fd_count(self):
|
| 629 |
+
# We cannot test the absolute value of fd_count(): on old Linux
|
| 630 |
+
# kernel or glibc versions, os.urandom() keeps a FD open on
|
| 631 |
+
# /dev/urandom device and Python has 4 FD opens instead of 3.
|
| 632 |
+
start = support.fd_count()
|
| 633 |
+
fd = os.open(__file__, os.O_RDONLY)
|
| 634 |
+
try:
|
| 635 |
+
more = support.fd_count()
|
| 636 |
+
finally:
|
| 637 |
+
os.close(fd)
|
| 638 |
+
self.assertEqual(more - start, 1)
|
| 639 |
+
|
| 640 |
+
def check_print_warning(self, msg, expected):
|
| 641 |
+
stderr = io.StringIO()
|
| 642 |
+
|
| 643 |
+
old_stderr = sys.__stderr__
|
| 644 |
+
try:
|
| 645 |
+
sys.__stderr__ = stderr
|
| 646 |
+
support.print_warning(msg)
|
| 647 |
+
finally:
|
| 648 |
+
sys.__stderr__ = old_stderr
|
| 649 |
+
|
| 650 |
+
self.assertEqual(stderr.getvalue(), expected)
|
| 651 |
+
|
| 652 |
+
def test_print_warning(self):
|
| 653 |
+
self.check_print_warning("msg",
|
| 654 |
+
"Warning -- msg\n")
|
| 655 |
+
self.check_print_warning("a\nb",
|
| 656 |
+
'Warning -- a\nWarning -- b\n')
|
| 657 |
+
|
| 658 |
+
# XXX -follows a list of untested API
|
| 659 |
+
# make_legacy_pyc
|
| 660 |
+
# is_resource_enabled
|
| 661 |
+
# requires
|
| 662 |
+
# fcmp
|
| 663 |
+
# umaks
|
| 664 |
+
# findfile
|
| 665 |
+
# check_warnings
|
| 666 |
+
# EnvironmentVarGuard
|
| 667 |
+
# TransientResource
|
| 668 |
+
# transient_internet
|
| 669 |
+
# run_with_locale
|
| 670 |
+
# set_memlimit
|
| 671 |
+
# bigmemtest
|
| 672 |
+
# precisionbigmemtest
|
| 673 |
+
# bigaddrspacetest
|
| 674 |
+
# requires_resource
|
| 675 |
+
# run_doctest
|
| 676 |
+
# threading_cleanup
|
| 677 |
+
# reap_threads
|
| 678 |
+
# strip_python_stderr
|
| 679 |
+
# can_symlink
|
| 680 |
+
# skip_unless_symlink
|
| 681 |
+
# SuppressCrashReport
|
| 682 |
+
|
| 683 |
+
|
| 684 |
+
def test_main():
|
| 685 |
+
tests = [TestSupport]
|
| 686 |
+
support.run_unittest(*tests)
|
| 687 |
+
|
| 688 |
+
if __name__ == '__main__':
|
| 689 |
+
test_main()
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/turtledemo/__init__.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
--------------------------------------
|
| 3 |
+
About this viewer
|
| 4 |
+
--------------------------------------
|
| 5 |
+
|
| 6 |
+
Tiny demo viewer to view turtle graphics example scripts.
|
| 7 |
+
|
| 8 |
+
Quickly and dirtyly assembled by Gregor Lingl.
|
| 9 |
+
June, 2006
|
| 10 |
+
|
| 11 |
+
For more information see: turtledemo - Help
|
| 12 |
+
|
| 13 |
+
Have fun!
|
| 14 |
+
"""
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/turtledemo/__main__.py
ADDED
|
@@ -0,0 +1,386 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
|
| 3 |
+
"""
|
| 4 |
+
----------------------------------------------
|
| 5 |
+
turtleDemo - Help
|
| 6 |
+
----------------------------------------------
|
| 7 |
+
|
| 8 |
+
This document has two sections:
|
| 9 |
+
|
| 10 |
+
(1) How to use the demo viewer
|
| 11 |
+
(2) How to add your own demos to the demo repository
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
(1) How to use the demo viewer.
|
| 15 |
+
|
| 16 |
+
Select a demoscript from the example menu.
|
| 17 |
+
The (syntax colored) source code appears in the left
|
| 18 |
+
source code window. IT CANNOT BE EDITED, but ONLY VIEWED!
|
| 19 |
+
|
| 20 |
+
The demo viewer windows can be resized. The divider between text
|
| 21 |
+
and canvas can be moved by grabbing it with the mouse. The text font
|
| 22 |
+
size can be changed from the menu and with Control/Command '-'/'+'.
|
| 23 |
+
It can also be changed on most systems with Control-mousewheel
|
| 24 |
+
when the mouse is over the text.
|
| 25 |
+
|
| 26 |
+
Press START button to start the demo.
|
| 27 |
+
Stop execution by pressing the STOP button.
|
| 28 |
+
Clear screen by pressing the CLEAR button.
|
| 29 |
+
Restart by pressing the START button again.
|
| 30 |
+
|
| 31 |
+
SPECIAL demos, such as clock.py are those which run EVENTDRIVEN.
|
| 32 |
+
|
| 33 |
+
Press START button to start the demo.
|
| 34 |
+
|
| 35 |
+
- Until the EVENTLOOP is entered everything works
|
| 36 |
+
as in an ordinary demo script.
|
| 37 |
+
|
| 38 |
+
- When the EVENTLOOP is entered, you control the
|
| 39 |
+
application by using the mouse and/or keys (or it's
|
| 40 |
+
controlled by some timer events)
|
| 41 |
+
To stop it you can and must press the STOP button.
|
| 42 |
+
|
| 43 |
+
While the EVENTLOOP is running, the examples menu is disabled.
|
| 44 |
+
|
| 45 |
+
- Only after having pressed the STOP button, you may
|
| 46 |
+
restart it or choose another example script.
|
| 47 |
+
|
| 48 |
+
* * * * * * * *
|
| 49 |
+
In some rare situations there may occur interferences/conflicts
|
| 50 |
+
between events concerning the demo script and those concerning the
|
| 51 |
+
demo-viewer. (They run in the same process.) Strange behaviour may be
|
| 52 |
+
the consequence and in the worst case you must close and restart the
|
| 53 |
+
viewer.
|
| 54 |
+
* * * * * * * *
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
(2) How to add your own demos to the demo repository
|
| 58 |
+
|
| 59 |
+
- Place the file in the same directory as turtledemo/__main__.py
|
| 60 |
+
IMPORTANT! When imported, the demo should not modify the system
|
| 61 |
+
by calling functions in other modules, such as sys, tkinter, or
|
| 62 |
+
turtle. Global variables should be initialized in main().
|
| 63 |
+
|
| 64 |
+
- The code must contain a main() function which will
|
| 65 |
+
be executed by the viewer (see provided example scripts).
|
| 66 |
+
It may return a string which will be displayed in the Label below
|
| 67 |
+
the source code window (when execution has finished.)
|
| 68 |
+
|
| 69 |
+
- In order to run mydemo.py by itself, such as during development,
|
| 70 |
+
add the following at the end of the file:
|
| 71 |
+
|
| 72 |
+
if __name__ == '__main__':
|
| 73 |
+
main()
|
| 74 |
+
mainloop() # keep window open
|
| 75 |
+
|
| 76 |
+
python -m turtledemo.mydemo # will then run it
|
| 77 |
+
|
| 78 |
+
- If the demo is EVENT DRIVEN, main must return the string
|
| 79 |
+
"EVENTLOOP". This informs the demo viewer that the script is
|
| 80 |
+
still running and must be stopped by the user!
|
| 81 |
+
|
| 82 |
+
If an "EVENTLOOP" demo runs by itself, as with clock, which uses
|
| 83 |
+
ontimer, or minimal_hanoi, which loops by recursion, then the
|
| 84 |
+
code should catch the turtle.Terminator exception that will be
|
| 85 |
+
raised when the user presses the STOP button. (Paint is not such
|
| 86 |
+
a demo; it only acts in response to mouse clicks and movements.)
|
| 87 |
+
"""
|
| 88 |
+
import sys
|
| 89 |
+
import os
|
| 90 |
+
|
| 91 |
+
from tkinter import *
|
| 92 |
+
from idlelib.colorizer import ColorDelegator, color_config
|
| 93 |
+
from idlelib.percolator import Percolator
|
| 94 |
+
from idlelib.textview import view_text
|
| 95 |
+
from turtledemo import __doc__ as about_turtledemo
|
| 96 |
+
|
| 97 |
+
import turtle
|
| 98 |
+
|
| 99 |
+
demo_dir = os.path.dirname(os.path.abspath(__file__))
|
| 100 |
+
darwin = sys.platform == 'darwin'
|
| 101 |
+
|
| 102 |
+
STARTUP = 1
|
| 103 |
+
READY = 2
|
| 104 |
+
RUNNING = 3
|
| 105 |
+
DONE = 4
|
| 106 |
+
EVENTDRIVEN = 5
|
| 107 |
+
|
| 108 |
+
menufont = ("Arial", 12, NORMAL)
|
| 109 |
+
btnfont = ("Arial", 12, 'bold')
|
| 110 |
+
txtfont = ['Lucida Console', 10, 'normal']
|
| 111 |
+
|
| 112 |
+
MINIMUM_FONT_SIZE = 6
|
| 113 |
+
MAXIMUM_FONT_SIZE = 100
|
| 114 |
+
font_sizes = [8, 9, 10, 11, 12, 14, 18, 20, 22, 24, 30]
|
| 115 |
+
|
| 116 |
+
def getExampleEntries():
|
| 117 |
+
return [entry[:-3] for entry in os.listdir(demo_dir) if
|
| 118 |
+
entry.endswith(".py") and entry[0] != '_']
|
| 119 |
+
|
| 120 |
+
help_entries = ( # (help_label, help_doc)
|
| 121 |
+
('Turtledemo help', __doc__),
|
| 122 |
+
('About turtledemo', about_turtledemo),
|
| 123 |
+
('About turtle module', turtle.__doc__),
|
| 124 |
+
)
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
|
| 128 |
+
class DemoWindow(object):
|
| 129 |
+
|
| 130 |
+
def __init__(self, filename=None):
|
| 131 |
+
self.root = root = turtle._root = Tk()
|
| 132 |
+
root.title('Python turtle-graphics examples')
|
| 133 |
+
root.wm_protocol("WM_DELETE_WINDOW", self._destroy)
|
| 134 |
+
|
| 135 |
+
if darwin:
|
| 136 |
+
import subprocess
|
| 137 |
+
# Make sure we are the currently activated OS X application
|
| 138 |
+
# so that our menu bar appears.
|
| 139 |
+
subprocess.run(
|
| 140 |
+
[
|
| 141 |
+
'osascript',
|
| 142 |
+
'-e', 'tell application "System Events"',
|
| 143 |
+
'-e', 'set frontmost of the first process whose '
|
| 144 |
+
'unix id is {} to true'.format(os.getpid()),
|
| 145 |
+
'-e', 'end tell',
|
| 146 |
+
],
|
| 147 |
+
stderr=subprocess.DEVNULL,
|
| 148 |
+
stdout=subprocess.DEVNULL,)
|
| 149 |
+
|
| 150 |
+
root.grid_rowconfigure(0, weight=1)
|
| 151 |
+
root.grid_columnconfigure(0, weight=1)
|
| 152 |
+
root.grid_columnconfigure(1, minsize=90, weight=1)
|
| 153 |
+
root.grid_columnconfigure(2, minsize=90, weight=1)
|
| 154 |
+
root.grid_columnconfigure(3, minsize=90, weight=1)
|
| 155 |
+
|
| 156 |
+
self.mBar = Menu(root, relief=RAISED, borderwidth=2)
|
| 157 |
+
self.mBar.add_cascade(menu=self.makeLoadDemoMenu(self.mBar),
|
| 158 |
+
label='Examples', underline=0)
|
| 159 |
+
self.mBar.add_cascade(menu=self.makeFontMenu(self.mBar),
|
| 160 |
+
label='Fontsize', underline=0)
|
| 161 |
+
self.mBar.add_cascade(menu=self.makeHelpMenu(self.mBar),
|
| 162 |
+
label='Help', underline=0)
|
| 163 |
+
root['menu'] = self.mBar
|
| 164 |
+
|
| 165 |
+
pane = PanedWindow(orient=HORIZONTAL, sashwidth=5,
|
| 166 |
+
sashrelief=SOLID, bg='#ddd')
|
| 167 |
+
pane.add(self.makeTextFrame(pane))
|
| 168 |
+
pane.add(self.makeGraphFrame(pane))
|
| 169 |
+
pane.grid(row=0, columnspan=4, sticky='news')
|
| 170 |
+
|
| 171 |
+
self.output_lbl = Label(root, height= 1, text=" --- ", bg="#ddf",
|
| 172 |
+
font=("Arial", 16, 'normal'), borderwidth=2,
|
| 173 |
+
relief=RIDGE)
|
| 174 |
+
self.start_btn = Button(root, text=" START ", font=btnfont,
|
| 175 |
+
fg="white", disabledforeground = "#fed",
|
| 176 |
+
command=self.startDemo)
|
| 177 |
+
self.stop_btn = Button(root, text=" STOP ", font=btnfont,
|
| 178 |
+
fg="white", disabledforeground = "#fed",
|
| 179 |
+
command=self.stopIt)
|
| 180 |
+
self.clear_btn = Button(root, text=" CLEAR ", font=btnfont,
|
| 181 |
+
fg="white", disabledforeground="#fed",
|
| 182 |
+
command = self.clearCanvas)
|
| 183 |
+
self.output_lbl.grid(row=1, column=0, sticky='news', padx=(0,5))
|
| 184 |
+
self.start_btn.grid(row=1, column=1, sticky='ew')
|
| 185 |
+
self.stop_btn.grid(row=1, column=2, sticky='ew')
|
| 186 |
+
self.clear_btn.grid(row=1, column=3, sticky='ew')
|
| 187 |
+
|
| 188 |
+
Percolator(self.text).insertfilter(ColorDelegator())
|
| 189 |
+
self.dirty = False
|
| 190 |
+
self.exitflag = False
|
| 191 |
+
if filename:
|
| 192 |
+
self.loadfile(filename)
|
| 193 |
+
self.configGUI(DISABLED, DISABLED, DISABLED,
|
| 194 |
+
"Choose example from menu", "black")
|
| 195 |
+
self.state = STARTUP
|
| 196 |
+
|
| 197 |
+
|
| 198 |
+
def onResize(self, event):
|
| 199 |
+
cwidth = self._canvas.winfo_width()
|
| 200 |
+
cheight = self._canvas.winfo_height()
|
| 201 |
+
self._canvas.xview_moveto(0.5*(self.canvwidth-cwidth)/self.canvwidth)
|
| 202 |
+
self._canvas.yview_moveto(0.5*(self.canvheight-cheight)/self.canvheight)
|
| 203 |
+
|
| 204 |
+
def makeTextFrame(self, root):
|
| 205 |
+
self.text_frame = text_frame = Frame(root)
|
| 206 |
+
self.text = text = Text(text_frame, name='text', padx=5,
|
| 207 |
+
wrap='none', width=45)
|
| 208 |
+
color_config(text)
|
| 209 |
+
|
| 210 |
+
self.vbar = vbar = Scrollbar(text_frame, name='vbar')
|
| 211 |
+
vbar['command'] = text.yview
|
| 212 |
+
vbar.pack(side=LEFT, fill=Y)
|
| 213 |
+
self.hbar = hbar = Scrollbar(text_frame, name='hbar', orient=HORIZONTAL)
|
| 214 |
+
hbar['command'] = text.xview
|
| 215 |
+
hbar.pack(side=BOTTOM, fill=X)
|
| 216 |
+
text['yscrollcommand'] = vbar.set
|
| 217 |
+
text['xscrollcommand'] = hbar.set
|
| 218 |
+
|
| 219 |
+
text['font'] = tuple(txtfont)
|
| 220 |
+
shortcut = 'Command' if darwin else 'Control'
|
| 221 |
+
text.bind_all('<%s-minus>' % shortcut, self.decrease_size)
|
| 222 |
+
text.bind_all('<%s-underscore>' % shortcut, self.decrease_size)
|
| 223 |
+
text.bind_all('<%s-equal>' % shortcut, self.increase_size)
|
| 224 |
+
text.bind_all('<%s-plus>' % shortcut, self.increase_size)
|
| 225 |
+
text.bind('<Control-MouseWheel>', self.update_mousewheel)
|
| 226 |
+
text.bind('<Control-Button-4>', self.increase_size)
|
| 227 |
+
text.bind('<Control-Button-5>', self.decrease_size)
|
| 228 |
+
|
| 229 |
+
text.pack(side=LEFT, fill=BOTH, expand=1)
|
| 230 |
+
return text_frame
|
| 231 |
+
|
| 232 |
+
def makeGraphFrame(self, root):
|
| 233 |
+
turtle._Screen._root = root
|
| 234 |
+
self.canvwidth = 1000
|
| 235 |
+
self.canvheight = 800
|
| 236 |
+
turtle._Screen._canvas = self._canvas = canvas = turtle.ScrolledCanvas(
|
| 237 |
+
root, 800, 600, self.canvwidth, self.canvheight)
|
| 238 |
+
canvas.adjustScrolls()
|
| 239 |
+
canvas._rootwindow.bind('<Configure>', self.onResize)
|
| 240 |
+
canvas._canvas['borderwidth'] = 0
|
| 241 |
+
|
| 242 |
+
self.screen = _s_ = turtle.Screen()
|
| 243 |
+
turtle.TurtleScreen.__init__(_s_, _s_._canvas)
|
| 244 |
+
self.scanvas = _s_._canvas
|
| 245 |
+
turtle.RawTurtle.screens = [_s_]
|
| 246 |
+
return canvas
|
| 247 |
+
|
| 248 |
+
def set_txtsize(self, size):
|
| 249 |
+
txtfont[1] = size
|
| 250 |
+
self.text['font'] = tuple(txtfont)
|
| 251 |
+
self.output_lbl['text'] = 'Font size %d' % size
|
| 252 |
+
|
| 253 |
+
def decrease_size(self, dummy=None):
|
| 254 |
+
self.set_txtsize(max(txtfont[1] - 1, MINIMUM_FONT_SIZE))
|
| 255 |
+
return 'break'
|
| 256 |
+
|
| 257 |
+
def increase_size(self, dummy=None):
|
| 258 |
+
self.set_txtsize(min(txtfont[1] + 1, MAXIMUM_FONT_SIZE))
|
| 259 |
+
return 'break'
|
| 260 |
+
|
| 261 |
+
def update_mousewheel(self, event):
|
| 262 |
+
# For wheel up, event.delta = 120 on Windows, -1 on darwin.
|
| 263 |
+
# X-11 sends Control-Button-4 event instead.
|
| 264 |
+
if (event.delta < 0) == (not darwin):
|
| 265 |
+
return self.decrease_size()
|
| 266 |
+
else:
|
| 267 |
+
return self.increase_size()
|
| 268 |
+
|
| 269 |
+
def configGUI(self, start, stop, clear, txt="", color="blue"):
|
| 270 |
+
self.start_btn.config(state=start,
|
| 271 |
+
bg="#d00" if start == NORMAL else "#fca")
|
| 272 |
+
self.stop_btn.config(state=stop,
|
| 273 |
+
bg="#d00" if stop == NORMAL else "#fca")
|
| 274 |
+
self.clear_btn.config(state=clear,
|
| 275 |
+
bg="#d00" if clear == NORMAL else "#fca")
|
| 276 |
+
self.output_lbl.config(text=txt, fg=color)
|
| 277 |
+
|
| 278 |
+
def makeLoadDemoMenu(self, master):
|
| 279 |
+
menu = Menu(master)
|
| 280 |
+
|
| 281 |
+
for entry in getExampleEntries():
|
| 282 |
+
def load(entry=entry):
|
| 283 |
+
self.loadfile(entry)
|
| 284 |
+
menu.add_command(label=entry, underline=0,
|
| 285 |
+
font=menufont, command=load)
|
| 286 |
+
return menu
|
| 287 |
+
|
| 288 |
+
def makeFontMenu(self, master):
|
| 289 |
+
menu = Menu(master)
|
| 290 |
+
menu.add_command(label="Decrease (C-'-')", command=self.decrease_size,
|
| 291 |
+
font=menufont)
|
| 292 |
+
menu.add_command(label="Increase (C-'+')", command=self.increase_size,
|
| 293 |
+
font=menufont)
|
| 294 |
+
menu.add_separator()
|
| 295 |
+
|
| 296 |
+
for size in font_sizes:
|
| 297 |
+
def resize(size=size):
|
| 298 |
+
self.set_txtsize(size)
|
| 299 |
+
menu.add_command(label=str(size), underline=0,
|
| 300 |
+
font=menufont, command=resize)
|
| 301 |
+
return menu
|
| 302 |
+
|
| 303 |
+
def makeHelpMenu(self, master):
|
| 304 |
+
menu = Menu(master)
|
| 305 |
+
|
| 306 |
+
for help_label, help_file in help_entries:
|
| 307 |
+
def show(help_label=help_label, help_file=help_file):
|
| 308 |
+
view_text(self.root, help_label, help_file)
|
| 309 |
+
menu.add_command(label=help_label, font=menufont, command=show)
|
| 310 |
+
return menu
|
| 311 |
+
|
| 312 |
+
def refreshCanvas(self):
|
| 313 |
+
if self.dirty:
|
| 314 |
+
self.screen.clear()
|
| 315 |
+
self.dirty=False
|
| 316 |
+
|
| 317 |
+
def loadfile(self, filename):
|
| 318 |
+
self.clearCanvas()
|
| 319 |
+
turtle.TurtleScreen._RUNNING = False
|
| 320 |
+
modname = 'turtledemo.' + filename
|
| 321 |
+
__import__(modname)
|
| 322 |
+
self.module = sys.modules[modname]
|
| 323 |
+
with open(self.module.__file__, 'r') as f:
|
| 324 |
+
chars = f.read()
|
| 325 |
+
self.text.delete("1.0", "end")
|
| 326 |
+
self.text.insert("1.0", chars)
|
| 327 |
+
self.root.title(filename + " - a Python turtle graphics example")
|
| 328 |
+
self.configGUI(NORMAL, DISABLED, DISABLED,
|
| 329 |
+
"Press start button", "red")
|
| 330 |
+
self.state = READY
|
| 331 |
+
|
| 332 |
+
def startDemo(self):
|
| 333 |
+
self.refreshCanvas()
|
| 334 |
+
self.dirty = True
|
| 335 |
+
turtle.TurtleScreen._RUNNING = True
|
| 336 |
+
self.configGUI(DISABLED, NORMAL, DISABLED,
|
| 337 |
+
"demo running...", "black")
|
| 338 |
+
self.screen.clear()
|
| 339 |
+
self.screen.mode("standard")
|
| 340 |
+
self.state = RUNNING
|
| 341 |
+
|
| 342 |
+
try:
|
| 343 |
+
result = self.module.main()
|
| 344 |
+
if result == "EVENTLOOP":
|
| 345 |
+
self.state = EVENTDRIVEN
|
| 346 |
+
else:
|
| 347 |
+
self.state = DONE
|
| 348 |
+
except turtle.Terminator:
|
| 349 |
+
if self.root is None:
|
| 350 |
+
return
|
| 351 |
+
self.state = DONE
|
| 352 |
+
result = "stopped!"
|
| 353 |
+
if self.state == DONE:
|
| 354 |
+
self.configGUI(NORMAL, DISABLED, NORMAL,
|
| 355 |
+
result)
|
| 356 |
+
elif self.state == EVENTDRIVEN:
|
| 357 |
+
self.exitflag = True
|
| 358 |
+
self.configGUI(DISABLED, NORMAL, DISABLED,
|
| 359 |
+
"use mouse/keys or STOP", "red")
|
| 360 |
+
|
| 361 |
+
def clearCanvas(self):
|
| 362 |
+
self.refreshCanvas()
|
| 363 |
+
self.screen._delete("all")
|
| 364 |
+
self.scanvas.config(cursor="")
|
| 365 |
+
self.configGUI(NORMAL, DISABLED, DISABLED)
|
| 366 |
+
|
| 367 |
+
def stopIt(self):
|
| 368 |
+
if self.exitflag:
|
| 369 |
+
self.clearCanvas()
|
| 370 |
+
self.exitflag = False
|
| 371 |
+
self.configGUI(NORMAL, DISABLED, DISABLED,
|
| 372 |
+
"STOPPED!", "red")
|
| 373 |
+
turtle.TurtleScreen._RUNNING = False
|
| 374 |
+
|
| 375 |
+
def _destroy(self):
|
| 376 |
+
turtle.TurtleScreen._RUNNING = False
|
| 377 |
+
self.root.destroy()
|
| 378 |
+
self.root = None
|
| 379 |
+
|
| 380 |
+
|
| 381 |
+
def main():
|
| 382 |
+
demo = DemoWindow()
|
| 383 |
+
demo.root.mainloop()
|
| 384 |
+
|
| 385 |
+
if __name__ == '__main__':
|
| 386 |
+
main()
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/turtledemo/bytedesign.py
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
""" turtle-example-suite:
|
| 3 |
+
|
| 4 |
+
tdemo_bytedesign.py
|
| 5 |
+
|
| 6 |
+
An example adapted from the example-suite
|
| 7 |
+
of PythonCard's turtle graphics.
|
| 8 |
+
|
| 9 |
+
It's based on an article in BYTE magazine
|
| 10 |
+
Problem Solving with Logo: Using Turtle
|
| 11 |
+
Graphics to Redraw a Design
|
| 12 |
+
November 1982, p. 118 - 134
|
| 13 |
+
|
| 14 |
+
-------------------------------------------
|
| 15 |
+
|
| 16 |
+
Due to the statement
|
| 17 |
+
|
| 18 |
+
t.delay(0)
|
| 19 |
+
|
| 20 |
+
in line 152, which sets the animation delay
|
| 21 |
+
to 0, this animation runs in "line per line"
|
| 22 |
+
mode as fast as possible.
|
| 23 |
+
"""
|
| 24 |
+
|
| 25 |
+
from turtle import Turtle, mainloop
|
| 26 |
+
from time import perf_counter as clock
|
| 27 |
+
|
| 28 |
+
# wrapper for any additional drawing routines
|
| 29 |
+
# that need to know about each other
|
| 30 |
+
class Designer(Turtle):
|
| 31 |
+
|
| 32 |
+
def design(self, homePos, scale):
|
| 33 |
+
self.up()
|
| 34 |
+
for i in range(5):
|
| 35 |
+
self.forward(64.65 * scale)
|
| 36 |
+
self.down()
|
| 37 |
+
self.wheel(self.position(), scale)
|
| 38 |
+
self.up()
|
| 39 |
+
self.backward(64.65 * scale)
|
| 40 |
+
self.right(72)
|
| 41 |
+
self.up()
|
| 42 |
+
self.goto(homePos)
|
| 43 |
+
self.right(36)
|
| 44 |
+
self.forward(24.5 * scale)
|
| 45 |
+
self.right(198)
|
| 46 |
+
self.down()
|
| 47 |
+
self.centerpiece(46 * scale, 143.4, scale)
|
| 48 |
+
self.getscreen().tracer(True)
|
| 49 |
+
|
| 50 |
+
def wheel(self, initpos, scale):
|
| 51 |
+
self.right(54)
|
| 52 |
+
for i in range(4):
|
| 53 |
+
self.pentpiece(initpos, scale)
|
| 54 |
+
self.down()
|
| 55 |
+
self.left(36)
|
| 56 |
+
for i in range(5):
|
| 57 |
+
self.tripiece(initpos, scale)
|
| 58 |
+
self.left(36)
|
| 59 |
+
for i in range(5):
|
| 60 |
+
self.down()
|
| 61 |
+
self.right(72)
|
| 62 |
+
self.forward(28 * scale)
|
| 63 |
+
self.up()
|
| 64 |
+
self.backward(28 * scale)
|
| 65 |
+
self.left(54)
|
| 66 |
+
self.getscreen().update()
|
| 67 |
+
|
| 68 |
+
def tripiece(self, initpos, scale):
|
| 69 |
+
oldh = self.heading()
|
| 70 |
+
self.down()
|
| 71 |
+
self.backward(2.5 * scale)
|
| 72 |
+
self.tripolyr(31.5 * scale, scale)
|
| 73 |
+
self.up()
|
| 74 |
+
self.goto(initpos)
|
| 75 |
+
self.setheading(oldh)
|
| 76 |
+
self.down()
|
| 77 |
+
self.backward(2.5 * scale)
|
| 78 |
+
self.tripolyl(31.5 * scale, scale)
|
| 79 |
+
self.up()
|
| 80 |
+
self.goto(initpos)
|
| 81 |
+
self.setheading(oldh)
|
| 82 |
+
self.left(72)
|
| 83 |
+
self.getscreen().update()
|
| 84 |
+
|
| 85 |
+
def pentpiece(self, initpos, scale):
|
| 86 |
+
oldh = self.heading()
|
| 87 |
+
self.up()
|
| 88 |
+
self.forward(29 * scale)
|
| 89 |
+
self.down()
|
| 90 |
+
for i in range(5):
|
| 91 |
+
self.forward(18 * scale)
|
| 92 |
+
self.right(72)
|
| 93 |
+
self.pentr(18 * scale, 75, scale)
|
| 94 |
+
self.up()
|
| 95 |
+
self.goto(initpos)
|
| 96 |
+
self.setheading(oldh)
|
| 97 |
+
self.forward(29 * scale)
|
| 98 |
+
self.down()
|
| 99 |
+
for i in range(5):
|
| 100 |
+
self.forward(18 * scale)
|
| 101 |
+
self.right(72)
|
| 102 |
+
self.pentl(18 * scale, 75, scale)
|
| 103 |
+
self.up()
|
| 104 |
+
self.goto(initpos)
|
| 105 |
+
self.setheading(oldh)
|
| 106 |
+
self.left(72)
|
| 107 |
+
self.getscreen().update()
|
| 108 |
+
|
| 109 |
+
def pentl(self, side, ang, scale):
|
| 110 |
+
if side < (2 * scale): return
|
| 111 |
+
self.forward(side)
|
| 112 |
+
self.left(ang)
|
| 113 |
+
self.pentl(side - (.38 * scale), ang, scale)
|
| 114 |
+
|
| 115 |
+
def pentr(self, side, ang, scale):
|
| 116 |
+
if side < (2 * scale): return
|
| 117 |
+
self.forward(side)
|
| 118 |
+
self.right(ang)
|
| 119 |
+
self.pentr(side - (.38 * scale), ang, scale)
|
| 120 |
+
|
| 121 |
+
def tripolyr(self, side, scale):
|
| 122 |
+
if side < (4 * scale): return
|
| 123 |
+
self.forward(side)
|
| 124 |
+
self.right(111)
|
| 125 |
+
self.forward(side / 1.78)
|
| 126 |
+
self.right(111)
|
| 127 |
+
self.forward(side / 1.3)
|
| 128 |
+
self.right(146)
|
| 129 |
+
self.tripolyr(side * .75, scale)
|
| 130 |
+
|
| 131 |
+
def tripolyl(self, side, scale):
|
| 132 |
+
if side < (4 * scale): return
|
| 133 |
+
self.forward(side)
|
| 134 |
+
self.left(111)
|
| 135 |
+
self.forward(side / 1.78)
|
| 136 |
+
self.left(111)
|
| 137 |
+
self.forward(side / 1.3)
|
| 138 |
+
self.left(146)
|
| 139 |
+
self.tripolyl(side * .75, scale)
|
| 140 |
+
|
| 141 |
+
def centerpiece(self, s, a, scale):
|
| 142 |
+
self.forward(s); self.left(a)
|
| 143 |
+
if s < (7.5 * scale):
|
| 144 |
+
return
|
| 145 |
+
self.centerpiece(s - (1.2 * scale), a, scale)
|
| 146 |
+
|
| 147 |
+
def main():
|
| 148 |
+
t = Designer()
|
| 149 |
+
t.speed(0)
|
| 150 |
+
t.hideturtle()
|
| 151 |
+
t.getscreen().delay(0)
|
| 152 |
+
t.getscreen().tracer(0)
|
| 153 |
+
at = clock()
|
| 154 |
+
t.design(t.position(), 2)
|
| 155 |
+
et = clock()
|
| 156 |
+
return "runtime: %.2f sec." % (et-at)
|
| 157 |
+
|
| 158 |
+
if __name__ == '__main__':
|
| 159 |
+
msg = main()
|
| 160 |
+
print(msg)
|
| 161 |
+
mainloop()
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/turtledemo/chaos.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# File: tdemo_chaos.py
|
| 2 |
+
# Author: Gregor Lingl
|
| 3 |
+
# Date: 2009-06-24
|
| 4 |
+
|
| 5 |
+
# A demonstration of chaos
|
| 6 |
+
|
| 7 |
+
from turtle import *
|
| 8 |
+
|
| 9 |
+
N = 80
|
| 10 |
+
|
| 11 |
+
def f(x):
|
| 12 |
+
return 3.9*x*(1-x)
|
| 13 |
+
|
| 14 |
+
def g(x):
|
| 15 |
+
return 3.9*(x-x**2)
|
| 16 |
+
|
| 17 |
+
def h(x):
|
| 18 |
+
return 3.9*x-3.9*x*x
|
| 19 |
+
|
| 20 |
+
def jumpto(x, y):
|
| 21 |
+
penup(); goto(x,y)
|
| 22 |
+
|
| 23 |
+
def line(x1, y1, x2, y2):
|
| 24 |
+
jumpto(x1, y1)
|
| 25 |
+
pendown()
|
| 26 |
+
goto(x2, y2)
|
| 27 |
+
|
| 28 |
+
def coosys():
|
| 29 |
+
line(-1, 0, N+1, 0)
|
| 30 |
+
line(0, -0.1, 0, 1.1)
|
| 31 |
+
|
| 32 |
+
def plot(fun, start, color):
|
| 33 |
+
pencolor(color)
|
| 34 |
+
x = start
|
| 35 |
+
jumpto(0, x)
|
| 36 |
+
pendown()
|
| 37 |
+
dot(5)
|
| 38 |
+
for i in range(N):
|
| 39 |
+
x=fun(x)
|
| 40 |
+
goto(i+1,x)
|
| 41 |
+
dot(5)
|
| 42 |
+
|
| 43 |
+
def main():
|
| 44 |
+
reset()
|
| 45 |
+
setworldcoordinates(-1.0,-0.1, N+1, 1.1)
|
| 46 |
+
speed(0)
|
| 47 |
+
hideturtle()
|
| 48 |
+
coosys()
|
| 49 |
+
plot(f, 0.35, "blue")
|
| 50 |
+
plot(g, 0.35, "green")
|
| 51 |
+
plot(h, 0.35, "red")
|
| 52 |
+
# Now zoom in:
|
| 53 |
+
for s in range(100):
|
| 54 |
+
setworldcoordinates(0.5*s,-0.1, N+1, 1.1)
|
| 55 |
+
return "Done!"
|
| 56 |
+
|
| 57 |
+
if __name__ == "__main__":
|
| 58 |
+
main()
|
| 59 |
+
mainloop()
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/turtledemo/clock.py
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
# -*- coding: cp1252 -*-
|
| 3 |
+
""" turtle-example-suite:
|
| 4 |
+
|
| 5 |
+
tdemo_clock.py
|
| 6 |
+
|
| 7 |
+
Enhanced clock-program, showing date
|
| 8 |
+
and time
|
| 9 |
+
------------------------------------
|
| 10 |
+
Press STOP to exit the program!
|
| 11 |
+
------------------------------------
|
| 12 |
+
"""
|
| 13 |
+
from turtle import *
|
| 14 |
+
from datetime import datetime
|
| 15 |
+
|
| 16 |
+
def jump(distanz, winkel=0):
|
| 17 |
+
penup()
|
| 18 |
+
right(winkel)
|
| 19 |
+
forward(distanz)
|
| 20 |
+
left(winkel)
|
| 21 |
+
pendown()
|
| 22 |
+
|
| 23 |
+
def hand(laenge, spitze):
|
| 24 |
+
fd(laenge*1.15)
|
| 25 |
+
rt(90)
|
| 26 |
+
fd(spitze/2.0)
|
| 27 |
+
lt(120)
|
| 28 |
+
fd(spitze)
|
| 29 |
+
lt(120)
|
| 30 |
+
fd(spitze)
|
| 31 |
+
lt(120)
|
| 32 |
+
fd(spitze/2.0)
|
| 33 |
+
|
| 34 |
+
def make_hand_shape(name, laenge, spitze):
|
| 35 |
+
reset()
|
| 36 |
+
jump(-laenge*0.15)
|
| 37 |
+
begin_poly()
|
| 38 |
+
hand(laenge, spitze)
|
| 39 |
+
end_poly()
|
| 40 |
+
hand_form = get_poly()
|
| 41 |
+
register_shape(name, hand_form)
|
| 42 |
+
|
| 43 |
+
def clockface(radius):
|
| 44 |
+
reset()
|
| 45 |
+
pensize(7)
|
| 46 |
+
for i in range(60):
|
| 47 |
+
jump(radius)
|
| 48 |
+
if i % 5 == 0:
|
| 49 |
+
fd(25)
|
| 50 |
+
jump(-radius-25)
|
| 51 |
+
else:
|
| 52 |
+
dot(3)
|
| 53 |
+
jump(-radius)
|
| 54 |
+
rt(6)
|
| 55 |
+
|
| 56 |
+
def setup():
|
| 57 |
+
global second_hand, minute_hand, hour_hand, writer
|
| 58 |
+
mode("logo")
|
| 59 |
+
make_hand_shape("second_hand", 125, 25)
|
| 60 |
+
make_hand_shape("minute_hand", 130, 25)
|
| 61 |
+
make_hand_shape("hour_hand", 90, 25)
|
| 62 |
+
clockface(160)
|
| 63 |
+
second_hand = Turtle()
|
| 64 |
+
second_hand.shape("second_hand")
|
| 65 |
+
second_hand.color("gray20", "gray80")
|
| 66 |
+
minute_hand = Turtle()
|
| 67 |
+
minute_hand.shape("minute_hand")
|
| 68 |
+
minute_hand.color("blue1", "red1")
|
| 69 |
+
hour_hand = Turtle()
|
| 70 |
+
hour_hand.shape("hour_hand")
|
| 71 |
+
hour_hand.color("blue3", "red3")
|
| 72 |
+
for hand in second_hand, minute_hand, hour_hand:
|
| 73 |
+
hand.resizemode("user")
|
| 74 |
+
hand.shapesize(1, 1, 3)
|
| 75 |
+
hand.speed(0)
|
| 76 |
+
ht()
|
| 77 |
+
writer = Turtle()
|
| 78 |
+
#writer.mode("logo")
|
| 79 |
+
writer.ht()
|
| 80 |
+
writer.pu()
|
| 81 |
+
writer.bk(85)
|
| 82 |
+
|
| 83 |
+
def wochentag(t):
|
| 84 |
+
wochentag = ["Monday", "Tuesday", "Wednesday",
|
| 85 |
+
"Thursday", "Friday", "Saturday", "Sunday"]
|
| 86 |
+
return wochentag[t.weekday()]
|
| 87 |
+
|
| 88 |
+
def datum(z):
|
| 89 |
+
monat = ["Jan.", "Feb.", "Mar.", "Apr.", "May", "June",
|
| 90 |
+
"July", "Aug.", "Sep.", "Oct.", "Nov.", "Dec."]
|
| 91 |
+
j = z.year
|
| 92 |
+
m = monat[z.month - 1]
|
| 93 |
+
t = z.day
|
| 94 |
+
return "%s %d %d" % (m, t, j)
|
| 95 |
+
|
| 96 |
+
def tick():
|
| 97 |
+
t = datetime.today()
|
| 98 |
+
sekunde = t.second + t.microsecond*0.000001
|
| 99 |
+
minute = t.minute + sekunde/60.0
|
| 100 |
+
stunde = t.hour + minute/60.0
|
| 101 |
+
try:
|
| 102 |
+
tracer(False) # Terminator can occur here
|
| 103 |
+
writer.clear()
|
| 104 |
+
writer.home()
|
| 105 |
+
writer.forward(65)
|
| 106 |
+
writer.write(wochentag(t),
|
| 107 |
+
align="center", font=("Courier", 14, "bold"))
|
| 108 |
+
writer.back(150)
|
| 109 |
+
writer.write(datum(t),
|
| 110 |
+
align="center", font=("Courier", 14, "bold"))
|
| 111 |
+
writer.forward(85)
|
| 112 |
+
tracer(True)
|
| 113 |
+
second_hand.setheading(6*sekunde) # or here
|
| 114 |
+
minute_hand.setheading(6*minute)
|
| 115 |
+
hour_hand.setheading(30*stunde)
|
| 116 |
+
tracer(True)
|
| 117 |
+
ontimer(tick, 100)
|
| 118 |
+
except Terminator:
|
| 119 |
+
pass # turtledemo user pressed STOP
|
| 120 |
+
|
| 121 |
+
def main():
|
| 122 |
+
tracer(False)
|
| 123 |
+
setup()
|
| 124 |
+
tracer(True)
|
| 125 |
+
tick()
|
| 126 |
+
return "EVENTLOOP"
|
| 127 |
+
|
| 128 |
+
if __name__ == "__main__":
|
| 129 |
+
mode("logo")
|
| 130 |
+
msg = main()
|
| 131 |
+
print(msg)
|
| 132 |
+
mainloop()
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/turtledemo/colormixer.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# colormixer
|
| 2 |
+
|
| 3 |
+
from turtle import Screen, Turtle, mainloop
|
| 4 |
+
|
| 5 |
+
class ColorTurtle(Turtle):
|
| 6 |
+
|
| 7 |
+
def __init__(self, x, y):
|
| 8 |
+
Turtle.__init__(self)
|
| 9 |
+
self.shape("turtle")
|
| 10 |
+
self.resizemode("user")
|
| 11 |
+
self.shapesize(3,3,5)
|
| 12 |
+
self.pensize(10)
|
| 13 |
+
self._color = [0,0,0]
|
| 14 |
+
self.x = x
|
| 15 |
+
self._color[x] = y
|
| 16 |
+
self.color(self._color)
|
| 17 |
+
self.speed(0)
|
| 18 |
+
self.left(90)
|
| 19 |
+
self.pu()
|
| 20 |
+
self.goto(x,0)
|
| 21 |
+
self.pd()
|
| 22 |
+
self.sety(1)
|
| 23 |
+
self.pu()
|
| 24 |
+
self.sety(y)
|
| 25 |
+
self.pencolor("gray25")
|
| 26 |
+
self.ondrag(self.shift)
|
| 27 |
+
|
| 28 |
+
def shift(self, x, y):
|
| 29 |
+
self.sety(max(0,min(y,1)))
|
| 30 |
+
self._color[self.x] = self.ycor()
|
| 31 |
+
self.fillcolor(self._color)
|
| 32 |
+
setbgcolor()
|
| 33 |
+
|
| 34 |
+
def setbgcolor():
|
| 35 |
+
screen.bgcolor(red.ycor(), green.ycor(), blue.ycor())
|
| 36 |
+
|
| 37 |
+
def main():
|
| 38 |
+
global screen, red, green, blue
|
| 39 |
+
screen = Screen()
|
| 40 |
+
screen.delay(0)
|
| 41 |
+
screen.setworldcoordinates(-1, -0.3, 3, 1.3)
|
| 42 |
+
|
| 43 |
+
red = ColorTurtle(0, .5)
|
| 44 |
+
green = ColorTurtle(1, .5)
|
| 45 |
+
blue = ColorTurtle(2, .5)
|
| 46 |
+
setbgcolor()
|
| 47 |
+
|
| 48 |
+
writer = Turtle()
|
| 49 |
+
writer.ht()
|
| 50 |
+
writer.pu()
|
| 51 |
+
writer.goto(1,1.15)
|
| 52 |
+
writer.write("DRAG!",align="center",font=("Arial",30,("bold","italic")))
|
| 53 |
+
return "EVENTLOOP"
|
| 54 |
+
|
| 55 |
+
if __name__ == "__main__":
|
| 56 |
+
msg = main()
|
| 57 |
+
print(msg)
|
| 58 |
+
mainloop()
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/turtledemo/forest.py
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
""" turtlegraphics-example-suite:
|
| 3 |
+
|
| 4 |
+
tdemo_forest.py
|
| 5 |
+
|
| 6 |
+
Displays a 'forest' of 3 breadth-first-trees
|
| 7 |
+
similar to the one in tree.
|
| 8 |
+
For further remarks see tree.py
|
| 9 |
+
|
| 10 |
+
This example is a 'breadth-first'-rewrite of
|
| 11 |
+
a Logo program written by Erich Neuwirth. See
|
| 12 |
+
http://homepage.univie.ac.at/erich.neuwirth/
|
| 13 |
+
"""
|
| 14 |
+
from turtle import Turtle, colormode, tracer, mainloop
|
| 15 |
+
from random import randrange
|
| 16 |
+
from time import perf_counter as clock
|
| 17 |
+
|
| 18 |
+
def symRandom(n):
|
| 19 |
+
return randrange(-n,n+1)
|
| 20 |
+
|
| 21 |
+
def randomize( branchlist, angledist, sizedist ):
|
| 22 |
+
return [ (angle+symRandom(angledist),
|
| 23 |
+
sizefactor*1.01**symRandom(sizedist))
|
| 24 |
+
for angle, sizefactor in branchlist ]
|
| 25 |
+
|
| 26 |
+
def randomfd( t, distance, parts, angledist ):
|
| 27 |
+
for i in range(parts):
|
| 28 |
+
t.left(symRandom(angledist))
|
| 29 |
+
t.forward( (1.0 * distance)/parts )
|
| 30 |
+
|
| 31 |
+
def tree(tlist, size, level, widthfactor, branchlists, angledist=10, sizedist=5):
|
| 32 |
+
# benutzt Liste von turtles und Liste von Zweiglisten,
|
| 33 |
+
# fuer jede turtle eine!
|
| 34 |
+
if level > 0:
|
| 35 |
+
lst = []
|
| 36 |
+
brs = []
|
| 37 |
+
for t, branchlist in list(zip(tlist,branchlists)):
|
| 38 |
+
t.pensize( size * widthfactor )
|
| 39 |
+
t.pencolor( 255 - (180 - 11 * level + symRandom(15)),
|
| 40 |
+
180 - 11 * level + symRandom(15),
|
| 41 |
+
0 )
|
| 42 |
+
t.pendown()
|
| 43 |
+
randomfd(t, size, level, angledist )
|
| 44 |
+
yield 1
|
| 45 |
+
for angle, sizefactor in branchlist:
|
| 46 |
+
t.left(angle)
|
| 47 |
+
lst.append(t.clone())
|
| 48 |
+
brs.append(randomize(branchlist, angledist, sizedist))
|
| 49 |
+
t.right(angle)
|
| 50 |
+
for x in tree(lst, size*sizefactor, level-1, widthfactor, brs,
|
| 51 |
+
angledist, sizedist):
|
| 52 |
+
yield None
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def start(t,x,y):
|
| 56 |
+
colormode(255)
|
| 57 |
+
t.reset()
|
| 58 |
+
t.speed(0)
|
| 59 |
+
t.hideturtle()
|
| 60 |
+
t.left(90)
|
| 61 |
+
t.penup()
|
| 62 |
+
t.setpos(x,y)
|
| 63 |
+
t.pendown()
|
| 64 |
+
|
| 65 |
+
def doit1(level, pen):
|
| 66 |
+
pen.hideturtle()
|
| 67 |
+
start(pen, 20, -208)
|
| 68 |
+
t = tree( [pen], 80, level, 0.1, [[ (45,0.69), (0,0.65), (-45,0.71) ]] )
|
| 69 |
+
return t
|
| 70 |
+
|
| 71 |
+
def doit2(level, pen):
|
| 72 |
+
pen.hideturtle()
|
| 73 |
+
start(pen, -135, -130)
|
| 74 |
+
t = tree( [pen], 120, level, 0.1, [[ (45,0.69), (-45,0.71) ]] )
|
| 75 |
+
return t
|
| 76 |
+
|
| 77 |
+
def doit3(level, pen):
|
| 78 |
+
pen.hideturtle()
|
| 79 |
+
start(pen, 190, -90)
|
| 80 |
+
t = tree( [pen], 100, level, 0.1, [[ (45,0.7), (0,0.72), (-45,0.65) ]] )
|
| 81 |
+
return t
|
| 82 |
+
|
| 83 |
+
# Hier 3 Baumgeneratoren:
|
| 84 |
+
def main():
|
| 85 |
+
p = Turtle()
|
| 86 |
+
p.ht()
|
| 87 |
+
tracer(75,0)
|
| 88 |
+
u = doit1(6, Turtle(undobuffersize=1))
|
| 89 |
+
s = doit2(7, Turtle(undobuffersize=1))
|
| 90 |
+
t = doit3(5, Turtle(undobuffersize=1))
|
| 91 |
+
a = clock()
|
| 92 |
+
while True:
|
| 93 |
+
done = 0
|
| 94 |
+
for b in u,s,t:
|
| 95 |
+
try:
|
| 96 |
+
b.__next__()
|
| 97 |
+
except:
|
| 98 |
+
done += 1
|
| 99 |
+
if done == 3:
|
| 100 |
+
break
|
| 101 |
+
|
| 102 |
+
tracer(1,10)
|
| 103 |
+
b = clock()
|
| 104 |
+
return "runtime: %.2f sec." % (b-a)
|
| 105 |
+
|
| 106 |
+
if __name__ == '__main__':
|
| 107 |
+
main()
|
| 108 |
+
mainloop()
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/turtledemo/lindenmayer.py
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
""" turtle-example-suite:
|
| 3 |
+
|
| 4 |
+
xtx_lindenmayer_indian.py
|
| 5 |
+
|
| 6 |
+
Each morning women in Tamil Nadu, in southern
|
| 7 |
+
India, place designs, created by using rice
|
| 8 |
+
flour and known as kolam on the thresholds of
|
| 9 |
+
their homes.
|
| 10 |
+
|
| 11 |
+
These can be described by Lindenmayer systems,
|
| 12 |
+
which can easily be implemented with turtle
|
| 13 |
+
graphics and Python.
|
| 14 |
+
|
| 15 |
+
Two examples are shown here:
|
| 16 |
+
(1) the snake kolam
|
| 17 |
+
(2) anklets of Krishna
|
| 18 |
+
|
| 19 |
+
Taken from Marcia Ascher: Mathematics
|
| 20 |
+
Elsewhere, An Exploration of Ideas Across
|
| 21 |
+
Cultures
|
| 22 |
+
|
| 23 |
+
"""
|
| 24 |
+
################################
|
| 25 |
+
# Mini Lindenmayer tool
|
| 26 |
+
###############################
|
| 27 |
+
|
| 28 |
+
from turtle import *
|
| 29 |
+
|
| 30 |
+
def replace( seq, replacementRules, n ):
|
| 31 |
+
for i in range(n):
|
| 32 |
+
newseq = ""
|
| 33 |
+
for element in seq:
|
| 34 |
+
newseq = newseq + replacementRules.get(element,element)
|
| 35 |
+
seq = newseq
|
| 36 |
+
return seq
|
| 37 |
+
|
| 38 |
+
def draw( commands, rules ):
|
| 39 |
+
for b in commands:
|
| 40 |
+
try:
|
| 41 |
+
rules[b]()
|
| 42 |
+
except TypeError:
|
| 43 |
+
try:
|
| 44 |
+
draw(rules[b], rules)
|
| 45 |
+
except:
|
| 46 |
+
pass
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def main():
|
| 50 |
+
################################
|
| 51 |
+
# Example 1: Snake kolam
|
| 52 |
+
################################
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def r():
|
| 56 |
+
right(45)
|
| 57 |
+
|
| 58 |
+
def l():
|
| 59 |
+
left(45)
|
| 60 |
+
|
| 61 |
+
def f():
|
| 62 |
+
forward(7.5)
|
| 63 |
+
|
| 64 |
+
snake_rules = {"-":r, "+":l, "f":f, "b":"f+f+f--f--f+f+f"}
|
| 65 |
+
snake_replacementRules = {"b": "b+f+b--f--b+f+b"}
|
| 66 |
+
snake_start = "b--f--b--f"
|
| 67 |
+
|
| 68 |
+
drawing = replace(snake_start, snake_replacementRules, 3)
|
| 69 |
+
|
| 70 |
+
reset()
|
| 71 |
+
speed(3)
|
| 72 |
+
tracer(1,0)
|
| 73 |
+
ht()
|
| 74 |
+
up()
|
| 75 |
+
backward(195)
|
| 76 |
+
down()
|
| 77 |
+
draw(drawing, snake_rules)
|
| 78 |
+
|
| 79 |
+
from time import sleep
|
| 80 |
+
sleep(3)
|
| 81 |
+
|
| 82 |
+
################################
|
| 83 |
+
# Example 2: Anklets of Krishna
|
| 84 |
+
################################
|
| 85 |
+
|
| 86 |
+
def A():
|
| 87 |
+
color("red")
|
| 88 |
+
circle(10,90)
|
| 89 |
+
|
| 90 |
+
def B():
|
| 91 |
+
from math import sqrt
|
| 92 |
+
color("black")
|
| 93 |
+
l = 5/sqrt(2)
|
| 94 |
+
forward(l)
|
| 95 |
+
circle(l, 270)
|
| 96 |
+
forward(l)
|
| 97 |
+
|
| 98 |
+
def F():
|
| 99 |
+
color("green")
|
| 100 |
+
forward(10)
|
| 101 |
+
|
| 102 |
+
krishna_rules = {"a":A, "b":B, "f":F}
|
| 103 |
+
krishna_replacementRules = {"a" : "afbfa", "b" : "afbfbfbfa" }
|
| 104 |
+
krishna_start = "fbfbfbfb"
|
| 105 |
+
|
| 106 |
+
reset()
|
| 107 |
+
speed(0)
|
| 108 |
+
tracer(3,0)
|
| 109 |
+
ht()
|
| 110 |
+
left(45)
|
| 111 |
+
drawing = replace(krishna_start, krishna_replacementRules, 3)
|
| 112 |
+
draw(drawing, krishna_rules)
|
| 113 |
+
tracer(1)
|
| 114 |
+
return "Done!"
|
| 115 |
+
|
| 116 |
+
if __name__=='__main__':
|
| 117 |
+
msg = main()
|
| 118 |
+
print(msg)
|
| 119 |
+
mainloop()
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/turtledemo/minimal_hanoi.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
""" turtle-example-suite:
|
| 3 |
+
|
| 4 |
+
tdemo_minimal_hanoi.py
|
| 5 |
+
|
| 6 |
+
A minimal 'Towers of Hanoi' animation:
|
| 7 |
+
A tower of 6 discs is transferred from the
|
| 8 |
+
left to the right peg.
|
| 9 |
+
|
| 10 |
+
An imho quite elegant and concise
|
| 11 |
+
implementation using a tower class, which
|
| 12 |
+
is derived from the built-in type list.
|
| 13 |
+
|
| 14 |
+
Discs are turtles with shape "square", but
|
| 15 |
+
stretched to rectangles by shapesize()
|
| 16 |
+
---------------------------------------
|
| 17 |
+
To exit press STOP button
|
| 18 |
+
---------------------------------------
|
| 19 |
+
"""
|
| 20 |
+
from turtle import *
|
| 21 |
+
|
| 22 |
+
class Disc(Turtle):
|
| 23 |
+
def __init__(self, n):
|
| 24 |
+
Turtle.__init__(self, shape="square", visible=False)
|
| 25 |
+
self.pu()
|
| 26 |
+
self.shapesize(1.5, n*1.5, 2) # square-->rectangle
|
| 27 |
+
self.fillcolor(n/6., 0, 1-n/6.)
|
| 28 |
+
self.st()
|
| 29 |
+
|
| 30 |
+
class Tower(list):
|
| 31 |
+
"Hanoi tower, a subclass of built-in type list"
|
| 32 |
+
def __init__(self, x):
|
| 33 |
+
"create an empty tower. x is x-position of peg"
|
| 34 |
+
self.x = x
|
| 35 |
+
def push(self, d):
|
| 36 |
+
d.setx(self.x)
|
| 37 |
+
d.sety(-150+34*len(self))
|
| 38 |
+
self.append(d)
|
| 39 |
+
def pop(self):
|
| 40 |
+
d = list.pop(self)
|
| 41 |
+
d.sety(150)
|
| 42 |
+
return d
|
| 43 |
+
|
| 44 |
+
def hanoi(n, from_, with_, to_):
|
| 45 |
+
if n > 0:
|
| 46 |
+
hanoi(n-1, from_, to_, with_)
|
| 47 |
+
to_.push(from_.pop())
|
| 48 |
+
hanoi(n-1, with_, from_, to_)
|
| 49 |
+
|
| 50 |
+
def play():
|
| 51 |
+
onkey(None,"space")
|
| 52 |
+
clear()
|
| 53 |
+
try:
|
| 54 |
+
hanoi(6, t1, t2, t3)
|
| 55 |
+
write("press STOP button to exit",
|
| 56 |
+
align="center", font=("Courier", 16, "bold"))
|
| 57 |
+
except Terminator:
|
| 58 |
+
pass # turtledemo user pressed STOP
|
| 59 |
+
|
| 60 |
+
def main():
|
| 61 |
+
global t1, t2, t3
|
| 62 |
+
ht(); penup(); goto(0, -225) # writer turtle
|
| 63 |
+
t1 = Tower(-250)
|
| 64 |
+
t2 = Tower(0)
|
| 65 |
+
t3 = Tower(250)
|
| 66 |
+
# make tower of 6 discs
|
| 67 |
+
for i in range(6,0,-1):
|
| 68 |
+
t1.push(Disc(i))
|
| 69 |
+
# prepare spartanic user interface ;-)
|
| 70 |
+
write("press spacebar to start game",
|
| 71 |
+
align="center", font=("Courier", 16, "bold"))
|
| 72 |
+
onkey(play, "space")
|
| 73 |
+
listen()
|
| 74 |
+
return "EVENTLOOP"
|
| 75 |
+
|
| 76 |
+
if __name__=="__main__":
|
| 77 |
+
msg = main()
|
| 78 |
+
print(msg)
|
| 79 |
+
mainloop()
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/turtledemo/paint.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
""" turtle-example-suite:
|
| 3 |
+
|
| 4 |
+
tdemo_paint.py
|
| 5 |
+
|
| 6 |
+
A simple event-driven paint program
|
| 7 |
+
|
| 8 |
+
- left mouse button moves turtle
|
| 9 |
+
- middle mouse button changes color
|
| 10 |
+
- right mouse button toggles between pen up
|
| 11 |
+
(no line drawn when the turtle moves) and
|
| 12 |
+
pen down (line is drawn). If pen up follows
|
| 13 |
+
at least two pen-down moves, the polygon that
|
| 14 |
+
includes the starting point is filled.
|
| 15 |
+
-------------------------------------------
|
| 16 |
+
Play around by clicking into the canvas
|
| 17 |
+
using all three mouse buttons.
|
| 18 |
+
-------------------------------------------
|
| 19 |
+
To exit press STOP button
|
| 20 |
+
-------------------------------------------
|
| 21 |
+
"""
|
| 22 |
+
from turtle import *
|
| 23 |
+
|
| 24 |
+
def switchupdown(x=0, y=0):
|
| 25 |
+
if pen()["pendown"]:
|
| 26 |
+
end_fill()
|
| 27 |
+
up()
|
| 28 |
+
else:
|
| 29 |
+
down()
|
| 30 |
+
begin_fill()
|
| 31 |
+
|
| 32 |
+
def changecolor(x=0, y=0):
|
| 33 |
+
global colors
|
| 34 |
+
colors = colors[1:]+colors[:1]
|
| 35 |
+
color(colors[0])
|
| 36 |
+
|
| 37 |
+
def main():
|
| 38 |
+
global colors
|
| 39 |
+
shape("circle")
|
| 40 |
+
resizemode("user")
|
| 41 |
+
shapesize(.5)
|
| 42 |
+
width(3)
|
| 43 |
+
colors=["red", "green", "blue", "yellow"]
|
| 44 |
+
color(colors[0])
|
| 45 |
+
switchupdown()
|
| 46 |
+
onscreenclick(goto,1)
|
| 47 |
+
onscreenclick(changecolor,2)
|
| 48 |
+
onscreenclick(switchupdown,3)
|
| 49 |
+
return "EVENTLOOP"
|
| 50 |
+
|
| 51 |
+
if __name__ == "__main__":
|
| 52 |
+
msg = main()
|
| 53 |
+
print(msg)
|
| 54 |
+
mainloop()
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/turtledemo/peace.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
""" turtle-example-suite:
|
| 3 |
+
|
| 4 |
+
tdemo_peace.py
|
| 5 |
+
|
| 6 |
+
A simple drawing suitable as a beginner's
|
| 7 |
+
programming example. Aside from the
|
| 8 |
+
peacecolors assignment and the for loop,
|
| 9 |
+
it only uses turtle commands.
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
from turtle import *
|
| 13 |
+
|
| 14 |
+
def main():
|
| 15 |
+
peacecolors = ("red3", "orange", "yellow",
|
| 16 |
+
"seagreen4", "orchid4",
|
| 17 |
+
"royalblue1", "dodgerblue4")
|
| 18 |
+
|
| 19 |
+
reset()
|
| 20 |
+
Screen()
|
| 21 |
+
up()
|
| 22 |
+
goto(-320,-195)
|
| 23 |
+
width(70)
|
| 24 |
+
|
| 25 |
+
for pcolor in peacecolors:
|
| 26 |
+
color(pcolor)
|
| 27 |
+
down()
|
| 28 |
+
forward(640)
|
| 29 |
+
up()
|
| 30 |
+
backward(640)
|
| 31 |
+
left(90)
|
| 32 |
+
forward(66)
|
| 33 |
+
right(90)
|
| 34 |
+
|
| 35 |
+
width(25)
|
| 36 |
+
color("white")
|
| 37 |
+
goto(0,-170)
|
| 38 |
+
down()
|
| 39 |
+
|
| 40 |
+
circle(170)
|
| 41 |
+
left(90)
|
| 42 |
+
forward(340)
|
| 43 |
+
up()
|
| 44 |
+
left(180)
|
| 45 |
+
forward(170)
|
| 46 |
+
right(45)
|
| 47 |
+
down()
|
| 48 |
+
forward(170)
|
| 49 |
+
up()
|
| 50 |
+
backward(170)
|
| 51 |
+
left(90)
|
| 52 |
+
down()
|
| 53 |
+
forward(170)
|
| 54 |
+
up()
|
| 55 |
+
|
| 56 |
+
goto(0,300) # vanish if hideturtle() is not available ;-)
|
| 57 |
+
return "Done!"
|
| 58 |
+
|
| 59 |
+
if __name__ == "__main__":
|
| 60 |
+
main()
|
| 61 |
+
mainloop()
|