Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- my_container_sandbox/workspace/anaconda3/lib/python3.8/__pycache__/__future__.cpython-38.pyc +0 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/__pycache__/_bootlocale.cpython-38.pyc +0 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/__pycache__/_sitebuiltins.cpython-38.pyc +0 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/__pycache__/_strptime.cpython-38.pyc +0 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/__pycache__/_sysconfigdata_i686_conda_linux_gnu.cpython-38.pyc +0 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/__pycache__/asynchat.cpython-38.pyc +0 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/__pycache__/code.cpython-38.pyc +0 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/__pycache__/codecs.cpython-38.pyc +0 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/__pycache__/codeop.cpython-38.pyc +0 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/__pycache__/compileall.cpython-38.pyc +0 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/__pycache__/dis.cpython-38.pyc +0 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/__pycache__/filecmp.cpython-38.pyc +0 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/__pycache__/imp.cpython-38.pyc +0 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/__pycache__/inspect.cpython-38.pyc +0 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/__pycache__/posixpath.cpython-38.pyc +0 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/__pycache__/rlcompleter.cpython-38.pyc +0 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/__pycache__/runpy.cpython-38.pyc +0 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/__pycache__/secrets.cpython-38.pyc +0 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/__pycache__/typing.cpython-38.pyc +0 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/__pycache__/wave.cpython-38.pyc +0 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/ctypes/__init__.py +560 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/ctypes/_aix.py +331 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/ctypes/_endian.py +61 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/ctypes/util.py +397 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/ctypes/wintypes.py +202 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/http/__init__.py +142 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/http/client.py +1496 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/http/cookiejar.py +2113 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/http/cookies.py +609 -0
- my_container_sandbox/workspace/anaconda3/lib/python3.8/http/server.py +1316 -0
- my_container_sandbox/workspace/anaconda3/lib/tcl8/8.4/platform-1.0.18.tm +439 -0
- my_container_sandbox/workspace/anaconda3/lib/tcl8/8.4/platform/shell-1.1.4.tm +241 -0
- my_container_sandbox/workspace/anaconda3/lib/tcl8/8.5/msgcat-1.6.1.tm +1210 -0
- my_container_sandbox/workspace/anaconda3/lib/tcl8/8.5/tcltest-2.5.3.tm +0 -0
- my_container_sandbox/workspace/anaconda3/lib/tcl8/8.6/http-2.9.5.tm +0 -0
- my_container_sandbox/workspace/anaconda3/lib/tcl8/8.6/tdbc/sqlite3-1.1.3.tm +715 -0
- my_container_sandbox/workspace/anaconda3/lib/tk8.6/demos/images/earth.gif +0 -0
- my_container_sandbox/workspace/anaconda3/lib/tk8.6/demos/images/earthmenu.png +0 -0
- my_container_sandbox/workspace/anaconda3/lib/tk8.6/demos/images/earthris.gif +0 -0
- my_container_sandbox/workspace/anaconda3/lib/tk8.6/demos/images/flagdown.xbm +27 -0
- my_container_sandbox/workspace/anaconda3/lib/tk8.6/demos/images/flagup.xbm +27 -0
- my_container_sandbox/workspace/anaconda3/lib/tk8.6/demos/images/gray25.xbm +6 -0
- my_container_sandbox/workspace/anaconda3/lib/tk8.6/demos/images/letters.xbm +27 -0
- my_container_sandbox/workspace/anaconda3/lib/tk8.6/demos/images/noletter.xbm +27 -0
- my_container_sandbox/workspace/anaconda3/lib/tk8.6/demos/images/ouster.png +0 -0
- my_container_sandbox/workspace/anaconda3/lib/tk8.6/demos/images/pattern.xbm +6 -0
- my_container_sandbox/workspace/anaconda3/lib/tk8.6/demos/images/tcllogo.gif +0 -0
- my_container_sandbox/workspace/anaconda3/lib/tk8.6/images/README +7 -0
- my_container_sandbox/workspace/anaconda3/lib/tk8.6/images/logo.eps +2091 -0
- my_container_sandbox/workspace/anaconda3/lib/tk8.6/images/logo100.gif +0 -0
my_container_sandbox/workspace/anaconda3/lib/python3.8/__pycache__/__future__.cpython-38.pyc
ADDED
|
Binary file (4.15 kB). View file
|
|
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/__pycache__/_bootlocale.cpython-38.pyc
ADDED
|
Binary file (1.24 kB). View file
|
|
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/__pycache__/_sitebuiltins.cpython-38.pyc
ADDED
|
Binary file (3.48 kB). View file
|
|
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/__pycache__/_strptime.cpython-38.pyc
ADDED
|
Binary file (16 kB). View file
|
|
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/__pycache__/_sysconfigdata_i686_conda_linux_gnu.cpython-38.pyc
ADDED
|
Binary file (20.6 kB). View file
|
|
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/__pycache__/asynchat.cpython-38.pyc
ADDED
|
Binary file (6.85 kB). View file
|
|
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/__pycache__/code.cpython-38.pyc
ADDED
|
Binary file (9.91 kB). View file
|
|
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/__pycache__/codecs.cpython-38.pyc
ADDED
|
Binary file (34 kB). View file
|
|
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/__pycache__/codeop.cpython-38.pyc
ADDED
|
Binary file (6.41 kB). View file
|
|
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/__pycache__/compileall.cpython-38.pyc
ADDED
|
Binary file (9.41 kB). View file
|
|
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/__pycache__/dis.cpython-38.pyc
ADDED
|
Binary file (15.8 kB). View file
|
|
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/__pycache__/filecmp.cpython-38.pyc
ADDED
|
Binary file (8.42 kB). View file
|
|
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/__pycache__/imp.cpython-38.pyc
ADDED
|
Binary file (9.8 kB). View file
|
|
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/__pycache__/inspect.cpython-38.pyc
ADDED
|
Binary file (80.6 kB). View file
|
|
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/__pycache__/posixpath.cpython-38.pyc
ADDED
|
Binary file (10.4 kB). View file
|
|
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/__pycache__/rlcompleter.cpython-38.pyc
ADDED
|
Binary file (5.75 kB). View file
|
|
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/__pycache__/runpy.cpython-38.pyc
ADDED
|
Binary file (8.18 kB). View file
|
|
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/__pycache__/secrets.cpython-38.pyc
ADDED
|
Binary file (2.19 kB). View file
|
|
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/__pycache__/typing.cpython-38.pyc
ADDED
|
Binary file (62.4 kB). View file
|
|
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/__pycache__/wave.cpython-38.pyc
ADDED
|
Binary file (18.1 kB). View file
|
|
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/ctypes/__init__.py
ADDED
|
@@ -0,0 +1,560 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""create and manipulate C data types in Python"""
|
| 2 |
+
|
| 3 |
+
import os as _os, sys as _sys
|
| 4 |
+
|
| 5 |
+
__version__ = "1.1.0"
|
| 6 |
+
|
| 7 |
+
from _ctypes import Union, Structure, Array
|
| 8 |
+
from _ctypes import _Pointer
|
| 9 |
+
from _ctypes import CFuncPtr as _CFuncPtr
|
| 10 |
+
from _ctypes import __version__ as _ctypes_version
|
| 11 |
+
from _ctypes import RTLD_LOCAL, RTLD_GLOBAL
|
| 12 |
+
from _ctypes import ArgumentError
|
| 13 |
+
|
| 14 |
+
from struct import calcsize as _calcsize
|
| 15 |
+
|
| 16 |
+
if __version__ != _ctypes_version:
|
| 17 |
+
raise Exception("Version number mismatch", __version__, _ctypes_version)
|
| 18 |
+
|
| 19 |
+
if _os.name == "nt":
|
| 20 |
+
from _ctypes import FormatError
|
| 21 |
+
|
| 22 |
+
DEFAULT_MODE = RTLD_LOCAL
|
| 23 |
+
if _os.name == "posix" and _sys.platform == "darwin":
|
| 24 |
+
# On OS X 10.3, we use RTLD_GLOBAL as default mode
|
| 25 |
+
# because RTLD_LOCAL does not work at least on some
|
| 26 |
+
# libraries. OS X 10.3 is Darwin 7, so we check for
|
| 27 |
+
# that.
|
| 28 |
+
|
| 29 |
+
if int(_os.uname().release.split('.')[0]) < 8:
|
| 30 |
+
DEFAULT_MODE = RTLD_GLOBAL
|
| 31 |
+
|
| 32 |
+
from _ctypes import FUNCFLAG_CDECL as _FUNCFLAG_CDECL, \
|
| 33 |
+
FUNCFLAG_PYTHONAPI as _FUNCFLAG_PYTHONAPI, \
|
| 34 |
+
FUNCFLAG_USE_ERRNO as _FUNCFLAG_USE_ERRNO, \
|
| 35 |
+
FUNCFLAG_USE_LASTERROR as _FUNCFLAG_USE_LASTERROR
|
| 36 |
+
|
| 37 |
+
# WINOLEAPI -> HRESULT
|
| 38 |
+
# WINOLEAPI_(type)
|
| 39 |
+
#
|
| 40 |
+
# STDMETHODCALLTYPE
|
| 41 |
+
#
|
| 42 |
+
# STDMETHOD(name)
|
| 43 |
+
# STDMETHOD_(type, name)
|
| 44 |
+
#
|
| 45 |
+
# STDAPICALLTYPE
|
| 46 |
+
|
| 47 |
+
def create_string_buffer(init, size=None):
|
| 48 |
+
"""create_string_buffer(aBytes) -> character array
|
| 49 |
+
create_string_buffer(anInteger) -> character array
|
| 50 |
+
create_string_buffer(aBytes, anInteger) -> character array
|
| 51 |
+
"""
|
| 52 |
+
if isinstance(init, bytes):
|
| 53 |
+
if size is None:
|
| 54 |
+
size = len(init)+1
|
| 55 |
+
_sys.audit("ctypes.create_string_buffer", init, size)
|
| 56 |
+
buftype = c_char * size
|
| 57 |
+
buf = buftype()
|
| 58 |
+
buf.value = init
|
| 59 |
+
return buf
|
| 60 |
+
elif isinstance(init, int):
|
| 61 |
+
_sys.audit("ctypes.create_string_buffer", None, init)
|
| 62 |
+
buftype = c_char * init
|
| 63 |
+
buf = buftype()
|
| 64 |
+
return buf
|
| 65 |
+
raise TypeError(init)
|
| 66 |
+
|
| 67 |
+
def c_buffer(init, size=None):
|
| 68 |
+
## "deprecated, use create_string_buffer instead"
|
| 69 |
+
## import warnings
|
| 70 |
+
## warnings.warn("c_buffer is deprecated, use create_string_buffer instead",
|
| 71 |
+
## DeprecationWarning, stacklevel=2)
|
| 72 |
+
return create_string_buffer(init, size)
|
| 73 |
+
|
| 74 |
+
_c_functype_cache = {}
|
| 75 |
+
def CFUNCTYPE(restype, *argtypes, **kw):
|
| 76 |
+
"""CFUNCTYPE(restype, *argtypes,
|
| 77 |
+
use_errno=False, use_last_error=False) -> function prototype.
|
| 78 |
+
|
| 79 |
+
restype: the result type
|
| 80 |
+
argtypes: a sequence specifying the argument types
|
| 81 |
+
|
| 82 |
+
The function prototype can be called in different ways to create a
|
| 83 |
+
callable object:
|
| 84 |
+
|
| 85 |
+
prototype(integer address) -> foreign function
|
| 86 |
+
prototype(callable) -> create and return a C callable function from callable
|
| 87 |
+
prototype(integer index, method name[, paramflags]) -> foreign function calling a COM method
|
| 88 |
+
prototype((ordinal number, dll object)[, paramflags]) -> foreign function exported by ordinal
|
| 89 |
+
prototype((function name, dll object)[, paramflags]) -> foreign function exported by name
|
| 90 |
+
"""
|
| 91 |
+
flags = _FUNCFLAG_CDECL
|
| 92 |
+
if kw.pop("use_errno", False):
|
| 93 |
+
flags |= _FUNCFLAG_USE_ERRNO
|
| 94 |
+
if kw.pop("use_last_error", False):
|
| 95 |
+
flags |= _FUNCFLAG_USE_LASTERROR
|
| 96 |
+
if kw:
|
| 97 |
+
raise ValueError("unexpected keyword argument(s) %s" % kw.keys())
|
| 98 |
+
try:
|
| 99 |
+
return _c_functype_cache[(restype, argtypes, flags)]
|
| 100 |
+
except KeyError:
|
| 101 |
+
class CFunctionType(_CFuncPtr):
|
| 102 |
+
_argtypes_ = argtypes
|
| 103 |
+
_restype_ = restype
|
| 104 |
+
_flags_ = flags
|
| 105 |
+
_c_functype_cache[(restype, argtypes, flags)] = CFunctionType
|
| 106 |
+
return CFunctionType
|
| 107 |
+
|
| 108 |
+
if _os.name == "nt":
|
| 109 |
+
from _ctypes import LoadLibrary as _dlopen
|
| 110 |
+
from _ctypes import FUNCFLAG_STDCALL as _FUNCFLAG_STDCALL
|
| 111 |
+
|
| 112 |
+
_win_functype_cache = {}
|
| 113 |
+
def WINFUNCTYPE(restype, *argtypes, **kw):
|
| 114 |
+
# docstring set later (very similar to CFUNCTYPE.__doc__)
|
| 115 |
+
flags = _FUNCFLAG_STDCALL
|
| 116 |
+
if kw.pop("use_errno", False):
|
| 117 |
+
flags |= _FUNCFLAG_USE_ERRNO
|
| 118 |
+
if kw.pop("use_last_error", False):
|
| 119 |
+
flags |= _FUNCFLAG_USE_LASTERROR
|
| 120 |
+
if kw:
|
| 121 |
+
raise ValueError("unexpected keyword argument(s) %s" % kw.keys())
|
| 122 |
+
try:
|
| 123 |
+
return _win_functype_cache[(restype, argtypes, flags)]
|
| 124 |
+
except KeyError:
|
| 125 |
+
class WinFunctionType(_CFuncPtr):
|
| 126 |
+
_argtypes_ = argtypes
|
| 127 |
+
_restype_ = restype
|
| 128 |
+
_flags_ = flags
|
| 129 |
+
_win_functype_cache[(restype, argtypes, flags)] = WinFunctionType
|
| 130 |
+
return WinFunctionType
|
| 131 |
+
if WINFUNCTYPE.__doc__:
|
| 132 |
+
WINFUNCTYPE.__doc__ = CFUNCTYPE.__doc__.replace("CFUNCTYPE", "WINFUNCTYPE")
|
| 133 |
+
|
| 134 |
+
elif _os.name == "posix":
|
| 135 |
+
from _ctypes import dlopen as _dlopen
|
| 136 |
+
|
| 137 |
+
from _ctypes import sizeof, byref, addressof, alignment, resize
|
| 138 |
+
from _ctypes import get_errno, set_errno
|
| 139 |
+
from _ctypes import _SimpleCData
|
| 140 |
+
|
| 141 |
+
def _check_size(typ, typecode=None):
|
| 142 |
+
# Check if sizeof(ctypes_type) against struct.calcsize. This
|
| 143 |
+
# should protect somewhat against a misconfigured libffi.
|
| 144 |
+
from struct import calcsize
|
| 145 |
+
if typecode is None:
|
| 146 |
+
# Most _type_ codes are the same as used in struct
|
| 147 |
+
typecode = typ._type_
|
| 148 |
+
actual, required = sizeof(typ), calcsize(typecode)
|
| 149 |
+
if actual != required:
|
| 150 |
+
raise SystemError("sizeof(%s) wrong: %d instead of %d" % \
|
| 151 |
+
(typ, actual, required))
|
| 152 |
+
|
| 153 |
+
class py_object(_SimpleCData):
|
| 154 |
+
_type_ = "O"
|
| 155 |
+
def __repr__(self):
|
| 156 |
+
try:
|
| 157 |
+
return super().__repr__()
|
| 158 |
+
except ValueError:
|
| 159 |
+
return "%s(<NULL>)" % type(self).__name__
|
| 160 |
+
_check_size(py_object, "P")
|
| 161 |
+
|
| 162 |
+
class c_short(_SimpleCData):
|
| 163 |
+
_type_ = "h"
|
| 164 |
+
_check_size(c_short)
|
| 165 |
+
|
| 166 |
+
class c_ushort(_SimpleCData):
|
| 167 |
+
_type_ = "H"
|
| 168 |
+
_check_size(c_ushort)
|
| 169 |
+
|
| 170 |
+
class c_long(_SimpleCData):
|
| 171 |
+
_type_ = "l"
|
| 172 |
+
_check_size(c_long)
|
| 173 |
+
|
| 174 |
+
class c_ulong(_SimpleCData):
|
| 175 |
+
_type_ = "L"
|
| 176 |
+
_check_size(c_ulong)
|
| 177 |
+
|
| 178 |
+
if _calcsize("i") == _calcsize("l"):
|
| 179 |
+
# if int and long have the same size, make c_int an alias for c_long
|
| 180 |
+
c_int = c_long
|
| 181 |
+
c_uint = c_ulong
|
| 182 |
+
else:
|
| 183 |
+
class c_int(_SimpleCData):
|
| 184 |
+
_type_ = "i"
|
| 185 |
+
_check_size(c_int)
|
| 186 |
+
|
| 187 |
+
class c_uint(_SimpleCData):
|
| 188 |
+
_type_ = "I"
|
| 189 |
+
_check_size(c_uint)
|
| 190 |
+
|
| 191 |
+
class c_float(_SimpleCData):
|
| 192 |
+
_type_ = "f"
|
| 193 |
+
_check_size(c_float)
|
| 194 |
+
|
| 195 |
+
class c_double(_SimpleCData):
|
| 196 |
+
_type_ = "d"
|
| 197 |
+
_check_size(c_double)
|
| 198 |
+
|
| 199 |
+
class c_longdouble(_SimpleCData):
|
| 200 |
+
_type_ = "g"
|
| 201 |
+
if sizeof(c_longdouble) == sizeof(c_double):
|
| 202 |
+
c_longdouble = c_double
|
| 203 |
+
|
| 204 |
+
if _calcsize("l") == _calcsize("q"):
|
| 205 |
+
# if long and long long have the same size, make c_longlong an alias for c_long
|
| 206 |
+
c_longlong = c_long
|
| 207 |
+
c_ulonglong = c_ulong
|
| 208 |
+
else:
|
| 209 |
+
class c_longlong(_SimpleCData):
|
| 210 |
+
_type_ = "q"
|
| 211 |
+
_check_size(c_longlong)
|
| 212 |
+
|
| 213 |
+
class c_ulonglong(_SimpleCData):
|
| 214 |
+
_type_ = "Q"
|
| 215 |
+
## def from_param(cls, val):
|
| 216 |
+
## return ('d', float(val), val)
|
| 217 |
+
## from_param = classmethod(from_param)
|
| 218 |
+
_check_size(c_ulonglong)
|
| 219 |
+
|
| 220 |
+
class c_ubyte(_SimpleCData):
|
| 221 |
+
_type_ = "B"
|
| 222 |
+
c_ubyte.__ctype_le__ = c_ubyte.__ctype_be__ = c_ubyte
|
| 223 |
+
# backward compatibility:
|
| 224 |
+
##c_uchar = c_ubyte
|
| 225 |
+
_check_size(c_ubyte)
|
| 226 |
+
|
| 227 |
+
class c_byte(_SimpleCData):
|
| 228 |
+
_type_ = "b"
|
| 229 |
+
c_byte.__ctype_le__ = c_byte.__ctype_be__ = c_byte
|
| 230 |
+
_check_size(c_byte)
|
| 231 |
+
|
| 232 |
+
class c_char(_SimpleCData):
|
| 233 |
+
_type_ = "c"
|
| 234 |
+
c_char.__ctype_le__ = c_char.__ctype_be__ = c_char
|
| 235 |
+
_check_size(c_char)
|
| 236 |
+
|
| 237 |
+
class c_char_p(_SimpleCData):
|
| 238 |
+
_type_ = "z"
|
| 239 |
+
def __repr__(self):
|
| 240 |
+
return "%s(%s)" % (self.__class__.__name__, c_void_p.from_buffer(self).value)
|
| 241 |
+
_check_size(c_char_p, "P")
|
| 242 |
+
|
| 243 |
+
class c_void_p(_SimpleCData):
|
| 244 |
+
_type_ = "P"
|
| 245 |
+
c_voidp = c_void_p # backwards compatibility (to a bug)
|
| 246 |
+
_check_size(c_void_p)
|
| 247 |
+
|
| 248 |
+
class c_bool(_SimpleCData):
|
| 249 |
+
_type_ = "?"
|
| 250 |
+
|
| 251 |
+
from _ctypes import POINTER, pointer, _pointer_type_cache
|
| 252 |
+
|
| 253 |
+
class c_wchar_p(_SimpleCData):
|
| 254 |
+
_type_ = "Z"
|
| 255 |
+
def __repr__(self):
|
| 256 |
+
return "%s(%s)" % (self.__class__.__name__, c_void_p.from_buffer(self).value)
|
| 257 |
+
|
| 258 |
+
class c_wchar(_SimpleCData):
|
| 259 |
+
_type_ = "u"
|
| 260 |
+
|
| 261 |
+
def _reset_cache():
|
| 262 |
+
_pointer_type_cache.clear()
|
| 263 |
+
_c_functype_cache.clear()
|
| 264 |
+
if _os.name == "nt":
|
| 265 |
+
_win_functype_cache.clear()
|
| 266 |
+
# _SimpleCData.c_wchar_p_from_param
|
| 267 |
+
POINTER(c_wchar).from_param = c_wchar_p.from_param
|
| 268 |
+
# _SimpleCData.c_char_p_from_param
|
| 269 |
+
POINTER(c_char).from_param = c_char_p.from_param
|
| 270 |
+
_pointer_type_cache[None] = c_void_p
|
| 271 |
+
|
| 272 |
+
def create_unicode_buffer(init, size=None):
|
| 273 |
+
"""create_unicode_buffer(aString) -> character array
|
| 274 |
+
create_unicode_buffer(anInteger) -> character array
|
| 275 |
+
create_unicode_buffer(aString, anInteger) -> character array
|
| 276 |
+
"""
|
| 277 |
+
if isinstance(init, str):
|
| 278 |
+
if size is None:
|
| 279 |
+
if sizeof(c_wchar) == 2:
|
| 280 |
+
# UTF-16 requires a surrogate pair (2 wchar_t) for non-BMP
|
| 281 |
+
# characters (outside [U+0000; U+FFFF] range). +1 for trailing
|
| 282 |
+
# NUL character.
|
| 283 |
+
size = sum(2 if ord(c) > 0xFFFF else 1 for c in init) + 1
|
| 284 |
+
else:
|
| 285 |
+
# 32-bit wchar_t (1 wchar_t per Unicode character). +1 for
|
| 286 |
+
# trailing NUL character.
|
| 287 |
+
size = len(init) + 1
|
| 288 |
+
_sys.audit("ctypes.create_unicode_buffer", init, size)
|
| 289 |
+
buftype = c_wchar * size
|
| 290 |
+
buf = buftype()
|
| 291 |
+
buf.value = init
|
| 292 |
+
return buf
|
| 293 |
+
elif isinstance(init, int):
|
| 294 |
+
_sys.audit("ctypes.create_unicode_buffer", None, init)
|
| 295 |
+
buftype = c_wchar * init
|
| 296 |
+
buf = buftype()
|
| 297 |
+
return buf
|
| 298 |
+
raise TypeError(init)
|
| 299 |
+
|
| 300 |
+
|
| 301 |
+
# XXX Deprecated
|
| 302 |
+
def SetPointerType(pointer, cls):
|
| 303 |
+
if _pointer_type_cache.get(cls, None) is not None:
|
| 304 |
+
raise RuntimeError("This type already exists in the cache")
|
| 305 |
+
if id(pointer) not in _pointer_type_cache:
|
| 306 |
+
raise RuntimeError("What's this???")
|
| 307 |
+
pointer.set_type(cls)
|
| 308 |
+
_pointer_type_cache[cls] = pointer
|
| 309 |
+
del _pointer_type_cache[id(pointer)]
|
| 310 |
+
|
| 311 |
+
# XXX Deprecated
|
| 312 |
+
def ARRAY(typ, len):
|
| 313 |
+
return typ * len
|
| 314 |
+
|
| 315 |
+
################################################################
|
| 316 |
+
|
| 317 |
+
|
| 318 |
+
class CDLL(object):
|
| 319 |
+
"""An instance of this class represents a loaded dll/shared
|
| 320 |
+
library, exporting functions using the standard C calling
|
| 321 |
+
convention (named 'cdecl' on Windows).
|
| 322 |
+
|
| 323 |
+
The exported functions can be accessed as attributes, or by
|
| 324 |
+
indexing with the function name. Examples:
|
| 325 |
+
|
| 326 |
+
<obj>.qsort -> callable object
|
| 327 |
+
<obj>['qsort'] -> callable object
|
| 328 |
+
|
| 329 |
+
Calling the functions releases the Python GIL during the call and
|
| 330 |
+
reacquires it afterwards.
|
| 331 |
+
"""
|
| 332 |
+
_func_flags_ = _FUNCFLAG_CDECL
|
| 333 |
+
_func_restype_ = c_int
|
| 334 |
+
# default values for repr
|
| 335 |
+
_name = '<uninitialized>'
|
| 336 |
+
_handle = 0
|
| 337 |
+
_FuncPtr = None
|
| 338 |
+
|
| 339 |
+
def __init__(self, name, mode=DEFAULT_MODE, handle=None,
|
| 340 |
+
use_errno=False,
|
| 341 |
+
use_last_error=False,
|
| 342 |
+
winmode=None):
|
| 343 |
+
self._name = name
|
| 344 |
+
flags = self._func_flags_
|
| 345 |
+
if use_errno:
|
| 346 |
+
flags |= _FUNCFLAG_USE_ERRNO
|
| 347 |
+
if use_last_error:
|
| 348 |
+
flags |= _FUNCFLAG_USE_LASTERROR
|
| 349 |
+
if _sys.platform.startswith("aix"):
|
| 350 |
+
"""When the name contains ".a(" and ends with ")",
|
| 351 |
+
e.g., "libFOO.a(libFOO.so)" - this is taken to be an
|
| 352 |
+
archive(member) syntax for dlopen(), and the mode is adjusted.
|
| 353 |
+
Otherwise, name is presented to dlopen() as a file argument.
|
| 354 |
+
"""
|
| 355 |
+
if name and name.endswith(")") and ".a(" in name:
|
| 356 |
+
mode |= ( _os.RTLD_MEMBER | _os.RTLD_NOW )
|
| 357 |
+
if _os.name == "nt":
|
| 358 |
+
if winmode is not None:
|
| 359 |
+
mode = winmode
|
| 360 |
+
else:
|
| 361 |
+
import nt
|
| 362 |
+
mode = nt._LOAD_LIBRARY_SEARCH_DEFAULT_DIRS
|
| 363 |
+
if '/' in name or '\\' in name:
|
| 364 |
+
self._name = nt._getfullpathname(self._name)
|
| 365 |
+
mode |= nt._LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR
|
| 366 |
+
|
| 367 |
+
class _FuncPtr(_CFuncPtr):
|
| 368 |
+
_flags_ = flags
|
| 369 |
+
_restype_ = self._func_restype_
|
| 370 |
+
self._FuncPtr = _FuncPtr
|
| 371 |
+
|
| 372 |
+
if handle is None:
|
| 373 |
+
self._handle = _dlopen(self._name, mode)
|
| 374 |
+
else:
|
| 375 |
+
self._handle = handle
|
| 376 |
+
|
| 377 |
+
def __repr__(self):
|
| 378 |
+
return "<%s '%s', handle %x at %#x>" % \
|
| 379 |
+
(self.__class__.__name__, self._name,
|
| 380 |
+
(self._handle & (_sys.maxsize*2 + 1)),
|
| 381 |
+
id(self) & (_sys.maxsize*2 + 1))
|
| 382 |
+
|
| 383 |
+
def __getattr__(self, name):
|
| 384 |
+
if name.startswith('__') and name.endswith('__'):
|
| 385 |
+
raise AttributeError(name)
|
| 386 |
+
func = self.__getitem__(name)
|
| 387 |
+
setattr(self, name, func)
|
| 388 |
+
return func
|
| 389 |
+
|
| 390 |
+
def __getitem__(self, name_or_ordinal):
|
| 391 |
+
func = self._FuncPtr((name_or_ordinal, self))
|
| 392 |
+
if not isinstance(name_or_ordinal, int):
|
| 393 |
+
func.__name__ = name_or_ordinal
|
| 394 |
+
return func
|
| 395 |
+
|
| 396 |
+
class PyDLL(CDLL):
|
| 397 |
+
"""This class represents the Python library itself. It allows
|
| 398 |
+
accessing Python API functions. The GIL is not released, and
|
| 399 |
+
Python exceptions are handled correctly.
|
| 400 |
+
"""
|
| 401 |
+
_func_flags_ = _FUNCFLAG_CDECL | _FUNCFLAG_PYTHONAPI
|
| 402 |
+
|
| 403 |
+
if _os.name == "nt":
|
| 404 |
+
|
| 405 |
+
class WinDLL(CDLL):
|
| 406 |
+
"""This class represents a dll exporting functions using the
|
| 407 |
+
Windows stdcall calling convention.
|
| 408 |
+
"""
|
| 409 |
+
_func_flags_ = _FUNCFLAG_STDCALL
|
| 410 |
+
|
| 411 |
+
# XXX Hm, what about HRESULT as normal parameter?
|
| 412 |
+
# Mustn't it derive from c_long then?
|
| 413 |
+
from _ctypes import _check_HRESULT, _SimpleCData
|
| 414 |
+
class HRESULT(_SimpleCData):
|
| 415 |
+
_type_ = "l"
|
| 416 |
+
# _check_retval_ is called with the function's result when it
|
| 417 |
+
# is used as restype. It checks for the FAILED bit, and
|
| 418 |
+
# raises an OSError if it is set.
|
| 419 |
+
#
|
| 420 |
+
# The _check_retval_ method is implemented in C, so that the
|
| 421 |
+
# method definition itself is not included in the traceback
|
| 422 |
+
# when it raises an error - that is what we want (and Python
|
| 423 |
+
# doesn't have a way to raise an exception in the caller's
|
| 424 |
+
# frame).
|
| 425 |
+
_check_retval_ = _check_HRESULT
|
| 426 |
+
|
| 427 |
+
class OleDLL(CDLL):
|
| 428 |
+
"""This class represents a dll exporting functions using the
|
| 429 |
+
Windows stdcall calling convention, and returning HRESULT.
|
| 430 |
+
HRESULT error values are automatically raised as OSError
|
| 431 |
+
exceptions.
|
| 432 |
+
"""
|
| 433 |
+
_func_flags_ = _FUNCFLAG_STDCALL
|
| 434 |
+
_func_restype_ = HRESULT
|
| 435 |
+
|
| 436 |
+
class LibraryLoader(object):
|
| 437 |
+
def __init__(self, dlltype):
|
| 438 |
+
self._dlltype = dlltype
|
| 439 |
+
|
| 440 |
+
def __getattr__(self, name):
|
| 441 |
+
if name[0] == '_':
|
| 442 |
+
raise AttributeError(name)
|
| 443 |
+
dll = self._dlltype(name)
|
| 444 |
+
setattr(self, name, dll)
|
| 445 |
+
return dll
|
| 446 |
+
|
| 447 |
+
def __getitem__(self, name):
|
| 448 |
+
return getattr(self, name)
|
| 449 |
+
|
| 450 |
+
def LoadLibrary(self, name):
|
| 451 |
+
return self._dlltype(name)
|
| 452 |
+
|
| 453 |
+
cdll = LibraryLoader(CDLL)
|
| 454 |
+
pydll = LibraryLoader(PyDLL)
|
| 455 |
+
|
| 456 |
+
if _os.name == "nt":
|
| 457 |
+
pythonapi = PyDLL("python dll", None, _sys.dllhandle)
|
| 458 |
+
elif _sys.platform == "cygwin":
|
| 459 |
+
pythonapi = PyDLL("libpython%d.%d.dll" % _sys.version_info[:2])
|
| 460 |
+
else:
|
| 461 |
+
pythonapi = PyDLL(None)
|
| 462 |
+
|
| 463 |
+
|
| 464 |
+
if _os.name == "nt":
|
| 465 |
+
windll = LibraryLoader(WinDLL)
|
| 466 |
+
oledll = LibraryLoader(OleDLL)
|
| 467 |
+
|
| 468 |
+
GetLastError = windll.kernel32.GetLastError
|
| 469 |
+
from _ctypes import get_last_error, set_last_error
|
| 470 |
+
|
| 471 |
+
def WinError(code=None, descr=None):
|
| 472 |
+
if code is None:
|
| 473 |
+
code = GetLastError()
|
| 474 |
+
if descr is None:
|
| 475 |
+
descr = FormatError(code).strip()
|
| 476 |
+
return OSError(None, descr, None, code)
|
| 477 |
+
|
| 478 |
+
if sizeof(c_uint) == sizeof(c_void_p):
|
| 479 |
+
c_size_t = c_uint
|
| 480 |
+
c_ssize_t = c_int
|
| 481 |
+
elif sizeof(c_ulong) == sizeof(c_void_p):
|
| 482 |
+
c_size_t = c_ulong
|
| 483 |
+
c_ssize_t = c_long
|
| 484 |
+
elif sizeof(c_ulonglong) == sizeof(c_void_p):
|
| 485 |
+
c_size_t = c_ulonglong
|
| 486 |
+
c_ssize_t = c_longlong
|
| 487 |
+
|
| 488 |
+
# functions
|
| 489 |
+
|
| 490 |
+
from _ctypes import _memmove_addr, _memset_addr, _string_at_addr, _cast_addr
|
| 491 |
+
|
| 492 |
+
## void *memmove(void *, const void *, size_t);
|
| 493 |
+
memmove = CFUNCTYPE(c_void_p, c_void_p, c_void_p, c_size_t)(_memmove_addr)
|
| 494 |
+
|
| 495 |
+
## void *memset(void *, int, size_t)
|
| 496 |
+
memset = CFUNCTYPE(c_void_p, c_void_p, c_int, c_size_t)(_memset_addr)
|
| 497 |
+
|
| 498 |
+
def PYFUNCTYPE(restype, *argtypes):
|
| 499 |
+
class CFunctionType(_CFuncPtr):
|
| 500 |
+
_argtypes_ = argtypes
|
| 501 |
+
_restype_ = restype
|
| 502 |
+
_flags_ = _FUNCFLAG_CDECL | _FUNCFLAG_PYTHONAPI
|
| 503 |
+
return CFunctionType
|
| 504 |
+
|
| 505 |
+
_cast = PYFUNCTYPE(py_object, c_void_p, py_object, py_object)(_cast_addr)
|
| 506 |
+
def cast(obj, typ):
|
| 507 |
+
return _cast(obj, obj, typ)
|
| 508 |
+
|
| 509 |
+
_string_at = PYFUNCTYPE(py_object, c_void_p, c_int)(_string_at_addr)
|
| 510 |
+
def string_at(ptr, size=-1):
|
| 511 |
+
"""string_at(addr[, size]) -> string
|
| 512 |
+
|
| 513 |
+
Return the string at addr."""
|
| 514 |
+
return _string_at(ptr, size)
|
| 515 |
+
|
| 516 |
+
try:
|
| 517 |
+
from _ctypes import _wstring_at_addr
|
| 518 |
+
except ImportError:
|
| 519 |
+
pass
|
| 520 |
+
else:
|
| 521 |
+
_wstring_at = PYFUNCTYPE(py_object, c_void_p, c_int)(_wstring_at_addr)
|
| 522 |
+
def wstring_at(ptr, size=-1):
|
| 523 |
+
"""wstring_at(addr[, size]) -> string
|
| 524 |
+
|
| 525 |
+
Return the string at addr."""
|
| 526 |
+
return _wstring_at(ptr, size)
|
| 527 |
+
|
| 528 |
+
|
| 529 |
+
if _os.name == "nt": # COM stuff
|
| 530 |
+
def DllGetClassObject(rclsid, riid, ppv):
|
| 531 |
+
try:
|
| 532 |
+
ccom = __import__("comtypes.server.inprocserver", globals(), locals(), ['*'])
|
| 533 |
+
except ImportError:
|
| 534 |
+
return -2147221231 # CLASS_E_CLASSNOTAVAILABLE
|
| 535 |
+
else:
|
| 536 |
+
return ccom.DllGetClassObject(rclsid, riid, ppv)
|
| 537 |
+
|
| 538 |
+
def DllCanUnloadNow():
|
| 539 |
+
try:
|
| 540 |
+
ccom = __import__("comtypes.server.inprocserver", globals(), locals(), ['*'])
|
| 541 |
+
except ImportError:
|
| 542 |
+
return 0 # S_OK
|
| 543 |
+
return ccom.DllCanUnloadNow()
|
| 544 |
+
|
| 545 |
+
from ctypes._endian import BigEndianStructure, LittleEndianStructure
|
| 546 |
+
|
| 547 |
+
# Fill in specifically-sized types
|
| 548 |
+
c_int8 = c_byte
|
| 549 |
+
c_uint8 = c_ubyte
|
| 550 |
+
for kind in [c_short, c_int, c_long, c_longlong]:
|
| 551 |
+
if sizeof(kind) == 2: c_int16 = kind
|
| 552 |
+
elif sizeof(kind) == 4: c_int32 = kind
|
| 553 |
+
elif sizeof(kind) == 8: c_int64 = kind
|
| 554 |
+
for kind in [c_ushort, c_uint, c_ulong, c_ulonglong]:
|
| 555 |
+
if sizeof(kind) == 2: c_uint16 = kind
|
| 556 |
+
elif sizeof(kind) == 4: c_uint32 = kind
|
| 557 |
+
elif sizeof(kind) == 8: c_uint64 = kind
|
| 558 |
+
del(kind)
|
| 559 |
+
|
| 560 |
+
_reset_cache()
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/ctypes/_aix.py
ADDED
|
@@ -0,0 +1,331 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Lib/ctypes.util.find_library() support for AIX
|
| 3 |
+
Similar approach as done for Darwin support by using separate files
|
| 4 |
+
but unlike Darwin - no extension such as ctypes.macholib.*
|
| 5 |
+
|
| 6 |
+
dlopen() is an interface to AIX initAndLoad() - primary documentation at:
|
| 7 |
+
https://www.ibm.com/support/knowledgecenter/en/ssw_aix_61/com.ibm.aix.basetrf1/dlopen.htm
|
| 8 |
+
https://www.ibm.com/support/knowledgecenter/en/ssw_aix_61/com.ibm.aix.basetrf1/load.htm
|
| 9 |
+
|
| 10 |
+
AIX supports two styles for dlopen(): svr4 (System V Release 4) which is common on posix
|
| 11 |
+
platforms, but also a BSD style - aka SVR3.
|
| 12 |
+
|
| 13 |
+
From AIX 5.3 Difference Addendum (December 2004)
|
| 14 |
+
2.9 SVR4 linking affinity
|
| 15 |
+
Nowadays, there are two major object file formats used by the operating systems:
|
| 16 |
+
XCOFF: The COFF enhanced by IBM and others. The original COFF (Common
|
| 17 |
+
Object File Format) was the base of SVR3 and BSD 4.2 systems.
|
| 18 |
+
ELF: Executable and Linking Format that was developed by AT&T and is a
|
| 19 |
+
base for SVR4 UNIX.
|
| 20 |
+
|
| 21 |
+
While the shared library content is identical on AIX - one is located as a filepath name
|
| 22 |
+
(svr4 style) and the other is located as a member of an archive (and the archive
|
| 23 |
+
is located as a filepath name).
|
| 24 |
+
|
| 25 |
+
The key difference arises when supporting multiple abi formats (i.e., 32 and 64 bit).
|
| 26 |
+
For svr4 either only one ABI is supported, or there are two directories, or there
|
| 27 |
+
are different file names. The most common solution for multiple ABI is multiple
|
| 28 |
+
directories.
|
| 29 |
+
|
| 30 |
+
For the XCOFF (aka AIX) style - one directory (one archive file) is sufficient
|
| 31 |
+
as multiple shared libraries can be in the archive - even sharing the same name.
|
| 32 |
+
In documentation the archive is also referred to as the "base" and the shared
|
| 33 |
+
library object is referred to as the "member".
|
| 34 |
+
|
| 35 |
+
For dlopen() on AIX (read initAndLoad()) the calls are similar.
|
| 36 |
+
Default activity occurs when no path information is provided. When path
|
| 37 |
+
information is provided dlopen() does not search any other directories.
|
| 38 |
+
|
| 39 |
+
For SVR4 - the shared library name is the name of the file expected: libFOO.so
|
| 40 |
+
For AIX - the shared library is expressed as base(member). The search is for the
|
| 41 |
+
base (e.g., libFOO.a) and once the base is found the shared library - identified by
|
| 42 |
+
member (e.g., libFOO.so, or shr.o) is located and loaded.
|
| 43 |
+
|
| 44 |
+
The mode bit RTLD_MEMBER tells initAndLoad() that it needs to use the AIX (SVR3)
|
| 45 |
+
naming style.
|
| 46 |
+
"""
|
| 47 |
+
__author__ = "Michael Felt <aixtools@felt.demon.nl>"
|
| 48 |
+
|
| 49 |
+
import re
|
| 50 |
+
from os import environ, path
|
| 51 |
+
from sys import executable
|
| 52 |
+
from ctypes import c_void_p, sizeof
|
| 53 |
+
from subprocess import Popen, PIPE, DEVNULL
|
| 54 |
+
|
| 55 |
+
# Executable bit size - 32 or 64
|
| 56 |
+
# Used to filter the search in an archive by size, e.g., -X64
|
| 57 |
+
AIX_ABI = sizeof(c_void_p) * 8
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
from sys import maxsize
|
| 61 |
+
def _last_version(libnames, sep):
|
| 62 |
+
def _num_version(libname):
|
| 63 |
+
# "libxyz.so.MAJOR.MINOR" => [MAJOR, MINOR]
|
| 64 |
+
parts = libname.split(sep)
|
| 65 |
+
nums = []
|
| 66 |
+
try:
|
| 67 |
+
while parts:
|
| 68 |
+
nums.insert(0, int(parts.pop()))
|
| 69 |
+
except ValueError:
|
| 70 |
+
pass
|
| 71 |
+
return nums or [maxsize]
|
| 72 |
+
return max(reversed(libnames), key=_num_version)
|
| 73 |
+
|
| 74 |
+
def get_ld_header(p):
|
| 75 |
+
# "nested-function, but placed at module level
|
| 76 |
+
ld_header = None
|
| 77 |
+
for line in p.stdout:
|
| 78 |
+
if line.startswith(('/', './', '../')):
|
| 79 |
+
ld_header = line
|
| 80 |
+
elif "INDEX" in line:
|
| 81 |
+
return ld_header.rstrip('\n')
|
| 82 |
+
return None
|
| 83 |
+
|
| 84 |
+
def get_ld_header_info(p):
|
| 85 |
+
# "nested-function, but placed at module level
|
| 86 |
+
# as an ld_header was found, return known paths, archives and members
|
| 87 |
+
# these lines start with a digit
|
| 88 |
+
info = []
|
| 89 |
+
for line in p.stdout:
|
| 90 |
+
if re.match("[0-9]", line):
|
| 91 |
+
info.append(line)
|
| 92 |
+
else:
|
| 93 |
+
# blank line (separator), consume line and end for loop
|
| 94 |
+
break
|
| 95 |
+
return info
|
| 96 |
+
|
| 97 |
+
def get_ld_headers(file):
|
| 98 |
+
"""
|
| 99 |
+
Parse the header of the loader section of executable and archives
|
| 100 |
+
This function calls /usr/bin/dump -H as a subprocess
|
| 101 |
+
and returns a list of (ld_header, ld_header_info) tuples.
|
| 102 |
+
"""
|
| 103 |
+
# get_ld_headers parsing:
|
| 104 |
+
# 1. Find a line that starts with /, ./, or ../ - set as ld_header
|
| 105 |
+
# 2. If "INDEX" in occurs in a following line - return ld_header
|
| 106 |
+
# 3. get info (lines starting with [0-9])
|
| 107 |
+
ldr_headers = []
|
| 108 |
+
p = Popen(["/usr/bin/dump", f"-X{AIX_ABI}", "-H", file],
|
| 109 |
+
universal_newlines=True, stdout=PIPE, stderr=DEVNULL)
|
| 110 |
+
# be sure to read to the end-of-file - getting all entries
|
| 111 |
+
while True:
|
| 112 |
+
ld_header = get_ld_header(p)
|
| 113 |
+
if ld_header:
|
| 114 |
+
ldr_headers.append((ld_header, get_ld_header_info(p)))
|
| 115 |
+
else:
|
| 116 |
+
break
|
| 117 |
+
p.stdout.close()
|
| 118 |
+
p.wait()
|
| 119 |
+
return ldr_headers
|
| 120 |
+
|
| 121 |
+
def get_shared(ld_headers):
|
| 122 |
+
"""
|
| 123 |
+
extract the shareable objects from ld_headers
|
| 124 |
+
character "[" is used to strip off the path information.
|
| 125 |
+
Note: the "[" and "]" characters that are part of dump -H output
|
| 126 |
+
are not removed here.
|
| 127 |
+
"""
|
| 128 |
+
shared = []
|
| 129 |
+
for (line, _) in ld_headers:
|
| 130 |
+
# potential member lines contain "["
|
| 131 |
+
# otherwise, no processing needed
|
| 132 |
+
if "[" in line:
|
| 133 |
+
# Strip off trailing colon (:)
|
| 134 |
+
shared.append(line[line.index("["):-1])
|
| 135 |
+
return shared
|
| 136 |
+
|
| 137 |
+
def get_one_match(expr, lines):
|
| 138 |
+
"""
|
| 139 |
+
Must be only one match, otherwise result is None.
|
| 140 |
+
When there is a match, strip leading "[" and trailing "]"
|
| 141 |
+
"""
|
| 142 |
+
# member names in the ld_headers output are between square brackets
|
| 143 |
+
expr = rf'\[({expr})\]'
|
| 144 |
+
matches = list(filter(None, (re.search(expr, line) for line in lines)))
|
| 145 |
+
if len(matches) == 1:
|
| 146 |
+
return matches[0].group(1)
|
| 147 |
+
else:
|
| 148 |
+
return None
|
| 149 |
+
|
| 150 |
+
# additional processing to deal with AIX legacy names for 64-bit members
|
| 151 |
+
def get_legacy(members):
|
| 152 |
+
"""
|
| 153 |
+
This routine provides historical aka legacy naming schemes started
|
| 154 |
+
in AIX4 shared library support for library members names.
|
| 155 |
+
e.g., in /usr/lib/libc.a the member name shr.o for 32-bit binary and
|
| 156 |
+
shr_64.o for 64-bit binary.
|
| 157 |
+
"""
|
| 158 |
+
if AIX_ABI == 64:
|
| 159 |
+
# AIX 64-bit member is one of shr64.o, shr_64.o, or shr4_64.o
|
| 160 |
+
expr = r'shr4?_?64\.o'
|
| 161 |
+
member = get_one_match(expr, members)
|
| 162 |
+
if member:
|
| 163 |
+
return member
|
| 164 |
+
else:
|
| 165 |
+
# 32-bit legacy names - both shr.o and shr4.o exist.
|
| 166 |
+
# shr.o is the preffered name so we look for shr.o first
|
| 167 |
+
# i.e., shr4.o is returned only when shr.o does not exist
|
| 168 |
+
for name in ['shr.o', 'shr4.o']:
|
| 169 |
+
member = get_one_match(re.escape(name), members)
|
| 170 |
+
if member:
|
| 171 |
+
return member
|
| 172 |
+
return None
|
| 173 |
+
|
| 174 |
+
def get_version(name, members):
|
| 175 |
+
"""
|
| 176 |
+
Sort list of members and return highest numbered version - if it exists.
|
| 177 |
+
This function is called when an unversioned libFOO.a(libFOO.so) has
|
| 178 |
+
not been found.
|
| 179 |
+
|
| 180 |
+
Versioning for the member name is expected to follow
|
| 181 |
+
GNU LIBTOOL conventions: the highest version (x, then X.y, then X.Y.z)
|
| 182 |
+
* find [libFoo.so.X]
|
| 183 |
+
* find [libFoo.so.X.Y]
|
| 184 |
+
* find [libFoo.so.X.Y.Z]
|
| 185 |
+
|
| 186 |
+
Before the GNU convention became the standard scheme regardless of
|
| 187 |
+
binary size AIX packagers used GNU convention "as-is" for 32-bit
|
| 188 |
+
archive members but used an "distinguishing" name for 64-bit members.
|
| 189 |
+
This scheme inserted either 64 or _64 between libFOO and .so
|
| 190 |
+
- generally libFOO_64.so, but occasionally libFOO64.so
|
| 191 |
+
"""
|
| 192 |
+
# the expression ending for versions must start as
|
| 193 |
+
# '.so.[0-9]', i.e., *.so.[at least one digit]
|
| 194 |
+
# while multiple, more specific expressions could be specified
|
| 195 |
+
# to search for .so.X, .so.X.Y and .so.X.Y.Z
|
| 196 |
+
# after the first required 'dot' digit
|
| 197 |
+
# any combination of additional 'dot' digits pairs are accepted
|
| 198 |
+
# anything more than libFOO.so.digits.digits.digits
|
| 199 |
+
# should be seen as a member name outside normal expectations
|
| 200 |
+
exprs = [rf'lib{name}\.so\.[0-9]+[0-9.]*',
|
| 201 |
+
rf'lib{name}_?64\.so\.[0-9]+[0-9.]*']
|
| 202 |
+
for expr in exprs:
|
| 203 |
+
versions = []
|
| 204 |
+
for line in members:
|
| 205 |
+
m = re.search(expr, line)
|
| 206 |
+
if m:
|
| 207 |
+
versions.append(m.group(0))
|
| 208 |
+
if versions:
|
| 209 |
+
return _last_version(versions, '.')
|
| 210 |
+
return None
|
| 211 |
+
|
| 212 |
+
def get_member(name, members):
|
| 213 |
+
"""
|
| 214 |
+
Return an archive member matching the request in name.
|
| 215 |
+
Name is the library name without any prefix like lib, suffix like .so,
|
| 216 |
+
or version number.
|
| 217 |
+
Given a list of members find and return the most appropriate result
|
| 218 |
+
Priority is given to generic libXXX.so, then a versioned libXXX.so.a.b.c
|
| 219 |
+
and finally, legacy AIX naming scheme.
|
| 220 |
+
"""
|
| 221 |
+
# look first for a generic match - prepend lib and append .so
|
| 222 |
+
expr = rf'lib{name}\.so'
|
| 223 |
+
member = get_one_match(expr, members)
|
| 224 |
+
if member:
|
| 225 |
+
return member
|
| 226 |
+
elif AIX_ABI == 64:
|
| 227 |
+
expr = rf'lib{name}64\.so'
|
| 228 |
+
member = get_one_match(expr, members)
|
| 229 |
+
if member:
|
| 230 |
+
return member
|
| 231 |
+
# since an exact match with .so as suffix was not found
|
| 232 |
+
# look for a versioned name
|
| 233 |
+
# If a versioned name is not found, look for AIX legacy member name
|
| 234 |
+
member = get_version(name, members)
|
| 235 |
+
if member:
|
| 236 |
+
return member
|
| 237 |
+
else:
|
| 238 |
+
return get_legacy(members)
|
| 239 |
+
|
| 240 |
+
def get_libpaths():
|
| 241 |
+
"""
|
| 242 |
+
On AIX, the buildtime searchpath is stored in the executable.
|
| 243 |
+
as "loader header information".
|
| 244 |
+
The command /usr/bin/dump -H extracts this info.
|
| 245 |
+
Prefix searched libraries with LD_LIBRARY_PATH (preferred),
|
| 246 |
+
or LIBPATH if defined. These paths are appended to the paths
|
| 247 |
+
to libraries the python executable is linked with.
|
| 248 |
+
This mimics AIX dlopen() behavior.
|
| 249 |
+
"""
|
| 250 |
+
libpaths = environ.get("LD_LIBRARY_PATH")
|
| 251 |
+
if libpaths is None:
|
| 252 |
+
libpaths = environ.get("LIBPATH")
|
| 253 |
+
if libpaths is None:
|
| 254 |
+
libpaths = []
|
| 255 |
+
else:
|
| 256 |
+
libpaths = libpaths.split(":")
|
| 257 |
+
objects = get_ld_headers(executable)
|
| 258 |
+
for (_, lines) in objects:
|
| 259 |
+
for line in lines:
|
| 260 |
+
# the second (optional) argument is PATH if it includes a /
|
| 261 |
+
path = line.split()[1]
|
| 262 |
+
if "/" in path:
|
| 263 |
+
libpaths.extend(path.split(":"))
|
| 264 |
+
return libpaths
|
| 265 |
+
|
| 266 |
+
def find_shared(paths, name):
|
| 267 |
+
"""
|
| 268 |
+
paths is a list of directories to search for an archive.
|
| 269 |
+
name is the abbreviated name given to find_library().
|
| 270 |
+
Process: search "paths" for archive, and if an archive is found
|
| 271 |
+
return the result of get_member().
|
| 272 |
+
If an archive is not found then return None
|
| 273 |
+
"""
|
| 274 |
+
for dir in paths:
|
| 275 |
+
# /lib is a symbolic link to /usr/lib, skip it
|
| 276 |
+
if dir == "/lib":
|
| 277 |
+
continue
|
| 278 |
+
# "lib" is prefixed to emulate compiler name resolution,
|
| 279 |
+
# e.g., -lc to libc
|
| 280 |
+
base = f'lib{name}.a'
|
| 281 |
+
archive = path.join(dir, base)
|
| 282 |
+
if path.exists(archive):
|
| 283 |
+
members = get_shared(get_ld_headers(archive))
|
| 284 |
+
member = get_member(re.escape(name), members)
|
| 285 |
+
if member != None:
|
| 286 |
+
return (base, member)
|
| 287 |
+
else:
|
| 288 |
+
return (None, None)
|
| 289 |
+
return (None, None)
|
| 290 |
+
|
| 291 |
+
def find_library(name):
|
| 292 |
+
"""AIX implementation of ctypes.util.find_library()
|
| 293 |
+
Find an archive member that will dlopen(). If not available,
|
| 294 |
+
also search for a file (or link) with a .so suffix.
|
| 295 |
+
|
| 296 |
+
AIX supports two types of schemes that can be used with dlopen().
|
| 297 |
+
The so-called SystemV Release4 (svr4) format is commonly suffixed
|
| 298 |
+
with .so while the (default) AIX scheme has the library (archive)
|
| 299 |
+
ending with the suffix .a
|
| 300 |
+
As an archive has multiple members (e.g., 32-bit and 64-bit) in one file
|
| 301 |
+
the argument passed to dlopen must include both the library and
|
| 302 |
+
the member names in a single string.
|
| 303 |
+
|
| 304 |
+
find_library() looks first for an archive (.a) with a suitable member.
|
| 305 |
+
If no archive+member pair is found, look for a .so file.
|
| 306 |
+
"""
|
| 307 |
+
|
| 308 |
+
libpaths = get_libpaths()
|
| 309 |
+
(base, member) = find_shared(libpaths, name)
|
| 310 |
+
if base != None:
|
| 311 |
+
return f"{base}({member})"
|
| 312 |
+
|
| 313 |
+
# To get here, a member in an archive has not been found
|
| 314 |
+
# In other words, either:
|
| 315 |
+
# a) a .a file was not found
|
| 316 |
+
# b) a .a file did not have a suitable member
|
| 317 |
+
# So, look for a .so file
|
| 318 |
+
# Check libpaths for .so file
|
| 319 |
+
# Note, the installation must prepare a link from a .so
|
| 320 |
+
# to a versioned file
|
| 321 |
+
# This is common practice by GNU libtool on other platforms
|
| 322 |
+
soname = f"lib{name}.so"
|
| 323 |
+
for dir in libpaths:
|
| 324 |
+
# /lib is a symbolic link to /usr/lib, skip it
|
| 325 |
+
if dir == "/lib":
|
| 326 |
+
continue
|
| 327 |
+
shlib = path.join(dir, soname)
|
| 328 |
+
if path.exists(shlib):
|
| 329 |
+
return soname
|
| 330 |
+
# if we are here, we have not found anything plausible
|
| 331 |
+
return None
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/ctypes/_endian.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sys
|
| 2 |
+
from ctypes import *
|
| 3 |
+
|
| 4 |
+
_array_type = type(Array)
|
| 5 |
+
|
| 6 |
+
def _other_endian(typ):
|
| 7 |
+
"""Return the type with the 'other' byte order. Simple types like
|
| 8 |
+
c_int and so on already have __ctype_be__ and __ctype_le__
|
| 9 |
+
attributes which contain the types, for more complicated types
|
| 10 |
+
arrays and structures are supported.
|
| 11 |
+
"""
|
| 12 |
+
# check _OTHER_ENDIAN attribute (present if typ is primitive type)
|
| 13 |
+
if hasattr(typ, _OTHER_ENDIAN):
|
| 14 |
+
return getattr(typ, _OTHER_ENDIAN)
|
| 15 |
+
# if typ is array
|
| 16 |
+
if isinstance(typ, _array_type):
|
| 17 |
+
return _other_endian(typ._type_) * typ._length_
|
| 18 |
+
# if typ is structure
|
| 19 |
+
if issubclass(typ, Structure):
|
| 20 |
+
return typ
|
| 21 |
+
raise TypeError("This type does not support other endian: %s" % typ)
|
| 22 |
+
|
| 23 |
+
class _swapped_meta(type(Structure)):
|
| 24 |
+
def __setattr__(self, attrname, value):
|
| 25 |
+
if attrname == "_fields_":
|
| 26 |
+
fields = []
|
| 27 |
+
for desc in value:
|
| 28 |
+
name = desc[0]
|
| 29 |
+
typ = desc[1]
|
| 30 |
+
rest = desc[2:]
|
| 31 |
+
fields.append((name, _other_endian(typ)) + rest)
|
| 32 |
+
value = fields
|
| 33 |
+
super().__setattr__(attrname, value)
|
| 34 |
+
|
| 35 |
+
################################################################
|
| 36 |
+
|
| 37 |
+
# Note: The Structure metaclass checks for the *presence* (not the
|
| 38 |
+
# value!) of a _swapped_bytes_ attribute to determine the bit order in
|
| 39 |
+
# structures containing bit fields.
|
| 40 |
+
|
| 41 |
+
if sys.byteorder == "little":
|
| 42 |
+
_OTHER_ENDIAN = "__ctype_be__"
|
| 43 |
+
|
| 44 |
+
LittleEndianStructure = Structure
|
| 45 |
+
|
| 46 |
+
class BigEndianStructure(Structure, metaclass=_swapped_meta):
|
| 47 |
+
"""Structure with big endian byte order"""
|
| 48 |
+
__slots__ = ()
|
| 49 |
+
_swappedbytes_ = None
|
| 50 |
+
|
| 51 |
+
elif sys.byteorder == "big":
|
| 52 |
+
_OTHER_ENDIAN = "__ctype_le__"
|
| 53 |
+
|
| 54 |
+
BigEndianStructure = Structure
|
| 55 |
+
class LittleEndianStructure(Structure, metaclass=_swapped_meta):
|
| 56 |
+
"""Structure with little endian byte order"""
|
| 57 |
+
__slots__ = ()
|
| 58 |
+
_swappedbytes_ = None
|
| 59 |
+
|
| 60 |
+
else:
|
| 61 |
+
raise RuntimeError("Invalid byteorder")
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/ctypes/util.py
ADDED
|
@@ -0,0 +1,397 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import shutil
|
| 3 |
+
import subprocess
|
| 4 |
+
import sys
|
| 5 |
+
|
| 6 |
+
# find_library(name) returns the pathname of a library, or None.
|
| 7 |
+
if os.name == "nt":
|
| 8 |
+
|
| 9 |
+
def _get_build_version():
|
| 10 |
+
"""Return the version of MSVC that was used to build Python.
|
| 11 |
+
|
| 12 |
+
For Python 2.3 and up, the version number is included in
|
| 13 |
+
sys.version. For earlier versions, assume the compiler is MSVC 6.
|
| 14 |
+
"""
|
| 15 |
+
# This function was copied from Lib/distutils/msvccompiler.py
|
| 16 |
+
prefix = "MSC v."
|
| 17 |
+
i = sys.version.find(prefix)
|
| 18 |
+
if i == -1:
|
| 19 |
+
return 6
|
| 20 |
+
i = i + len(prefix)
|
| 21 |
+
s, rest = sys.version[i:].split(" ", 1)
|
| 22 |
+
majorVersion = int(s[:-2]) - 6
|
| 23 |
+
if majorVersion >= 13:
|
| 24 |
+
majorVersion += 1
|
| 25 |
+
minorVersion = int(s[2:3]) / 10.0
|
| 26 |
+
# I don't think paths are affected by minor version in version 6
|
| 27 |
+
if majorVersion == 6:
|
| 28 |
+
minorVersion = 0
|
| 29 |
+
if majorVersion >= 6:
|
| 30 |
+
return majorVersion + minorVersion
|
| 31 |
+
# else we don't know what version of the compiler this is
|
| 32 |
+
return None
|
| 33 |
+
|
| 34 |
+
def find_msvcrt():
|
| 35 |
+
"""Return the name of the VC runtime dll"""
|
| 36 |
+
version = _get_build_version()
|
| 37 |
+
if version is None:
|
| 38 |
+
# better be safe than sorry
|
| 39 |
+
return None
|
| 40 |
+
if version <= 6:
|
| 41 |
+
clibname = 'msvcrt'
|
| 42 |
+
elif version <= 13:
|
| 43 |
+
clibname = 'msvcr%d' % (version * 10)
|
| 44 |
+
else:
|
| 45 |
+
# CRT is no longer directly loadable. See issue23606 for the
|
| 46 |
+
# discussion about alternative approaches.
|
| 47 |
+
return None
|
| 48 |
+
|
| 49 |
+
# If python was built with in debug mode
|
| 50 |
+
import importlib.machinery
|
| 51 |
+
if '_d.pyd' in importlib.machinery.EXTENSION_SUFFIXES:
|
| 52 |
+
clibname += 'd'
|
| 53 |
+
return clibname+'.dll'
|
| 54 |
+
|
| 55 |
+
def find_library(name):
|
| 56 |
+
if name in ('c', 'm'):
|
| 57 |
+
return find_msvcrt()
|
| 58 |
+
# See MSDN for the REAL search order.
|
| 59 |
+
for directory in os.environ['PATH'].split(os.pathsep):
|
| 60 |
+
fname = os.path.join(directory, name)
|
| 61 |
+
if os.path.isfile(fname):
|
| 62 |
+
return fname
|
| 63 |
+
if fname.lower().endswith(".dll"):
|
| 64 |
+
continue
|
| 65 |
+
fname = fname + ".dll"
|
| 66 |
+
if os.path.isfile(fname):
|
| 67 |
+
return fname
|
| 68 |
+
return None
|
| 69 |
+
|
| 70 |
+
elif os.name == "posix" and sys.platform == "darwin":
|
| 71 |
+
from ctypes.macholib.dyld import dyld_find as _dyld_find
|
| 72 |
+
def find_library(name):
|
| 73 |
+
possible = ['@executable_path/../lib/lib%s.dylib' % name,
|
| 74 |
+
'lib%s.dylib' % name,
|
| 75 |
+
'%s.dylib' % name,
|
| 76 |
+
'%s.framework/%s' % (name, name)]
|
| 77 |
+
for name in possible:
|
| 78 |
+
try:
|
| 79 |
+
return _dyld_find(name)
|
| 80 |
+
except ValueError:
|
| 81 |
+
continue
|
| 82 |
+
return None
|
| 83 |
+
|
| 84 |
+
elif sys.platform.startswith("aix"):
|
| 85 |
+
# AIX has two styles of storing shared libraries
|
| 86 |
+
# GNU auto_tools refer to these as svr4 and aix
|
| 87 |
+
# svr4 (System V Release 4) is a regular file, often with .so as suffix
|
| 88 |
+
# AIX style uses an archive (suffix .a) with members (e.g., shr.o, libssl.so)
|
| 89 |
+
# see issue#26439 and _aix.py for more details
|
| 90 |
+
|
| 91 |
+
from ctypes._aix import find_library
|
| 92 |
+
|
| 93 |
+
elif os.name == "posix":
|
| 94 |
+
# Andreas Degert's find functions, using gcc, /sbin/ldconfig, objdump
|
| 95 |
+
import re, tempfile
|
| 96 |
+
|
| 97 |
+
def _is_elf(filename):
|
| 98 |
+
"Return True if the given file is an ELF file"
|
| 99 |
+
elf_header = b'\x7fELF'
|
| 100 |
+
with open(filename, 'br') as thefile:
|
| 101 |
+
return thefile.read(4) == elf_header
|
| 102 |
+
|
| 103 |
+
def _findLib_gcc(name):
|
| 104 |
+
# Run GCC's linker with the -t (aka --trace) option and examine the
|
| 105 |
+
# library name it prints out. The GCC command will fail because we
|
| 106 |
+
# haven't supplied a proper program with main(), but that does not
|
| 107 |
+
# matter.
|
| 108 |
+
expr = os.fsencode(r'[^\(\)\s]*lib%s\.[^\(\)\s]*' % re.escape(name))
|
| 109 |
+
|
| 110 |
+
c_compiler = shutil.which('gcc')
|
| 111 |
+
if not c_compiler:
|
| 112 |
+
c_compiler = shutil.which('cc')
|
| 113 |
+
if not c_compiler:
|
| 114 |
+
# No C compiler available, give up
|
| 115 |
+
return None
|
| 116 |
+
|
| 117 |
+
temp = tempfile.NamedTemporaryFile()
|
| 118 |
+
try:
|
| 119 |
+
args = [c_compiler, '-Wl,-t', '-o', temp.name, '-l' + name]
|
| 120 |
+
|
| 121 |
+
env = dict(os.environ)
|
| 122 |
+
env['LC_ALL'] = 'C'
|
| 123 |
+
env['LANG'] = 'C'
|
| 124 |
+
try:
|
| 125 |
+
proc = subprocess.Popen(args,
|
| 126 |
+
stdout=subprocess.PIPE,
|
| 127 |
+
stderr=subprocess.STDOUT,
|
| 128 |
+
env=env)
|
| 129 |
+
except OSError: # E.g. bad executable
|
| 130 |
+
return None
|
| 131 |
+
with proc:
|
| 132 |
+
trace = proc.stdout.read()
|
| 133 |
+
finally:
|
| 134 |
+
try:
|
| 135 |
+
temp.close()
|
| 136 |
+
except FileNotFoundError:
|
| 137 |
+
# Raised if the file was already removed, which is the normal
|
| 138 |
+
# behaviour of GCC if linking fails
|
| 139 |
+
pass
|
| 140 |
+
res = re.findall(expr, trace)
|
| 141 |
+
if not res:
|
| 142 |
+
return None
|
| 143 |
+
|
| 144 |
+
for file in res:
|
| 145 |
+
# Check if the given file is an elf file: gcc can report
|
| 146 |
+
# some files that are linker scripts and not actual
|
| 147 |
+
# shared objects. See bpo-41976 for more details
|
| 148 |
+
if not _is_elf(file):
|
| 149 |
+
continue
|
| 150 |
+
return os.fsdecode(file)
|
| 151 |
+
|
| 152 |
+
|
| 153 |
+
if sys.platform == "sunos5":
|
| 154 |
+
# use /usr/ccs/bin/dump on solaris
|
| 155 |
+
def _get_soname(f):
|
| 156 |
+
if not f:
|
| 157 |
+
return None
|
| 158 |
+
|
| 159 |
+
try:
|
| 160 |
+
proc = subprocess.Popen(("/usr/ccs/bin/dump", "-Lpv", f),
|
| 161 |
+
stdout=subprocess.PIPE,
|
| 162 |
+
stderr=subprocess.DEVNULL)
|
| 163 |
+
except OSError: # E.g. command not found
|
| 164 |
+
return None
|
| 165 |
+
with proc:
|
| 166 |
+
data = proc.stdout.read()
|
| 167 |
+
res = re.search(br'\[.*\]\sSONAME\s+([^\s]+)', data)
|
| 168 |
+
if not res:
|
| 169 |
+
return None
|
| 170 |
+
return os.fsdecode(res.group(1))
|
| 171 |
+
else:
|
| 172 |
+
def _get_soname(f):
|
| 173 |
+
# assuming GNU binutils / ELF
|
| 174 |
+
if not f:
|
| 175 |
+
return None
|
| 176 |
+
objdump = shutil.which('objdump')
|
| 177 |
+
if not objdump:
|
| 178 |
+
# objdump is not available, give up
|
| 179 |
+
return None
|
| 180 |
+
|
| 181 |
+
try:
|
| 182 |
+
proc = subprocess.Popen((objdump, '-p', '-j', '.dynamic', f),
|
| 183 |
+
stdout=subprocess.PIPE,
|
| 184 |
+
stderr=subprocess.DEVNULL)
|
| 185 |
+
except OSError: # E.g. bad executable
|
| 186 |
+
return None
|
| 187 |
+
with proc:
|
| 188 |
+
dump = proc.stdout.read()
|
| 189 |
+
res = re.search(br'\sSONAME\s+([^\s]+)', dump)
|
| 190 |
+
if not res:
|
| 191 |
+
return None
|
| 192 |
+
return os.fsdecode(res.group(1))
|
| 193 |
+
|
| 194 |
+
if sys.platform.startswith(("freebsd", "openbsd", "dragonfly")):
|
| 195 |
+
|
| 196 |
+
def _num_version(libname):
|
| 197 |
+
# "libxyz.so.MAJOR.MINOR" => [ MAJOR, MINOR ]
|
| 198 |
+
parts = libname.split(b".")
|
| 199 |
+
nums = []
|
| 200 |
+
try:
|
| 201 |
+
while parts:
|
| 202 |
+
nums.insert(0, int(parts.pop()))
|
| 203 |
+
except ValueError:
|
| 204 |
+
pass
|
| 205 |
+
return nums or [sys.maxsize]
|
| 206 |
+
|
| 207 |
+
def find_library(name):
|
| 208 |
+
ename = re.escape(name)
|
| 209 |
+
expr = r':-l%s\.\S+ => \S*/(lib%s\.\S+)' % (ename, ename)
|
| 210 |
+
expr = os.fsencode(expr)
|
| 211 |
+
|
| 212 |
+
try:
|
| 213 |
+
proc = subprocess.Popen(('/sbin/ldconfig', '-r'),
|
| 214 |
+
stdout=subprocess.PIPE,
|
| 215 |
+
stderr=subprocess.DEVNULL)
|
| 216 |
+
except OSError: # E.g. command not found
|
| 217 |
+
data = b''
|
| 218 |
+
else:
|
| 219 |
+
with proc:
|
| 220 |
+
data = proc.stdout.read()
|
| 221 |
+
|
| 222 |
+
res = re.findall(expr, data)
|
| 223 |
+
if not res:
|
| 224 |
+
return _get_soname(_findLib_gcc(name))
|
| 225 |
+
res.sort(key=_num_version)
|
| 226 |
+
return os.fsdecode(res[-1])
|
| 227 |
+
|
| 228 |
+
elif sys.platform == "sunos5":
|
| 229 |
+
|
| 230 |
+
def _findLib_crle(name, is64):
|
| 231 |
+
if not os.path.exists('/usr/bin/crle'):
|
| 232 |
+
return None
|
| 233 |
+
|
| 234 |
+
env = dict(os.environ)
|
| 235 |
+
env['LC_ALL'] = 'C'
|
| 236 |
+
|
| 237 |
+
if is64:
|
| 238 |
+
args = ('/usr/bin/crle', '-64')
|
| 239 |
+
else:
|
| 240 |
+
args = ('/usr/bin/crle',)
|
| 241 |
+
|
| 242 |
+
paths = None
|
| 243 |
+
try:
|
| 244 |
+
proc = subprocess.Popen(args,
|
| 245 |
+
stdout=subprocess.PIPE,
|
| 246 |
+
stderr=subprocess.DEVNULL,
|
| 247 |
+
env=env)
|
| 248 |
+
except OSError: # E.g. bad executable
|
| 249 |
+
return None
|
| 250 |
+
with proc:
|
| 251 |
+
for line in proc.stdout:
|
| 252 |
+
line = line.strip()
|
| 253 |
+
if line.startswith(b'Default Library Path (ELF):'):
|
| 254 |
+
paths = os.fsdecode(line).split()[4]
|
| 255 |
+
|
| 256 |
+
if not paths:
|
| 257 |
+
return None
|
| 258 |
+
|
| 259 |
+
for dir in paths.split(":"):
|
| 260 |
+
libfile = os.path.join(dir, "lib%s.so" % name)
|
| 261 |
+
if os.path.exists(libfile):
|
| 262 |
+
return libfile
|
| 263 |
+
|
| 264 |
+
return None
|
| 265 |
+
|
| 266 |
+
def find_library(name, is64 = False):
|
| 267 |
+
return _get_soname(_findLib_crle(name, is64) or _findLib_gcc(name))
|
| 268 |
+
|
| 269 |
+
else:
|
| 270 |
+
|
| 271 |
+
def _findSoname_ldconfig(name):
|
| 272 |
+
import struct
|
| 273 |
+
if struct.calcsize('l') == 4:
|
| 274 |
+
machine = os.uname().machine + '-32'
|
| 275 |
+
else:
|
| 276 |
+
machine = os.uname().machine + '-64'
|
| 277 |
+
mach_map = {
|
| 278 |
+
'x86_64-64': 'libc6,x86-64',
|
| 279 |
+
'ppc64-64': 'libc6,64bit',
|
| 280 |
+
'sparc64-64': 'libc6,64bit',
|
| 281 |
+
's390x-64': 'libc6,64bit',
|
| 282 |
+
'ia64-64': 'libc6,IA-64',
|
| 283 |
+
}
|
| 284 |
+
abi_type = mach_map.get(machine, 'libc6')
|
| 285 |
+
|
| 286 |
+
# XXX assuming GLIBC's ldconfig (with option -p)
|
| 287 |
+
regex = r'\s+(lib%s\.[^\s]+)\s+\(%s'
|
| 288 |
+
regex = os.fsencode(regex % (re.escape(name), abi_type))
|
| 289 |
+
try:
|
| 290 |
+
with subprocess.Popen(['/sbin/ldconfig', '-p'],
|
| 291 |
+
stdin=subprocess.DEVNULL,
|
| 292 |
+
stderr=subprocess.DEVNULL,
|
| 293 |
+
stdout=subprocess.PIPE,
|
| 294 |
+
env={'LC_ALL': 'C', 'LANG': 'C'}) as p:
|
| 295 |
+
res = re.search(regex, p.stdout.read())
|
| 296 |
+
if res:
|
| 297 |
+
return os.fsdecode(res.group(1))
|
| 298 |
+
except OSError:
|
| 299 |
+
pass
|
| 300 |
+
|
| 301 |
+
def _findLib_ld(name):
|
| 302 |
+
# See issue #9998 for why this is needed
|
| 303 |
+
expr = r'[^\(\)\s]*lib%s\.[^\(\)\s]*' % re.escape(name)
|
| 304 |
+
cmd = ['ld', '-t']
|
| 305 |
+
libpath = os.environ.get('LD_LIBRARY_PATH')
|
| 306 |
+
if libpath:
|
| 307 |
+
for d in libpath.split(':'):
|
| 308 |
+
cmd.extend(['-L', d])
|
| 309 |
+
cmd.extend(['-o', os.devnull, '-l%s' % name])
|
| 310 |
+
result = None
|
| 311 |
+
try:
|
| 312 |
+
p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
|
| 313 |
+
stderr=subprocess.PIPE,
|
| 314 |
+
universal_newlines=True)
|
| 315 |
+
out, _ = p.communicate()
|
| 316 |
+
res = re.findall(expr, os.fsdecode(out))
|
| 317 |
+
for file in res:
|
| 318 |
+
# Check if the given file is an elf file: gcc can report
|
| 319 |
+
# some files that are linker scripts and not actual
|
| 320 |
+
# shared objects. See bpo-41976 for more details
|
| 321 |
+
if not _is_elf(file):
|
| 322 |
+
continue
|
| 323 |
+
return os.fsdecode(file)
|
| 324 |
+
except Exception:
|
| 325 |
+
pass # result will be None
|
| 326 |
+
return result
|
| 327 |
+
|
| 328 |
+
def _findLib_prefix(name):
|
| 329 |
+
if not name:
|
| 330 |
+
return None
|
| 331 |
+
for fullname in (name, "lib%s.so" % (name)):
|
| 332 |
+
path = os.path.join(sys.prefix, 'lib', fullname)
|
| 333 |
+
if os.path.exists(path):
|
| 334 |
+
return path
|
| 335 |
+
return None
|
| 336 |
+
|
| 337 |
+
def find_library(name):
|
| 338 |
+
# See issue #9998
|
| 339 |
+
# Yes calling _findLib_prefix twice is deliberate, because _get_soname ditches
|
| 340 |
+
# the full path.
|
| 341 |
+
# When objdump is unavailable this returns None
|
| 342 |
+
so_name = _get_soname(_findLib_prefix(name)) or name
|
| 343 |
+
if so_name != name:
|
| 344 |
+
return _findLib_prefix(so_name) or \
|
| 345 |
+
_findLib_prefix(name) or \
|
| 346 |
+
_findSoname_ldconfig(name) or \
|
| 347 |
+
_get_soname(_findLib_gcc(name) or _findLib_ld(name))
|
| 348 |
+
else:
|
| 349 |
+
return _findLib_prefix(name) or \
|
| 350 |
+
_findSoname_ldconfig(name) or \
|
| 351 |
+
_get_soname(_findLib_gcc(name) or _findLib_ld(name))
|
| 352 |
+
|
| 353 |
+
################################################################
|
| 354 |
+
# test code
|
| 355 |
+
|
| 356 |
+
def test():
|
| 357 |
+
from ctypes import cdll
|
| 358 |
+
if os.name == "nt":
|
| 359 |
+
print(cdll.msvcrt)
|
| 360 |
+
print(cdll.load("msvcrt"))
|
| 361 |
+
print(find_library("msvcrt"))
|
| 362 |
+
|
| 363 |
+
if os.name == "posix":
|
| 364 |
+
# find and load_version
|
| 365 |
+
print(find_library("m"))
|
| 366 |
+
print(find_library("c"))
|
| 367 |
+
print(find_library("bz2"))
|
| 368 |
+
|
| 369 |
+
# load
|
| 370 |
+
if sys.platform == "darwin":
|
| 371 |
+
print(cdll.LoadLibrary("libm.dylib"))
|
| 372 |
+
print(cdll.LoadLibrary("libcrypto.dylib"))
|
| 373 |
+
print(cdll.LoadLibrary("libSystem.dylib"))
|
| 374 |
+
print(cdll.LoadLibrary("System.framework/System"))
|
| 375 |
+
# issue-26439 - fix broken test call for AIX
|
| 376 |
+
elif sys.platform.startswith("aix"):
|
| 377 |
+
from ctypes import CDLL
|
| 378 |
+
if sys.maxsize < 2**32:
|
| 379 |
+
print(f"Using CDLL(name, os.RTLD_MEMBER): {CDLL('libc.a(shr.o)', os.RTLD_MEMBER)}")
|
| 380 |
+
print(f"Using cdll.LoadLibrary(): {cdll.LoadLibrary('libc.a(shr.o)')}")
|
| 381 |
+
# librpm.so is only available as 32-bit shared library
|
| 382 |
+
print(find_library("rpm"))
|
| 383 |
+
print(cdll.LoadLibrary("librpm.so"))
|
| 384 |
+
else:
|
| 385 |
+
print(f"Using CDLL(name, os.RTLD_MEMBER): {CDLL('libc.a(shr_64.o)', os.RTLD_MEMBER)}")
|
| 386 |
+
print(f"Using cdll.LoadLibrary(): {cdll.LoadLibrary('libc.a(shr_64.o)')}")
|
| 387 |
+
print(f"crypt\t:: {find_library('crypt')}")
|
| 388 |
+
print(f"crypt\t:: {cdll.LoadLibrary(find_library('crypt'))}")
|
| 389 |
+
print(f"crypto\t:: {find_library('crypto')}")
|
| 390 |
+
print(f"crypto\t:: {cdll.LoadLibrary(find_library('crypto'))}")
|
| 391 |
+
else:
|
| 392 |
+
print(cdll.LoadLibrary("libm.so"))
|
| 393 |
+
print(cdll.LoadLibrary("libcrypt.so"))
|
| 394 |
+
print(find_library("crypt"))
|
| 395 |
+
|
| 396 |
+
if __name__ == "__main__":
|
| 397 |
+
test()
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/ctypes/wintypes.py
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# The most useful windows datatypes
|
| 2 |
+
import ctypes
|
| 3 |
+
|
| 4 |
+
BYTE = ctypes.c_byte
|
| 5 |
+
WORD = ctypes.c_ushort
|
| 6 |
+
DWORD = ctypes.c_ulong
|
| 7 |
+
|
| 8 |
+
#UCHAR = ctypes.c_uchar
|
| 9 |
+
CHAR = ctypes.c_char
|
| 10 |
+
WCHAR = ctypes.c_wchar
|
| 11 |
+
UINT = ctypes.c_uint
|
| 12 |
+
INT = ctypes.c_int
|
| 13 |
+
|
| 14 |
+
DOUBLE = ctypes.c_double
|
| 15 |
+
FLOAT = ctypes.c_float
|
| 16 |
+
|
| 17 |
+
BOOLEAN = BYTE
|
| 18 |
+
BOOL = ctypes.c_long
|
| 19 |
+
|
| 20 |
+
class VARIANT_BOOL(ctypes._SimpleCData):
|
| 21 |
+
_type_ = "v"
|
| 22 |
+
def __repr__(self):
|
| 23 |
+
return "%s(%r)" % (self.__class__.__name__, self.value)
|
| 24 |
+
|
| 25 |
+
ULONG = ctypes.c_ulong
|
| 26 |
+
LONG = ctypes.c_long
|
| 27 |
+
|
| 28 |
+
USHORT = ctypes.c_ushort
|
| 29 |
+
SHORT = ctypes.c_short
|
| 30 |
+
|
| 31 |
+
# in the windows header files, these are structures.
|
| 32 |
+
_LARGE_INTEGER = LARGE_INTEGER = ctypes.c_longlong
|
| 33 |
+
_ULARGE_INTEGER = ULARGE_INTEGER = ctypes.c_ulonglong
|
| 34 |
+
|
| 35 |
+
LPCOLESTR = LPOLESTR = OLESTR = ctypes.c_wchar_p
|
| 36 |
+
LPCWSTR = LPWSTR = ctypes.c_wchar_p
|
| 37 |
+
LPCSTR = LPSTR = ctypes.c_char_p
|
| 38 |
+
LPCVOID = LPVOID = ctypes.c_void_p
|
| 39 |
+
|
| 40 |
+
# WPARAM is defined as UINT_PTR (unsigned type)
|
| 41 |
+
# LPARAM is defined as LONG_PTR (signed type)
|
| 42 |
+
if ctypes.sizeof(ctypes.c_long) == ctypes.sizeof(ctypes.c_void_p):
|
| 43 |
+
WPARAM = ctypes.c_ulong
|
| 44 |
+
LPARAM = ctypes.c_long
|
| 45 |
+
elif ctypes.sizeof(ctypes.c_longlong) == ctypes.sizeof(ctypes.c_void_p):
|
| 46 |
+
WPARAM = ctypes.c_ulonglong
|
| 47 |
+
LPARAM = ctypes.c_longlong
|
| 48 |
+
|
| 49 |
+
ATOM = WORD
|
| 50 |
+
LANGID = WORD
|
| 51 |
+
|
| 52 |
+
COLORREF = DWORD
|
| 53 |
+
LGRPID = DWORD
|
| 54 |
+
LCTYPE = DWORD
|
| 55 |
+
|
| 56 |
+
LCID = DWORD
|
| 57 |
+
|
| 58 |
+
################################################################
|
| 59 |
+
# HANDLE types
|
| 60 |
+
HANDLE = ctypes.c_void_p # in the header files: void *
|
| 61 |
+
|
| 62 |
+
HACCEL = HANDLE
|
| 63 |
+
HBITMAP = HANDLE
|
| 64 |
+
HBRUSH = HANDLE
|
| 65 |
+
HCOLORSPACE = HANDLE
|
| 66 |
+
HDC = HANDLE
|
| 67 |
+
HDESK = HANDLE
|
| 68 |
+
HDWP = HANDLE
|
| 69 |
+
HENHMETAFILE = HANDLE
|
| 70 |
+
HFONT = HANDLE
|
| 71 |
+
HGDIOBJ = HANDLE
|
| 72 |
+
HGLOBAL = HANDLE
|
| 73 |
+
HHOOK = HANDLE
|
| 74 |
+
HICON = HANDLE
|
| 75 |
+
HINSTANCE = HANDLE
|
| 76 |
+
HKEY = HANDLE
|
| 77 |
+
HKL = HANDLE
|
| 78 |
+
HLOCAL = HANDLE
|
| 79 |
+
HMENU = HANDLE
|
| 80 |
+
HMETAFILE = HANDLE
|
| 81 |
+
HMODULE = HANDLE
|
| 82 |
+
HMONITOR = HANDLE
|
| 83 |
+
HPALETTE = HANDLE
|
| 84 |
+
HPEN = HANDLE
|
| 85 |
+
HRGN = HANDLE
|
| 86 |
+
HRSRC = HANDLE
|
| 87 |
+
HSTR = HANDLE
|
| 88 |
+
HTASK = HANDLE
|
| 89 |
+
HWINSTA = HANDLE
|
| 90 |
+
HWND = HANDLE
|
| 91 |
+
SC_HANDLE = HANDLE
|
| 92 |
+
SERVICE_STATUS_HANDLE = HANDLE
|
| 93 |
+
|
| 94 |
+
################################################################
|
| 95 |
+
# Some important structure definitions
|
| 96 |
+
|
| 97 |
+
class RECT(ctypes.Structure):
|
| 98 |
+
_fields_ = [("left", LONG),
|
| 99 |
+
("top", LONG),
|
| 100 |
+
("right", LONG),
|
| 101 |
+
("bottom", LONG)]
|
| 102 |
+
tagRECT = _RECTL = RECTL = RECT
|
| 103 |
+
|
| 104 |
+
class _SMALL_RECT(ctypes.Structure):
|
| 105 |
+
_fields_ = [('Left', SHORT),
|
| 106 |
+
('Top', SHORT),
|
| 107 |
+
('Right', SHORT),
|
| 108 |
+
('Bottom', SHORT)]
|
| 109 |
+
SMALL_RECT = _SMALL_RECT
|
| 110 |
+
|
| 111 |
+
class _COORD(ctypes.Structure):
|
| 112 |
+
_fields_ = [('X', SHORT),
|
| 113 |
+
('Y', SHORT)]
|
| 114 |
+
|
| 115 |
+
class POINT(ctypes.Structure):
|
| 116 |
+
_fields_ = [("x", LONG),
|
| 117 |
+
("y", LONG)]
|
| 118 |
+
tagPOINT = _POINTL = POINTL = POINT
|
| 119 |
+
|
| 120 |
+
class SIZE(ctypes.Structure):
|
| 121 |
+
_fields_ = [("cx", LONG),
|
| 122 |
+
("cy", LONG)]
|
| 123 |
+
tagSIZE = SIZEL = SIZE
|
| 124 |
+
|
| 125 |
+
def RGB(red, green, blue):
|
| 126 |
+
return red + (green << 8) + (blue << 16)
|
| 127 |
+
|
| 128 |
+
class FILETIME(ctypes.Structure):
|
| 129 |
+
_fields_ = [("dwLowDateTime", DWORD),
|
| 130 |
+
("dwHighDateTime", DWORD)]
|
| 131 |
+
_FILETIME = FILETIME
|
| 132 |
+
|
| 133 |
+
class MSG(ctypes.Structure):
|
| 134 |
+
_fields_ = [("hWnd", HWND),
|
| 135 |
+
("message", UINT),
|
| 136 |
+
("wParam", WPARAM),
|
| 137 |
+
("lParam", LPARAM),
|
| 138 |
+
("time", DWORD),
|
| 139 |
+
("pt", POINT)]
|
| 140 |
+
tagMSG = MSG
|
| 141 |
+
MAX_PATH = 260
|
| 142 |
+
|
| 143 |
+
class WIN32_FIND_DATAA(ctypes.Structure):
|
| 144 |
+
_fields_ = [("dwFileAttributes", DWORD),
|
| 145 |
+
("ftCreationTime", FILETIME),
|
| 146 |
+
("ftLastAccessTime", FILETIME),
|
| 147 |
+
("ftLastWriteTime", FILETIME),
|
| 148 |
+
("nFileSizeHigh", DWORD),
|
| 149 |
+
("nFileSizeLow", DWORD),
|
| 150 |
+
("dwReserved0", DWORD),
|
| 151 |
+
("dwReserved1", DWORD),
|
| 152 |
+
("cFileName", CHAR * MAX_PATH),
|
| 153 |
+
("cAlternateFileName", CHAR * 14)]
|
| 154 |
+
|
| 155 |
+
class WIN32_FIND_DATAW(ctypes.Structure):
|
| 156 |
+
_fields_ = [("dwFileAttributes", DWORD),
|
| 157 |
+
("ftCreationTime", FILETIME),
|
| 158 |
+
("ftLastAccessTime", FILETIME),
|
| 159 |
+
("ftLastWriteTime", FILETIME),
|
| 160 |
+
("nFileSizeHigh", DWORD),
|
| 161 |
+
("nFileSizeLow", DWORD),
|
| 162 |
+
("dwReserved0", DWORD),
|
| 163 |
+
("dwReserved1", DWORD),
|
| 164 |
+
("cFileName", WCHAR * MAX_PATH),
|
| 165 |
+
("cAlternateFileName", WCHAR * 14)]
|
| 166 |
+
|
| 167 |
+
################################################################
|
| 168 |
+
# Pointer types
|
| 169 |
+
|
| 170 |
+
LPBOOL = PBOOL = ctypes.POINTER(BOOL)
|
| 171 |
+
PBOOLEAN = ctypes.POINTER(BOOLEAN)
|
| 172 |
+
LPBYTE = PBYTE = ctypes.POINTER(BYTE)
|
| 173 |
+
PCHAR = ctypes.POINTER(CHAR)
|
| 174 |
+
LPCOLORREF = ctypes.POINTER(COLORREF)
|
| 175 |
+
LPDWORD = PDWORD = ctypes.POINTER(DWORD)
|
| 176 |
+
LPFILETIME = PFILETIME = ctypes.POINTER(FILETIME)
|
| 177 |
+
PFLOAT = ctypes.POINTER(FLOAT)
|
| 178 |
+
LPHANDLE = PHANDLE = ctypes.POINTER(HANDLE)
|
| 179 |
+
PHKEY = ctypes.POINTER(HKEY)
|
| 180 |
+
LPHKL = ctypes.POINTER(HKL)
|
| 181 |
+
LPINT = PINT = ctypes.POINTER(INT)
|
| 182 |
+
PLARGE_INTEGER = ctypes.POINTER(LARGE_INTEGER)
|
| 183 |
+
PLCID = ctypes.POINTER(LCID)
|
| 184 |
+
LPLONG = PLONG = ctypes.POINTER(LONG)
|
| 185 |
+
LPMSG = PMSG = ctypes.POINTER(MSG)
|
| 186 |
+
LPPOINT = PPOINT = ctypes.POINTER(POINT)
|
| 187 |
+
PPOINTL = ctypes.POINTER(POINTL)
|
| 188 |
+
LPRECT = PRECT = ctypes.POINTER(RECT)
|
| 189 |
+
LPRECTL = PRECTL = ctypes.POINTER(RECTL)
|
| 190 |
+
LPSC_HANDLE = ctypes.POINTER(SC_HANDLE)
|
| 191 |
+
PSHORT = ctypes.POINTER(SHORT)
|
| 192 |
+
LPSIZE = PSIZE = ctypes.POINTER(SIZE)
|
| 193 |
+
LPSIZEL = PSIZEL = ctypes.POINTER(SIZEL)
|
| 194 |
+
PSMALL_RECT = ctypes.POINTER(SMALL_RECT)
|
| 195 |
+
LPUINT = PUINT = ctypes.POINTER(UINT)
|
| 196 |
+
PULARGE_INTEGER = ctypes.POINTER(ULARGE_INTEGER)
|
| 197 |
+
PULONG = ctypes.POINTER(ULONG)
|
| 198 |
+
PUSHORT = ctypes.POINTER(USHORT)
|
| 199 |
+
PWCHAR = ctypes.POINTER(WCHAR)
|
| 200 |
+
LPWIN32_FIND_DATAA = PWIN32_FIND_DATAA = ctypes.POINTER(WIN32_FIND_DATAA)
|
| 201 |
+
LPWIN32_FIND_DATAW = PWIN32_FIND_DATAW = ctypes.POINTER(WIN32_FIND_DATAW)
|
| 202 |
+
LPWORD = PWORD = ctypes.POINTER(WORD)
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/http/__init__.py
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from enum import IntEnum
|
| 2 |
+
|
| 3 |
+
__all__ = ['HTTPStatus']
|
| 4 |
+
|
| 5 |
+
class HTTPStatus(IntEnum):
|
| 6 |
+
"""HTTP status codes and reason phrases
|
| 7 |
+
|
| 8 |
+
Status codes from the following RFCs are all observed:
|
| 9 |
+
|
| 10 |
+
* RFC 7231: Hypertext Transfer Protocol (HTTP/1.1), obsoletes 2616
|
| 11 |
+
* RFC 6585: Additional HTTP Status Codes
|
| 12 |
+
* RFC 3229: Delta encoding in HTTP
|
| 13 |
+
* RFC 4918: HTTP Extensions for WebDAV, obsoletes 2518
|
| 14 |
+
* RFC 5842: Binding Extensions to WebDAV
|
| 15 |
+
* RFC 7238: Permanent Redirect
|
| 16 |
+
* RFC 2295: Transparent Content Negotiation in HTTP
|
| 17 |
+
* RFC 2774: An HTTP Extension Framework
|
| 18 |
+
* RFC 7725: An HTTP Status Code to Report Legal Obstacles
|
| 19 |
+
* RFC 7540: Hypertext Transfer Protocol Version 2 (HTTP/2)
|
| 20 |
+
"""
|
| 21 |
+
def __new__(cls, value, phrase, description=''):
|
| 22 |
+
obj = int.__new__(cls, value)
|
| 23 |
+
obj._value_ = value
|
| 24 |
+
|
| 25 |
+
obj.phrase = phrase
|
| 26 |
+
obj.description = description
|
| 27 |
+
return obj
|
| 28 |
+
|
| 29 |
+
# informational
|
| 30 |
+
CONTINUE = 100, 'Continue', 'Request received, please continue'
|
| 31 |
+
SWITCHING_PROTOCOLS = (101, 'Switching Protocols',
|
| 32 |
+
'Switching to new protocol; obey Upgrade header')
|
| 33 |
+
PROCESSING = 102, 'Processing'
|
| 34 |
+
|
| 35 |
+
# success
|
| 36 |
+
OK = 200, 'OK', 'Request fulfilled, document follows'
|
| 37 |
+
CREATED = 201, 'Created', 'Document created, URL follows'
|
| 38 |
+
ACCEPTED = (202, 'Accepted',
|
| 39 |
+
'Request accepted, processing continues off-line')
|
| 40 |
+
NON_AUTHORITATIVE_INFORMATION = (203,
|
| 41 |
+
'Non-Authoritative Information', 'Request fulfilled from cache')
|
| 42 |
+
NO_CONTENT = 204, 'No Content', 'Request fulfilled, nothing follows'
|
| 43 |
+
RESET_CONTENT = 205, 'Reset Content', 'Clear input form for further input'
|
| 44 |
+
PARTIAL_CONTENT = 206, 'Partial Content', 'Partial content follows'
|
| 45 |
+
MULTI_STATUS = 207, 'Multi-Status'
|
| 46 |
+
ALREADY_REPORTED = 208, 'Already Reported'
|
| 47 |
+
IM_USED = 226, 'IM Used'
|
| 48 |
+
|
| 49 |
+
# redirection
|
| 50 |
+
MULTIPLE_CHOICES = (300, 'Multiple Choices',
|
| 51 |
+
'Object has several resources -- see URI list')
|
| 52 |
+
MOVED_PERMANENTLY = (301, 'Moved Permanently',
|
| 53 |
+
'Object moved permanently -- see URI list')
|
| 54 |
+
FOUND = 302, 'Found', 'Object moved temporarily -- see URI list'
|
| 55 |
+
SEE_OTHER = 303, 'See Other', 'Object moved -- see Method and URL list'
|
| 56 |
+
NOT_MODIFIED = (304, 'Not Modified',
|
| 57 |
+
'Document has not changed since given time')
|
| 58 |
+
USE_PROXY = (305, 'Use Proxy',
|
| 59 |
+
'You must use proxy specified in Location to access this resource')
|
| 60 |
+
TEMPORARY_REDIRECT = (307, 'Temporary Redirect',
|
| 61 |
+
'Object moved temporarily -- see URI list')
|
| 62 |
+
PERMANENT_REDIRECT = (308, 'Permanent Redirect',
|
| 63 |
+
'Object moved permanently -- see URI list')
|
| 64 |
+
|
| 65 |
+
# client error
|
| 66 |
+
BAD_REQUEST = (400, 'Bad Request',
|
| 67 |
+
'Bad request syntax or unsupported method')
|
| 68 |
+
UNAUTHORIZED = (401, 'Unauthorized',
|
| 69 |
+
'No permission -- see authorization schemes')
|
| 70 |
+
PAYMENT_REQUIRED = (402, 'Payment Required',
|
| 71 |
+
'No payment -- see charging schemes')
|
| 72 |
+
FORBIDDEN = (403, 'Forbidden',
|
| 73 |
+
'Request forbidden -- authorization will not help')
|
| 74 |
+
NOT_FOUND = (404, 'Not Found',
|
| 75 |
+
'Nothing matches the given URI')
|
| 76 |
+
METHOD_NOT_ALLOWED = (405, 'Method Not Allowed',
|
| 77 |
+
'Specified method is invalid for this resource')
|
| 78 |
+
NOT_ACCEPTABLE = (406, 'Not Acceptable',
|
| 79 |
+
'URI not available in preferred format')
|
| 80 |
+
PROXY_AUTHENTICATION_REQUIRED = (407,
|
| 81 |
+
'Proxy Authentication Required',
|
| 82 |
+
'You must authenticate with this proxy before proceeding')
|
| 83 |
+
REQUEST_TIMEOUT = (408, 'Request Timeout',
|
| 84 |
+
'Request timed out; try again later')
|
| 85 |
+
CONFLICT = 409, 'Conflict', 'Request conflict'
|
| 86 |
+
GONE = (410, 'Gone',
|
| 87 |
+
'URI no longer exists and has been permanently removed')
|
| 88 |
+
LENGTH_REQUIRED = (411, 'Length Required',
|
| 89 |
+
'Client must specify Content-Length')
|
| 90 |
+
PRECONDITION_FAILED = (412, 'Precondition Failed',
|
| 91 |
+
'Precondition in headers is false')
|
| 92 |
+
REQUEST_ENTITY_TOO_LARGE = (413, 'Request Entity Too Large',
|
| 93 |
+
'Entity is too large')
|
| 94 |
+
REQUEST_URI_TOO_LONG = (414, 'Request-URI Too Long',
|
| 95 |
+
'URI is too long')
|
| 96 |
+
UNSUPPORTED_MEDIA_TYPE = (415, 'Unsupported Media Type',
|
| 97 |
+
'Entity body in unsupported format')
|
| 98 |
+
REQUESTED_RANGE_NOT_SATISFIABLE = (416,
|
| 99 |
+
'Requested Range Not Satisfiable',
|
| 100 |
+
'Cannot satisfy request range')
|
| 101 |
+
EXPECTATION_FAILED = (417, 'Expectation Failed',
|
| 102 |
+
'Expect condition could not be satisfied')
|
| 103 |
+
MISDIRECTED_REQUEST = (421, 'Misdirected Request',
|
| 104 |
+
'Server is not able to produce a response')
|
| 105 |
+
UNPROCESSABLE_ENTITY = 422, 'Unprocessable Entity'
|
| 106 |
+
LOCKED = 423, 'Locked'
|
| 107 |
+
FAILED_DEPENDENCY = 424, 'Failed Dependency'
|
| 108 |
+
UPGRADE_REQUIRED = 426, 'Upgrade Required'
|
| 109 |
+
PRECONDITION_REQUIRED = (428, 'Precondition Required',
|
| 110 |
+
'The origin server requires the request to be conditional')
|
| 111 |
+
TOO_MANY_REQUESTS = (429, 'Too Many Requests',
|
| 112 |
+
'The user has sent too many requests in '
|
| 113 |
+
'a given amount of time ("rate limiting")')
|
| 114 |
+
REQUEST_HEADER_FIELDS_TOO_LARGE = (431,
|
| 115 |
+
'Request Header Fields Too Large',
|
| 116 |
+
'The server is unwilling to process the request because its header '
|
| 117 |
+
'fields are too large')
|
| 118 |
+
UNAVAILABLE_FOR_LEGAL_REASONS = (451,
|
| 119 |
+
'Unavailable For Legal Reasons',
|
| 120 |
+
'The server is denying access to the '
|
| 121 |
+
'resource as a consequence of a legal demand')
|
| 122 |
+
|
| 123 |
+
# server errors
|
| 124 |
+
INTERNAL_SERVER_ERROR = (500, 'Internal Server Error',
|
| 125 |
+
'Server got itself in trouble')
|
| 126 |
+
NOT_IMPLEMENTED = (501, 'Not Implemented',
|
| 127 |
+
'Server does not support this operation')
|
| 128 |
+
BAD_GATEWAY = (502, 'Bad Gateway',
|
| 129 |
+
'Invalid responses from another server/proxy')
|
| 130 |
+
SERVICE_UNAVAILABLE = (503, 'Service Unavailable',
|
| 131 |
+
'The server cannot process the request due to a high load')
|
| 132 |
+
GATEWAY_TIMEOUT = (504, 'Gateway Timeout',
|
| 133 |
+
'The gateway server did not receive a timely response')
|
| 134 |
+
HTTP_VERSION_NOT_SUPPORTED = (505, 'HTTP Version Not Supported',
|
| 135 |
+
'Cannot fulfill request')
|
| 136 |
+
VARIANT_ALSO_NEGOTIATES = 506, 'Variant Also Negotiates'
|
| 137 |
+
INSUFFICIENT_STORAGE = 507, 'Insufficient Storage'
|
| 138 |
+
LOOP_DETECTED = 508, 'Loop Detected'
|
| 139 |
+
NOT_EXTENDED = 510, 'Not Extended'
|
| 140 |
+
NETWORK_AUTHENTICATION_REQUIRED = (511,
|
| 141 |
+
'Network Authentication Required',
|
| 142 |
+
'The client needs to authenticate to gain network access')
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/http/client.py
ADDED
|
@@ -0,0 +1,1496 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
r"""HTTP/1.1 client library
|
| 2 |
+
|
| 3 |
+
<intro stuff goes here>
|
| 4 |
+
<other stuff, too>
|
| 5 |
+
|
| 6 |
+
HTTPConnection goes through a number of "states", which define when a client
|
| 7 |
+
may legally make another request or fetch the response for a particular
|
| 8 |
+
request. This diagram details these state transitions:
|
| 9 |
+
|
| 10 |
+
(null)
|
| 11 |
+
|
|
| 12 |
+
| HTTPConnection()
|
| 13 |
+
v
|
| 14 |
+
Idle
|
| 15 |
+
|
|
| 16 |
+
| putrequest()
|
| 17 |
+
v
|
| 18 |
+
Request-started
|
| 19 |
+
|
|
| 20 |
+
| ( putheader() )* endheaders()
|
| 21 |
+
v
|
| 22 |
+
Request-sent
|
| 23 |
+
|\_____________________________
|
| 24 |
+
| | getresponse() raises
|
| 25 |
+
| response = getresponse() | ConnectionError
|
| 26 |
+
v v
|
| 27 |
+
Unread-response Idle
|
| 28 |
+
[Response-headers-read]
|
| 29 |
+
|\____________________
|
| 30 |
+
| |
|
| 31 |
+
| response.read() | putrequest()
|
| 32 |
+
v v
|
| 33 |
+
Idle Req-started-unread-response
|
| 34 |
+
______/|
|
| 35 |
+
/ |
|
| 36 |
+
response.read() | | ( putheader() )* endheaders()
|
| 37 |
+
v v
|
| 38 |
+
Request-started Req-sent-unread-response
|
| 39 |
+
|
|
| 40 |
+
| response.read()
|
| 41 |
+
v
|
| 42 |
+
Request-sent
|
| 43 |
+
|
| 44 |
+
This diagram presents the following rules:
|
| 45 |
+
-- a second request may not be started until {response-headers-read}
|
| 46 |
+
-- a response [object] cannot be retrieved until {request-sent}
|
| 47 |
+
-- there is no differentiation between an unread response body and a
|
| 48 |
+
partially read response body
|
| 49 |
+
|
| 50 |
+
Note: this enforcement is applied by the HTTPConnection class. The
|
| 51 |
+
HTTPResponse class does not enforce this state machine, which
|
| 52 |
+
implies sophisticated clients may accelerate the request/response
|
| 53 |
+
pipeline. Caution should be taken, though: accelerating the states
|
| 54 |
+
beyond the above pattern may imply knowledge of the server's
|
| 55 |
+
connection-close behavior for certain requests. For example, it
|
| 56 |
+
is impossible to tell whether the server will close the connection
|
| 57 |
+
UNTIL the response headers have been read; this means that further
|
| 58 |
+
requests cannot be placed into the pipeline until it is known that
|
| 59 |
+
the server will NOT be closing the connection.
|
| 60 |
+
|
| 61 |
+
Logical State __state __response
|
| 62 |
+
------------- ------- ----------
|
| 63 |
+
Idle _CS_IDLE None
|
| 64 |
+
Request-started _CS_REQ_STARTED None
|
| 65 |
+
Request-sent _CS_REQ_SENT None
|
| 66 |
+
Unread-response _CS_IDLE <response_class>
|
| 67 |
+
Req-started-unread-response _CS_REQ_STARTED <response_class>
|
| 68 |
+
Req-sent-unread-response _CS_REQ_SENT <response_class>
|
| 69 |
+
"""
|
| 70 |
+
|
| 71 |
+
import email.parser
|
| 72 |
+
import email.message
|
| 73 |
+
import http
|
| 74 |
+
import io
|
| 75 |
+
import re
|
| 76 |
+
import socket
|
| 77 |
+
import collections.abc
|
| 78 |
+
from urllib.parse import urlsplit
|
| 79 |
+
|
| 80 |
+
# HTTPMessage, parse_headers(), and the HTTP status code constants are
|
| 81 |
+
# intentionally omitted for simplicity
|
| 82 |
+
__all__ = ["HTTPResponse", "HTTPConnection",
|
| 83 |
+
"HTTPException", "NotConnected", "UnknownProtocol",
|
| 84 |
+
"UnknownTransferEncoding", "UnimplementedFileMode",
|
| 85 |
+
"IncompleteRead", "InvalidURL", "ImproperConnectionState",
|
| 86 |
+
"CannotSendRequest", "CannotSendHeader", "ResponseNotReady",
|
| 87 |
+
"BadStatusLine", "LineTooLong", "RemoteDisconnected", "error",
|
| 88 |
+
"responses"]
|
| 89 |
+
|
| 90 |
+
HTTP_PORT = 80
|
| 91 |
+
HTTPS_PORT = 443
|
| 92 |
+
|
| 93 |
+
_UNKNOWN = 'UNKNOWN'
|
| 94 |
+
|
| 95 |
+
# connection states
|
| 96 |
+
_CS_IDLE = 'Idle'
|
| 97 |
+
_CS_REQ_STARTED = 'Request-started'
|
| 98 |
+
_CS_REQ_SENT = 'Request-sent'
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
# hack to maintain backwards compatibility
|
| 102 |
+
globals().update(http.HTTPStatus.__members__)
|
| 103 |
+
|
| 104 |
+
# another hack to maintain backwards compatibility
|
| 105 |
+
# Mapping status codes to official W3C names
|
| 106 |
+
responses = {v: v.phrase for v in http.HTTPStatus.__members__.values()}
|
| 107 |
+
|
| 108 |
+
# maximal line length when calling readline().
|
| 109 |
+
_MAXLINE = 65536
|
| 110 |
+
_MAXHEADERS = 100
|
| 111 |
+
|
| 112 |
+
# Header name/value ABNF (http://tools.ietf.org/html/rfc7230#section-3.2)
|
| 113 |
+
#
|
| 114 |
+
# VCHAR = %x21-7E
|
| 115 |
+
# obs-text = %x80-FF
|
| 116 |
+
# header-field = field-name ":" OWS field-value OWS
|
| 117 |
+
# field-name = token
|
| 118 |
+
# field-value = *( field-content / obs-fold )
|
| 119 |
+
# field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
|
| 120 |
+
# field-vchar = VCHAR / obs-text
|
| 121 |
+
#
|
| 122 |
+
# obs-fold = CRLF 1*( SP / HTAB )
|
| 123 |
+
# ; obsolete line folding
|
| 124 |
+
# ; see Section 3.2.4
|
| 125 |
+
|
| 126 |
+
# token = 1*tchar
|
| 127 |
+
#
|
| 128 |
+
# tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*"
|
| 129 |
+
# / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
|
| 130 |
+
# / DIGIT / ALPHA
|
| 131 |
+
# ; any VCHAR, except delimiters
|
| 132 |
+
#
|
| 133 |
+
# VCHAR defined in http://tools.ietf.org/html/rfc5234#appendix-B.1
|
| 134 |
+
|
| 135 |
+
# the patterns for both name and value are more lenient than RFC
|
| 136 |
+
# definitions to allow for backwards compatibility
|
| 137 |
+
_is_legal_header_name = re.compile(rb'[^:\s][^:\r\n]*').fullmatch
|
| 138 |
+
_is_illegal_header_value = re.compile(rb'\n(?![ \t])|\r(?![ \t\n])').search
|
| 139 |
+
|
| 140 |
+
# These characters are not allowed within HTTP URL paths.
|
| 141 |
+
# See https://tools.ietf.org/html/rfc3986#section-3.3 and the
|
| 142 |
+
# https://tools.ietf.org/html/rfc3986#appendix-A pchar definition.
|
| 143 |
+
# Prevents CVE-2019-9740. Includes control characters such as \r\n.
|
| 144 |
+
# We don't restrict chars above \x7f as putrequest() limits us to ASCII.
|
| 145 |
+
_contains_disallowed_url_pchar_re = re.compile('[\x00-\x20\x7f]')
|
| 146 |
+
# Arguably only these _should_ allowed:
|
| 147 |
+
# _is_allowed_url_pchars_re = re.compile(r"^[/!$&'()*+,;=:@%a-zA-Z0-9._~-]+$")
|
| 148 |
+
# We are more lenient for assumed real world compatibility purposes.
|
| 149 |
+
|
| 150 |
+
# These characters are not allowed within HTTP method names
|
| 151 |
+
# to prevent http header injection.
|
| 152 |
+
_contains_disallowed_method_pchar_re = re.compile('[\x00-\x1f]')
|
| 153 |
+
|
| 154 |
+
# We always set the Content-Length header for these methods because some
|
| 155 |
+
# servers will otherwise respond with a 411
|
| 156 |
+
_METHODS_EXPECTING_BODY = {'PATCH', 'POST', 'PUT'}
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
def _encode(data, name='data'):
|
| 160 |
+
"""Call data.encode("latin-1") but show a better error message."""
|
| 161 |
+
try:
|
| 162 |
+
return data.encode("latin-1")
|
| 163 |
+
except UnicodeEncodeError as err:
|
| 164 |
+
raise UnicodeEncodeError(
|
| 165 |
+
err.encoding,
|
| 166 |
+
err.object,
|
| 167 |
+
err.start,
|
| 168 |
+
err.end,
|
| 169 |
+
"%s (%.20r) is not valid Latin-1. Use %s.encode('utf-8') "
|
| 170 |
+
"if you want to send it encoded in UTF-8." %
|
| 171 |
+
(name.title(), data[err.start:err.end], name)) from None
|
| 172 |
+
|
| 173 |
+
|
| 174 |
+
class HTTPMessage(email.message.Message):
|
| 175 |
+
# XXX The only usage of this method is in
|
| 176 |
+
# http.server.CGIHTTPRequestHandler. Maybe move the code there so
|
| 177 |
+
# that it doesn't need to be part of the public API. The API has
|
| 178 |
+
# never been defined so this could cause backwards compatibility
|
| 179 |
+
# issues.
|
| 180 |
+
|
| 181 |
+
def getallmatchingheaders(self, name):
|
| 182 |
+
"""Find all header lines matching a given header name.
|
| 183 |
+
|
| 184 |
+
Look through the list of headers and find all lines matching a given
|
| 185 |
+
header name (and their continuation lines). A list of the lines is
|
| 186 |
+
returned, without interpretation. If the header does not occur, an
|
| 187 |
+
empty list is returned. If the header occurs multiple times, all
|
| 188 |
+
occurrences are returned. Case is not important in the header name.
|
| 189 |
+
|
| 190 |
+
"""
|
| 191 |
+
name = name.lower() + ':'
|
| 192 |
+
n = len(name)
|
| 193 |
+
lst = []
|
| 194 |
+
hit = 0
|
| 195 |
+
for line in self.keys():
|
| 196 |
+
if line[:n].lower() == name:
|
| 197 |
+
hit = 1
|
| 198 |
+
elif not line[:1].isspace():
|
| 199 |
+
hit = 0
|
| 200 |
+
if hit:
|
| 201 |
+
lst.append(line)
|
| 202 |
+
return lst
|
| 203 |
+
|
| 204 |
+
def _read_headers(fp):
|
| 205 |
+
"""Reads potential header lines into a list from a file pointer.
|
| 206 |
+
|
| 207 |
+
Length of line is limited by _MAXLINE, and number of
|
| 208 |
+
headers is limited by _MAXHEADERS.
|
| 209 |
+
"""
|
| 210 |
+
headers = []
|
| 211 |
+
while True:
|
| 212 |
+
line = fp.readline(_MAXLINE + 1)
|
| 213 |
+
if len(line) > _MAXLINE:
|
| 214 |
+
raise LineTooLong("header line")
|
| 215 |
+
headers.append(line)
|
| 216 |
+
if len(headers) > _MAXHEADERS:
|
| 217 |
+
raise HTTPException("got more than %d headers" % _MAXHEADERS)
|
| 218 |
+
if line in (b'\r\n', b'\n', b''):
|
| 219 |
+
break
|
| 220 |
+
return headers
|
| 221 |
+
|
| 222 |
+
def parse_headers(fp, _class=HTTPMessage):
|
| 223 |
+
"""Parses only RFC2822 headers from a file pointer.
|
| 224 |
+
|
| 225 |
+
email Parser wants to see strings rather than bytes.
|
| 226 |
+
But a TextIOWrapper around self.rfile would buffer too many bytes
|
| 227 |
+
from the stream, bytes which we later need to read as bytes.
|
| 228 |
+
So we read the correct bytes here, as bytes, for email Parser
|
| 229 |
+
to parse.
|
| 230 |
+
|
| 231 |
+
"""
|
| 232 |
+
headers = _read_headers(fp)
|
| 233 |
+
hstring = b''.join(headers).decode('iso-8859-1')
|
| 234 |
+
return email.parser.Parser(_class=_class).parsestr(hstring)
|
| 235 |
+
|
| 236 |
+
|
| 237 |
+
class HTTPResponse(io.BufferedIOBase):
|
| 238 |
+
|
| 239 |
+
# See RFC 2616 sec 19.6 and RFC 1945 sec 6 for details.
|
| 240 |
+
|
| 241 |
+
# The bytes from the socket object are iso-8859-1 strings.
|
| 242 |
+
# See RFC 2616 sec 2.2 which notes an exception for MIME-encoded
|
| 243 |
+
# text following RFC 2047. The basic status line parsing only
|
| 244 |
+
# accepts iso-8859-1.
|
| 245 |
+
|
| 246 |
+
def __init__(self, sock, debuglevel=0, method=None, url=None):
|
| 247 |
+
# If the response includes a content-length header, we need to
|
| 248 |
+
# make sure that the client doesn't read more than the
|
| 249 |
+
# specified number of bytes. If it does, it will block until
|
| 250 |
+
# the server times out and closes the connection. This will
|
| 251 |
+
# happen if a self.fp.read() is done (without a size) whether
|
| 252 |
+
# self.fp is buffered or not. So, no self.fp.read() by
|
| 253 |
+
# clients unless they know what they are doing.
|
| 254 |
+
self.fp = sock.makefile("rb")
|
| 255 |
+
self.debuglevel = debuglevel
|
| 256 |
+
self._method = method
|
| 257 |
+
|
| 258 |
+
# The HTTPResponse object is returned via urllib. The clients
|
| 259 |
+
# of http and urllib expect different attributes for the
|
| 260 |
+
# headers. headers is used here and supports urllib. msg is
|
| 261 |
+
# provided as a backwards compatibility layer for http
|
| 262 |
+
# clients.
|
| 263 |
+
|
| 264 |
+
self.headers = self.msg = None
|
| 265 |
+
|
| 266 |
+
# from the Status-Line of the response
|
| 267 |
+
self.version = _UNKNOWN # HTTP-Version
|
| 268 |
+
self.status = _UNKNOWN # Status-Code
|
| 269 |
+
self.reason = _UNKNOWN # Reason-Phrase
|
| 270 |
+
|
| 271 |
+
self.chunked = _UNKNOWN # is "chunked" being used?
|
| 272 |
+
self.chunk_left = _UNKNOWN # bytes left to read in current chunk
|
| 273 |
+
self.length = _UNKNOWN # number of bytes left in response
|
| 274 |
+
self.will_close = _UNKNOWN # conn will close at end of response
|
| 275 |
+
|
| 276 |
+
def _read_status(self):
|
| 277 |
+
line = str(self.fp.readline(_MAXLINE + 1), "iso-8859-1")
|
| 278 |
+
if len(line) > _MAXLINE:
|
| 279 |
+
raise LineTooLong("status line")
|
| 280 |
+
if self.debuglevel > 0:
|
| 281 |
+
print("reply:", repr(line))
|
| 282 |
+
if not line:
|
| 283 |
+
# Presumably, the server closed the connection before
|
| 284 |
+
# sending a valid response.
|
| 285 |
+
raise RemoteDisconnected("Remote end closed connection without"
|
| 286 |
+
" response")
|
| 287 |
+
try:
|
| 288 |
+
version, status, reason = line.split(None, 2)
|
| 289 |
+
except ValueError:
|
| 290 |
+
try:
|
| 291 |
+
version, status = line.split(None, 1)
|
| 292 |
+
reason = ""
|
| 293 |
+
except ValueError:
|
| 294 |
+
# empty version will cause next test to fail.
|
| 295 |
+
version = ""
|
| 296 |
+
if not version.startswith("HTTP/"):
|
| 297 |
+
self._close_conn()
|
| 298 |
+
raise BadStatusLine(line)
|
| 299 |
+
|
| 300 |
+
# The status code is a three-digit number
|
| 301 |
+
try:
|
| 302 |
+
status = int(status)
|
| 303 |
+
if status < 100 or status > 999:
|
| 304 |
+
raise BadStatusLine(line)
|
| 305 |
+
except ValueError:
|
| 306 |
+
raise BadStatusLine(line)
|
| 307 |
+
return version, status, reason
|
| 308 |
+
|
| 309 |
+
def begin(self):
|
| 310 |
+
if self.headers is not None:
|
| 311 |
+
# we've already started reading the response
|
| 312 |
+
return
|
| 313 |
+
|
| 314 |
+
# read until we get a non-100 response
|
| 315 |
+
while True:
|
| 316 |
+
version, status, reason = self._read_status()
|
| 317 |
+
if status != CONTINUE:
|
| 318 |
+
break
|
| 319 |
+
# skip the header from the 100 response
|
| 320 |
+
skipped_headers = _read_headers(self.fp)
|
| 321 |
+
if self.debuglevel > 0:
|
| 322 |
+
print("headers:", skipped_headers)
|
| 323 |
+
del skipped_headers
|
| 324 |
+
|
| 325 |
+
self.code = self.status = status
|
| 326 |
+
self.reason = reason.strip()
|
| 327 |
+
if version in ("HTTP/1.0", "HTTP/0.9"):
|
| 328 |
+
# Some servers might still return "0.9", treat it as 1.0 anyway
|
| 329 |
+
self.version = 10
|
| 330 |
+
elif version.startswith("HTTP/1."):
|
| 331 |
+
self.version = 11 # use HTTP/1.1 code for HTTP/1.x where x>=1
|
| 332 |
+
else:
|
| 333 |
+
raise UnknownProtocol(version)
|
| 334 |
+
|
| 335 |
+
self.headers = self.msg = parse_headers(self.fp)
|
| 336 |
+
|
| 337 |
+
if self.debuglevel > 0:
|
| 338 |
+
for hdr, val in self.headers.items():
|
| 339 |
+
print("header:", hdr + ":", val)
|
| 340 |
+
|
| 341 |
+
# are we using the chunked-style of transfer encoding?
|
| 342 |
+
tr_enc = self.headers.get("transfer-encoding")
|
| 343 |
+
if tr_enc and tr_enc.lower() == "chunked":
|
| 344 |
+
self.chunked = True
|
| 345 |
+
self.chunk_left = None
|
| 346 |
+
else:
|
| 347 |
+
self.chunked = False
|
| 348 |
+
|
| 349 |
+
# will the connection close at the end of the response?
|
| 350 |
+
self.will_close = self._check_close()
|
| 351 |
+
|
| 352 |
+
# do we have a Content-Length?
|
| 353 |
+
# NOTE: RFC 2616, S4.4, #3 says we ignore this if tr_enc is "chunked"
|
| 354 |
+
self.length = None
|
| 355 |
+
length = self.headers.get("content-length")
|
| 356 |
+
if length and not self.chunked:
|
| 357 |
+
try:
|
| 358 |
+
self.length = int(length)
|
| 359 |
+
except ValueError:
|
| 360 |
+
self.length = None
|
| 361 |
+
else:
|
| 362 |
+
if self.length < 0: # ignore nonsensical negative lengths
|
| 363 |
+
self.length = None
|
| 364 |
+
else:
|
| 365 |
+
self.length = None
|
| 366 |
+
|
| 367 |
+
# does the body have a fixed length? (of zero)
|
| 368 |
+
if (status == NO_CONTENT or status == NOT_MODIFIED or
|
| 369 |
+
100 <= status < 200 or # 1xx codes
|
| 370 |
+
self._method == "HEAD"):
|
| 371 |
+
self.length = 0
|
| 372 |
+
|
| 373 |
+
# if the connection remains open, and we aren't using chunked, and
|
| 374 |
+
# a content-length was not provided, then assume that the connection
|
| 375 |
+
# WILL close.
|
| 376 |
+
if (not self.will_close and
|
| 377 |
+
not self.chunked and
|
| 378 |
+
self.length is None):
|
| 379 |
+
self.will_close = True
|
| 380 |
+
|
| 381 |
+
def _check_close(self):
|
| 382 |
+
conn = self.headers.get("connection")
|
| 383 |
+
if self.version == 11:
|
| 384 |
+
# An HTTP/1.1 proxy is assumed to stay open unless
|
| 385 |
+
# explicitly closed.
|
| 386 |
+
if conn and "close" in conn.lower():
|
| 387 |
+
return True
|
| 388 |
+
return False
|
| 389 |
+
|
| 390 |
+
# Some HTTP/1.0 implementations have support for persistent
|
| 391 |
+
# connections, using rules different than HTTP/1.1.
|
| 392 |
+
|
| 393 |
+
# For older HTTP, Keep-Alive indicates persistent connection.
|
| 394 |
+
if self.headers.get("keep-alive"):
|
| 395 |
+
return False
|
| 396 |
+
|
| 397 |
+
# At least Akamai returns a "Connection: Keep-Alive" header,
|
| 398 |
+
# which was supposed to be sent by the client.
|
| 399 |
+
if conn and "keep-alive" in conn.lower():
|
| 400 |
+
return False
|
| 401 |
+
|
| 402 |
+
# Proxy-Connection is a netscape hack.
|
| 403 |
+
pconn = self.headers.get("proxy-connection")
|
| 404 |
+
if pconn and "keep-alive" in pconn.lower():
|
| 405 |
+
return False
|
| 406 |
+
|
| 407 |
+
# otherwise, assume it will close
|
| 408 |
+
return True
|
| 409 |
+
|
| 410 |
+
def _close_conn(self):
|
| 411 |
+
fp = self.fp
|
| 412 |
+
self.fp = None
|
| 413 |
+
fp.close()
|
| 414 |
+
|
| 415 |
+
def close(self):
|
| 416 |
+
try:
|
| 417 |
+
super().close() # set "closed" flag
|
| 418 |
+
finally:
|
| 419 |
+
if self.fp:
|
| 420 |
+
self._close_conn()
|
| 421 |
+
|
| 422 |
+
# These implementations are for the benefit of io.BufferedReader.
|
| 423 |
+
|
| 424 |
+
# XXX This class should probably be revised to act more like
|
| 425 |
+
# the "raw stream" that BufferedReader expects.
|
| 426 |
+
|
| 427 |
+
def flush(self):
|
| 428 |
+
super().flush()
|
| 429 |
+
if self.fp:
|
| 430 |
+
self.fp.flush()
|
| 431 |
+
|
| 432 |
+
def readable(self):
|
| 433 |
+
"""Always returns True"""
|
| 434 |
+
return True
|
| 435 |
+
|
| 436 |
+
# End of "raw stream" methods
|
| 437 |
+
|
| 438 |
+
def isclosed(self):
|
| 439 |
+
"""True if the connection is closed."""
|
| 440 |
+
# NOTE: it is possible that we will not ever call self.close(). This
|
| 441 |
+
# case occurs when will_close is TRUE, length is None, and we
|
| 442 |
+
# read up to the last byte, but NOT past it.
|
| 443 |
+
#
|
| 444 |
+
# IMPLIES: if will_close is FALSE, then self.close() will ALWAYS be
|
| 445 |
+
# called, meaning self.isclosed() is meaningful.
|
| 446 |
+
return self.fp is None
|
| 447 |
+
|
| 448 |
+
def read(self, amt=None):
|
| 449 |
+
if self.fp is None:
|
| 450 |
+
return b""
|
| 451 |
+
|
| 452 |
+
if self._method == "HEAD":
|
| 453 |
+
self._close_conn()
|
| 454 |
+
return b""
|
| 455 |
+
|
| 456 |
+
if amt is not None:
|
| 457 |
+
# Amount is given, implement using readinto
|
| 458 |
+
b = bytearray(amt)
|
| 459 |
+
n = self.readinto(b)
|
| 460 |
+
return memoryview(b)[:n].tobytes()
|
| 461 |
+
else:
|
| 462 |
+
# Amount is not given (unbounded read) so we must check self.length
|
| 463 |
+
# and self.chunked
|
| 464 |
+
|
| 465 |
+
if self.chunked:
|
| 466 |
+
return self._readall_chunked()
|
| 467 |
+
|
| 468 |
+
if self.length is None:
|
| 469 |
+
s = self.fp.read()
|
| 470 |
+
else:
|
| 471 |
+
try:
|
| 472 |
+
s = self._safe_read(self.length)
|
| 473 |
+
except IncompleteRead:
|
| 474 |
+
self._close_conn()
|
| 475 |
+
raise
|
| 476 |
+
self.length = 0
|
| 477 |
+
self._close_conn() # we read everything
|
| 478 |
+
return s
|
| 479 |
+
|
| 480 |
+
def readinto(self, b):
|
| 481 |
+
"""Read up to len(b) bytes into bytearray b and return the number
|
| 482 |
+
of bytes read.
|
| 483 |
+
"""
|
| 484 |
+
|
| 485 |
+
if self.fp is None:
|
| 486 |
+
return 0
|
| 487 |
+
|
| 488 |
+
if self._method == "HEAD":
|
| 489 |
+
self._close_conn()
|
| 490 |
+
return 0
|
| 491 |
+
|
| 492 |
+
if self.chunked:
|
| 493 |
+
return self._readinto_chunked(b)
|
| 494 |
+
|
| 495 |
+
if self.length is not None:
|
| 496 |
+
if len(b) > self.length:
|
| 497 |
+
# clip the read to the "end of response"
|
| 498 |
+
b = memoryview(b)[0:self.length]
|
| 499 |
+
|
| 500 |
+
# we do not use _safe_read() here because this may be a .will_close
|
| 501 |
+
# connection, and the user is reading more bytes than will be provided
|
| 502 |
+
# (for example, reading in 1k chunks)
|
| 503 |
+
n = self.fp.readinto(b)
|
| 504 |
+
if not n and b:
|
| 505 |
+
# Ideally, we would raise IncompleteRead if the content-length
|
| 506 |
+
# wasn't satisfied, but it might break compatibility.
|
| 507 |
+
self._close_conn()
|
| 508 |
+
elif self.length is not None:
|
| 509 |
+
self.length -= n
|
| 510 |
+
if not self.length:
|
| 511 |
+
self._close_conn()
|
| 512 |
+
return n
|
| 513 |
+
|
| 514 |
+
def _read_next_chunk_size(self):
|
| 515 |
+
# Read the next chunk size from the file
|
| 516 |
+
line = self.fp.readline(_MAXLINE + 1)
|
| 517 |
+
if len(line) > _MAXLINE:
|
| 518 |
+
raise LineTooLong("chunk size")
|
| 519 |
+
i = line.find(b";")
|
| 520 |
+
if i >= 0:
|
| 521 |
+
line = line[:i] # strip chunk-extensions
|
| 522 |
+
try:
|
| 523 |
+
return int(line, 16)
|
| 524 |
+
except ValueError:
|
| 525 |
+
# close the connection as protocol synchronisation is
|
| 526 |
+
# probably lost
|
| 527 |
+
self._close_conn()
|
| 528 |
+
raise
|
| 529 |
+
|
| 530 |
+
def _read_and_discard_trailer(self):
|
| 531 |
+
# read and discard trailer up to the CRLF terminator
|
| 532 |
+
### note: we shouldn't have any trailers!
|
| 533 |
+
while True:
|
| 534 |
+
line = self.fp.readline(_MAXLINE + 1)
|
| 535 |
+
if len(line) > _MAXLINE:
|
| 536 |
+
raise LineTooLong("trailer line")
|
| 537 |
+
if not line:
|
| 538 |
+
# a vanishingly small number of sites EOF without
|
| 539 |
+
# sending the trailer
|
| 540 |
+
break
|
| 541 |
+
if line in (b'\r\n', b'\n', b''):
|
| 542 |
+
break
|
| 543 |
+
|
| 544 |
+
def _get_chunk_left(self):
|
| 545 |
+
# return self.chunk_left, reading a new chunk if necessary.
|
| 546 |
+
# chunk_left == 0: at the end of the current chunk, need to close it
|
| 547 |
+
# chunk_left == None: No current chunk, should read next.
|
| 548 |
+
# This function returns non-zero or None if the last chunk has
|
| 549 |
+
# been read.
|
| 550 |
+
chunk_left = self.chunk_left
|
| 551 |
+
if not chunk_left: # Can be 0 or None
|
| 552 |
+
if chunk_left is not None:
|
| 553 |
+
# We are at the end of chunk, discard chunk end
|
| 554 |
+
self._safe_read(2) # toss the CRLF at the end of the chunk
|
| 555 |
+
try:
|
| 556 |
+
chunk_left = self._read_next_chunk_size()
|
| 557 |
+
except ValueError:
|
| 558 |
+
raise IncompleteRead(b'')
|
| 559 |
+
if chunk_left == 0:
|
| 560 |
+
# last chunk: 1*("0") [ chunk-extension ] CRLF
|
| 561 |
+
self._read_and_discard_trailer()
|
| 562 |
+
# we read everything; close the "file"
|
| 563 |
+
self._close_conn()
|
| 564 |
+
chunk_left = None
|
| 565 |
+
self.chunk_left = chunk_left
|
| 566 |
+
return chunk_left
|
| 567 |
+
|
| 568 |
+
def _readall_chunked(self):
|
| 569 |
+
assert self.chunked != _UNKNOWN
|
| 570 |
+
value = []
|
| 571 |
+
try:
|
| 572 |
+
while True:
|
| 573 |
+
chunk_left = self._get_chunk_left()
|
| 574 |
+
if chunk_left is None:
|
| 575 |
+
break
|
| 576 |
+
value.append(self._safe_read(chunk_left))
|
| 577 |
+
self.chunk_left = 0
|
| 578 |
+
return b''.join(value)
|
| 579 |
+
except IncompleteRead:
|
| 580 |
+
raise IncompleteRead(b''.join(value))
|
| 581 |
+
|
| 582 |
+
def _readinto_chunked(self, b):
|
| 583 |
+
assert self.chunked != _UNKNOWN
|
| 584 |
+
total_bytes = 0
|
| 585 |
+
mvb = memoryview(b)
|
| 586 |
+
try:
|
| 587 |
+
while True:
|
| 588 |
+
chunk_left = self._get_chunk_left()
|
| 589 |
+
if chunk_left is None:
|
| 590 |
+
return total_bytes
|
| 591 |
+
|
| 592 |
+
if len(mvb) <= chunk_left:
|
| 593 |
+
n = self._safe_readinto(mvb)
|
| 594 |
+
self.chunk_left = chunk_left - n
|
| 595 |
+
return total_bytes + n
|
| 596 |
+
|
| 597 |
+
temp_mvb = mvb[:chunk_left]
|
| 598 |
+
n = self._safe_readinto(temp_mvb)
|
| 599 |
+
mvb = mvb[n:]
|
| 600 |
+
total_bytes += n
|
| 601 |
+
self.chunk_left = 0
|
| 602 |
+
|
| 603 |
+
except IncompleteRead:
|
| 604 |
+
raise IncompleteRead(bytes(b[0:total_bytes]))
|
| 605 |
+
|
| 606 |
+
def _safe_read(self, amt):
|
| 607 |
+
"""Read the number of bytes requested.
|
| 608 |
+
|
| 609 |
+
This function should be used when <amt> bytes "should" be present for
|
| 610 |
+
reading. If the bytes are truly not available (due to EOF), then the
|
| 611 |
+
IncompleteRead exception can be used to detect the problem.
|
| 612 |
+
"""
|
| 613 |
+
data = self.fp.read(amt)
|
| 614 |
+
if len(data) < amt:
|
| 615 |
+
raise IncompleteRead(data, amt-len(data))
|
| 616 |
+
return data
|
| 617 |
+
|
| 618 |
+
def _safe_readinto(self, b):
|
| 619 |
+
"""Same as _safe_read, but for reading into a buffer."""
|
| 620 |
+
amt = len(b)
|
| 621 |
+
n = self.fp.readinto(b)
|
| 622 |
+
if n < amt:
|
| 623 |
+
raise IncompleteRead(bytes(b[:n]), amt-n)
|
| 624 |
+
return n
|
| 625 |
+
|
| 626 |
+
def read1(self, n=-1):
|
| 627 |
+
"""Read with at most one underlying system call. If at least one
|
| 628 |
+
byte is buffered, return that instead.
|
| 629 |
+
"""
|
| 630 |
+
if self.fp is None or self._method == "HEAD":
|
| 631 |
+
return b""
|
| 632 |
+
if self.chunked:
|
| 633 |
+
return self._read1_chunked(n)
|
| 634 |
+
if self.length is not None and (n < 0 or n > self.length):
|
| 635 |
+
n = self.length
|
| 636 |
+
result = self.fp.read1(n)
|
| 637 |
+
if not result and n:
|
| 638 |
+
self._close_conn()
|
| 639 |
+
elif self.length is not None:
|
| 640 |
+
self.length -= len(result)
|
| 641 |
+
return result
|
| 642 |
+
|
| 643 |
+
def peek(self, n=-1):
|
| 644 |
+
# Having this enables IOBase.readline() to read more than one
|
| 645 |
+
# byte at a time
|
| 646 |
+
if self.fp is None or self._method == "HEAD":
|
| 647 |
+
return b""
|
| 648 |
+
if self.chunked:
|
| 649 |
+
return self._peek_chunked(n)
|
| 650 |
+
return self.fp.peek(n)
|
| 651 |
+
|
| 652 |
+
def readline(self, limit=-1):
|
| 653 |
+
if self.fp is None or self._method == "HEAD":
|
| 654 |
+
return b""
|
| 655 |
+
if self.chunked:
|
| 656 |
+
# Fallback to IOBase readline which uses peek() and read()
|
| 657 |
+
return super().readline(limit)
|
| 658 |
+
if self.length is not None and (limit < 0 or limit > self.length):
|
| 659 |
+
limit = self.length
|
| 660 |
+
result = self.fp.readline(limit)
|
| 661 |
+
if not result and limit:
|
| 662 |
+
self._close_conn()
|
| 663 |
+
elif self.length is not None:
|
| 664 |
+
self.length -= len(result)
|
| 665 |
+
return result
|
| 666 |
+
|
| 667 |
+
def _read1_chunked(self, n):
|
| 668 |
+
# Strictly speaking, _get_chunk_left() may cause more than one read,
|
| 669 |
+
# but that is ok, since that is to satisfy the chunked protocol.
|
| 670 |
+
chunk_left = self._get_chunk_left()
|
| 671 |
+
if chunk_left is None or n == 0:
|
| 672 |
+
return b''
|
| 673 |
+
if not (0 <= n <= chunk_left):
|
| 674 |
+
n = chunk_left # if n is negative or larger than chunk_left
|
| 675 |
+
read = self.fp.read1(n)
|
| 676 |
+
self.chunk_left -= len(read)
|
| 677 |
+
if not read:
|
| 678 |
+
raise IncompleteRead(b"")
|
| 679 |
+
return read
|
| 680 |
+
|
| 681 |
+
def _peek_chunked(self, n):
|
| 682 |
+
# Strictly speaking, _get_chunk_left() may cause more than one read,
|
| 683 |
+
# but that is ok, since that is to satisfy the chunked protocol.
|
| 684 |
+
try:
|
| 685 |
+
chunk_left = self._get_chunk_left()
|
| 686 |
+
except IncompleteRead:
|
| 687 |
+
return b'' # peek doesn't worry about protocol
|
| 688 |
+
if chunk_left is None:
|
| 689 |
+
return b'' # eof
|
| 690 |
+
# peek is allowed to return more than requested. Just request the
|
| 691 |
+
# entire chunk, and truncate what we get.
|
| 692 |
+
return self.fp.peek(chunk_left)[:chunk_left]
|
| 693 |
+
|
| 694 |
+
def fileno(self):
|
| 695 |
+
return self.fp.fileno()
|
| 696 |
+
|
| 697 |
+
def getheader(self, name, default=None):
|
| 698 |
+
'''Returns the value of the header matching *name*.
|
| 699 |
+
|
| 700 |
+
If there are multiple matching headers, the values are
|
| 701 |
+
combined into a single string separated by commas and spaces.
|
| 702 |
+
|
| 703 |
+
If no matching header is found, returns *default* or None if
|
| 704 |
+
the *default* is not specified.
|
| 705 |
+
|
| 706 |
+
If the headers are unknown, raises http.client.ResponseNotReady.
|
| 707 |
+
|
| 708 |
+
'''
|
| 709 |
+
if self.headers is None:
|
| 710 |
+
raise ResponseNotReady()
|
| 711 |
+
headers = self.headers.get_all(name) or default
|
| 712 |
+
if isinstance(headers, str) or not hasattr(headers, '__iter__'):
|
| 713 |
+
return headers
|
| 714 |
+
else:
|
| 715 |
+
return ', '.join(headers)
|
| 716 |
+
|
| 717 |
+
def getheaders(self):
|
| 718 |
+
"""Return list of (header, value) tuples."""
|
| 719 |
+
if self.headers is None:
|
| 720 |
+
raise ResponseNotReady()
|
| 721 |
+
return list(self.headers.items())
|
| 722 |
+
|
| 723 |
+
# We override IOBase.__iter__ so that it doesn't check for closed-ness
|
| 724 |
+
|
| 725 |
+
def __iter__(self):
|
| 726 |
+
return self
|
| 727 |
+
|
| 728 |
+
# For compatibility with old-style urllib responses.
|
| 729 |
+
|
| 730 |
+
def info(self):
|
| 731 |
+
'''Returns an instance of the class mimetools.Message containing
|
| 732 |
+
meta-information associated with the URL.
|
| 733 |
+
|
| 734 |
+
When the method is HTTP, these headers are those returned by
|
| 735 |
+
the server at the head of the retrieved HTML page (including
|
| 736 |
+
Content-Length and Content-Type).
|
| 737 |
+
|
| 738 |
+
When the method is FTP, a Content-Length header will be
|
| 739 |
+
present if (as is now usual) the server passed back a file
|
| 740 |
+
length in response to the FTP retrieval request. A
|
| 741 |
+
Content-Type header will be present if the MIME type can be
|
| 742 |
+
guessed.
|
| 743 |
+
|
| 744 |
+
When the method is local-file, returned headers will include
|
| 745 |
+
a Date representing the file's last-modified time, a
|
| 746 |
+
Content-Length giving file size, and a Content-Type
|
| 747 |
+
containing a guess at the file's type. See also the
|
| 748 |
+
description of the mimetools module.
|
| 749 |
+
|
| 750 |
+
'''
|
| 751 |
+
return self.headers
|
| 752 |
+
|
| 753 |
+
def geturl(self):
|
| 754 |
+
'''Return the real URL of the page.
|
| 755 |
+
|
| 756 |
+
In some cases, the HTTP server redirects a client to another
|
| 757 |
+
URL. The urlopen() function handles this transparently, but in
|
| 758 |
+
some cases the caller needs to know which URL the client was
|
| 759 |
+
redirected to. The geturl() method can be used to get at this
|
| 760 |
+
redirected URL.
|
| 761 |
+
|
| 762 |
+
'''
|
| 763 |
+
return self.url
|
| 764 |
+
|
| 765 |
+
def getcode(self):
|
| 766 |
+
'''Return the HTTP status code that was sent with the response,
|
| 767 |
+
or None if the URL is not an HTTP URL.
|
| 768 |
+
|
| 769 |
+
'''
|
| 770 |
+
return self.status
|
| 771 |
+
|
| 772 |
+
class HTTPConnection:
|
| 773 |
+
|
| 774 |
+
_http_vsn = 11
|
| 775 |
+
_http_vsn_str = 'HTTP/1.1'
|
| 776 |
+
|
| 777 |
+
response_class = HTTPResponse
|
| 778 |
+
default_port = HTTP_PORT
|
| 779 |
+
auto_open = 1
|
| 780 |
+
debuglevel = 0
|
| 781 |
+
|
| 782 |
+
@staticmethod
|
| 783 |
+
def _is_textIO(stream):
|
| 784 |
+
"""Test whether a file-like object is a text or a binary stream.
|
| 785 |
+
"""
|
| 786 |
+
return isinstance(stream, io.TextIOBase)
|
| 787 |
+
|
| 788 |
+
@staticmethod
|
| 789 |
+
def _get_content_length(body, method):
|
| 790 |
+
"""Get the content-length based on the body.
|
| 791 |
+
|
| 792 |
+
If the body is None, we set Content-Length: 0 for methods that expect
|
| 793 |
+
a body (RFC 7230, Section 3.3.2). We also set the Content-Length for
|
| 794 |
+
any method if the body is a str or bytes-like object and not a file.
|
| 795 |
+
"""
|
| 796 |
+
if body is None:
|
| 797 |
+
# do an explicit check for not None here to distinguish
|
| 798 |
+
# between unset and set but empty
|
| 799 |
+
if method.upper() in _METHODS_EXPECTING_BODY:
|
| 800 |
+
return 0
|
| 801 |
+
else:
|
| 802 |
+
return None
|
| 803 |
+
|
| 804 |
+
if hasattr(body, 'read'):
|
| 805 |
+
# file-like object.
|
| 806 |
+
return None
|
| 807 |
+
|
| 808 |
+
try:
|
| 809 |
+
# does it implement the buffer protocol (bytes, bytearray, array)?
|
| 810 |
+
mv = memoryview(body)
|
| 811 |
+
return mv.nbytes
|
| 812 |
+
except TypeError:
|
| 813 |
+
pass
|
| 814 |
+
|
| 815 |
+
if isinstance(body, str):
|
| 816 |
+
return len(body)
|
| 817 |
+
|
| 818 |
+
return None
|
| 819 |
+
|
| 820 |
+
def __init__(self, host, port=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
|
| 821 |
+
source_address=None, blocksize=8192):
|
| 822 |
+
self.timeout = timeout
|
| 823 |
+
self.source_address = source_address
|
| 824 |
+
self.blocksize = blocksize
|
| 825 |
+
self.sock = None
|
| 826 |
+
self._buffer = []
|
| 827 |
+
self.__response = None
|
| 828 |
+
self.__state = _CS_IDLE
|
| 829 |
+
self._method = None
|
| 830 |
+
self._tunnel_host = None
|
| 831 |
+
self._tunnel_port = None
|
| 832 |
+
self._tunnel_headers = {}
|
| 833 |
+
|
| 834 |
+
(self.host, self.port) = self._get_hostport(host, port)
|
| 835 |
+
|
| 836 |
+
self._validate_host(self.host)
|
| 837 |
+
|
| 838 |
+
# This is stored as an instance variable to allow unit
|
| 839 |
+
# tests to replace it with a suitable mockup
|
| 840 |
+
self._create_connection = socket.create_connection
|
| 841 |
+
|
| 842 |
+
def set_tunnel(self, host, port=None, headers=None):
|
| 843 |
+
"""Set up host and port for HTTP CONNECT tunnelling.
|
| 844 |
+
|
| 845 |
+
In a connection that uses HTTP CONNECT tunneling, the host passed to the
|
| 846 |
+
constructor is used as a proxy server that relays all communication to
|
| 847 |
+
the endpoint passed to `set_tunnel`. This done by sending an HTTP
|
| 848 |
+
CONNECT request to the proxy server when the connection is established.
|
| 849 |
+
|
| 850 |
+
This method must be called before the HTTP connection has been
|
| 851 |
+
established.
|
| 852 |
+
|
| 853 |
+
The headers argument should be a mapping of extra HTTP headers to send
|
| 854 |
+
with the CONNECT request.
|
| 855 |
+
"""
|
| 856 |
+
|
| 857 |
+
if self.sock:
|
| 858 |
+
raise RuntimeError("Can't set up tunnel for established connection")
|
| 859 |
+
|
| 860 |
+
self._tunnel_host, self._tunnel_port = self._get_hostport(host, port)
|
| 861 |
+
if headers:
|
| 862 |
+
self._tunnel_headers = headers
|
| 863 |
+
else:
|
| 864 |
+
self._tunnel_headers.clear()
|
| 865 |
+
|
| 866 |
+
def _get_hostport(self, host, port):
|
| 867 |
+
if port is None:
|
| 868 |
+
i = host.rfind(':')
|
| 869 |
+
j = host.rfind(']') # ipv6 addresses have [...]
|
| 870 |
+
if i > j:
|
| 871 |
+
try:
|
| 872 |
+
port = int(host[i+1:])
|
| 873 |
+
except ValueError:
|
| 874 |
+
if host[i+1:] == "": # http://foo.com:/ == http://foo.com/
|
| 875 |
+
port = self.default_port
|
| 876 |
+
else:
|
| 877 |
+
raise InvalidURL("nonnumeric port: '%s'" % host[i+1:])
|
| 878 |
+
host = host[:i]
|
| 879 |
+
else:
|
| 880 |
+
port = self.default_port
|
| 881 |
+
if host and host[0] == '[' and host[-1] == ']':
|
| 882 |
+
host = host[1:-1]
|
| 883 |
+
|
| 884 |
+
return (host, port)
|
| 885 |
+
|
| 886 |
+
def set_debuglevel(self, level):
|
| 887 |
+
self.debuglevel = level
|
| 888 |
+
|
| 889 |
+
def _tunnel(self):
|
| 890 |
+
connect_str = "CONNECT %s:%d HTTP/1.0\r\n" % (self._tunnel_host,
|
| 891 |
+
self._tunnel_port)
|
| 892 |
+
connect_bytes = connect_str.encode("ascii")
|
| 893 |
+
self.send(connect_bytes)
|
| 894 |
+
for header, value in self._tunnel_headers.items():
|
| 895 |
+
header_str = "%s: %s\r\n" % (header, value)
|
| 896 |
+
header_bytes = header_str.encode("latin-1")
|
| 897 |
+
self.send(header_bytes)
|
| 898 |
+
self.send(b'\r\n')
|
| 899 |
+
|
| 900 |
+
response = self.response_class(self.sock, method=self._method)
|
| 901 |
+
(version, code, message) = response._read_status()
|
| 902 |
+
|
| 903 |
+
if code != http.HTTPStatus.OK:
|
| 904 |
+
self.close()
|
| 905 |
+
raise OSError("Tunnel connection failed: %d %s" % (code,
|
| 906 |
+
message.strip()))
|
| 907 |
+
while True:
|
| 908 |
+
line = response.fp.readline(_MAXLINE + 1)
|
| 909 |
+
if len(line) > _MAXLINE:
|
| 910 |
+
raise LineTooLong("header line")
|
| 911 |
+
if not line:
|
| 912 |
+
# for sites which EOF without sending a trailer
|
| 913 |
+
break
|
| 914 |
+
if line in (b'\r\n', b'\n', b''):
|
| 915 |
+
break
|
| 916 |
+
|
| 917 |
+
if self.debuglevel > 0:
|
| 918 |
+
print('header:', line.decode())
|
| 919 |
+
|
| 920 |
+
def connect(self):
|
| 921 |
+
"""Connect to the host and port specified in __init__."""
|
| 922 |
+
self.sock = self._create_connection(
|
| 923 |
+
(self.host,self.port), self.timeout, self.source_address)
|
| 924 |
+
self.sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
| 925 |
+
|
| 926 |
+
if self._tunnel_host:
|
| 927 |
+
self._tunnel()
|
| 928 |
+
|
| 929 |
+
def close(self):
|
| 930 |
+
"""Close the connection to the HTTP server."""
|
| 931 |
+
self.__state = _CS_IDLE
|
| 932 |
+
try:
|
| 933 |
+
sock = self.sock
|
| 934 |
+
if sock:
|
| 935 |
+
self.sock = None
|
| 936 |
+
sock.close() # close it manually... there may be other refs
|
| 937 |
+
finally:
|
| 938 |
+
response = self.__response
|
| 939 |
+
if response:
|
| 940 |
+
self.__response = None
|
| 941 |
+
response.close()
|
| 942 |
+
|
| 943 |
+
def send(self, data):
|
| 944 |
+
"""Send `data' to the server.
|
| 945 |
+
``data`` can be a string object, a bytes object, an array object, a
|
| 946 |
+
file-like object that supports a .read() method, or an iterable object.
|
| 947 |
+
"""
|
| 948 |
+
|
| 949 |
+
if self.sock is None:
|
| 950 |
+
if self.auto_open:
|
| 951 |
+
self.connect()
|
| 952 |
+
else:
|
| 953 |
+
raise NotConnected()
|
| 954 |
+
|
| 955 |
+
if self.debuglevel > 0:
|
| 956 |
+
print("send:", repr(data))
|
| 957 |
+
if hasattr(data, "read") :
|
| 958 |
+
if self.debuglevel > 0:
|
| 959 |
+
print("sendIng a read()able")
|
| 960 |
+
encode = self._is_textIO(data)
|
| 961 |
+
if encode and self.debuglevel > 0:
|
| 962 |
+
print("encoding file using iso-8859-1")
|
| 963 |
+
while 1:
|
| 964 |
+
datablock = data.read(self.blocksize)
|
| 965 |
+
if not datablock:
|
| 966 |
+
break
|
| 967 |
+
if encode:
|
| 968 |
+
datablock = datablock.encode("iso-8859-1")
|
| 969 |
+
self.sock.sendall(datablock)
|
| 970 |
+
return
|
| 971 |
+
try:
|
| 972 |
+
self.sock.sendall(data)
|
| 973 |
+
except TypeError:
|
| 974 |
+
if isinstance(data, collections.abc.Iterable):
|
| 975 |
+
for d in data:
|
| 976 |
+
self.sock.sendall(d)
|
| 977 |
+
else:
|
| 978 |
+
raise TypeError("data should be a bytes-like object "
|
| 979 |
+
"or an iterable, got %r" % type(data))
|
| 980 |
+
|
| 981 |
+
def _output(self, s):
|
| 982 |
+
"""Add a line of output to the current request buffer.
|
| 983 |
+
|
| 984 |
+
Assumes that the line does *not* end with \\r\\n.
|
| 985 |
+
"""
|
| 986 |
+
self._buffer.append(s)
|
| 987 |
+
|
| 988 |
+
def _read_readable(self, readable):
|
| 989 |
+
if self.debuglevel > 0:
|
| 990 |
+
print("sendIng a read()able")
|
| 991 |
+
encode = self._is_textIO(readable)
|
| 992 |
+
if encode and self.debuglevel > 0:
|
| 993 |
+
print("encoding file using iso-8859-1")
|
| 994 |
+
while True:
|
| 995 |
+
datablock = readable.read(self.blocksize)
|
| 996 |
+
if not datablock:
|
| 997 |
+
break
|
| 998 |
+
if encode:
|
| 999 |
+
datablock = datablock.encode("iso-8859-1")
|
| 1000 |
+
yield datablock
|
| 1001 |
+
|
| 1002 |
+
def _send_output(self, message_body=None, encode_chunked=False):
|
| 1003 |
+
"""Send the currently buffered request and clear the buffer.
|
| 1004 |
+
|
| 1005 |
+
Appends an extra \\r\\n to the buffer.
|
| 1006 |
+
A message_body may be specified, to be appended to the request.
|
| 1007 |
+
"""
|
| 1008 |
+
self._buffer.extend((b"", b""))
|
| 1009 |
+
msg = b"\r\n".join(self._buffer)
|
| 1010 |
+
del self._buffer[:]
|
| 1011 |
+
self.send(msg)
|
| 1012 |
+
|
| 1013 |
+
if message_body is not None:
|
| 1014 |
+
|
| 1015 |
+
# create a consistent interface to message_body
|
| 1016 |
+
if hasattr(message_body, 'read'):
|
| 1017 |
+
# Let file-like take precedence over byte-like. This
|
| 1018 |
+
# is needed to allow the current position of mmap'ed
|
| 1019 |
+
# files to be taken into account.
|
| 1020 |
+
chunks = self._read_readable(message_body)
|
| 1021 |
+
else:
|
| 1022 |
+
try:
|
| 1023 |
+
# this is solely to check to see if message_body
|
| 1024 |
+
# implements the buffer API. it /would/ be easier
|
| 1025 |
+
# to capture if PyObject_CheckBuffer was exposed
|
| 1026 |
+
# to Python.
|
| 1027 |
+
memoryview(message_body)
|
| 1028 |
+
except TypeError:
|
| 1029 |
+
try:
|
| 1030 |
+
chunks = iter(message_body)
|
| 1031 |
+
except TypeError:
|
| 1032 |
+
raise TypeError("message_body should be a bytes-like "
|
| 1033 |
+
"object or an iterable, got %r"
|
| 1034 |
+
% type(message_body))
|
| 1035 |
+
else:
|
| 1036 |
+
# the object implements the buffer interface and
|
| 1037 |
+
# can be passed directly into socket methods
|
| 1038 |
+
chunks = (message_body,)
|
| 1039 |
+
|
| 1040 |
+
for chunk in chunks:
|
| 1041 |
+
if not chunk:
|
| 1042 |
+
if self.debuglevel > 0:
|
| 1043 |
+
print('Zero length chunk ignored')
|
| 1044 |
+
continue
|
| 1045 |
+
|
| 1046 |
+
if encode_chunked and self._http_vsn == 11:
|
| 1047 |
+
# chunked encoding
|
| 1048 |
+
chunk = f'{len(chunk):X}\r\n'.encode('ascii') + chunk \
|
| 1049 |
+
+ b'\r\n'
|
| 1050 |
+
self.send(chunk)
|
| 1051 |
+
|
| 1052 |
+
if encode_chunked and self._http_vsn == 11:
|
| 1053 |
+
# end chunked transfer
|
| 1054 |
+
self.send(b'0\r\n\r\n')
|
| 1055 |
+
|
| 1056 |
+
def putrequest(self, method, url, skip_host=False,
|
| 1057 |
+
skip_accept_encoding=False):
|
| 1058 |
+
"""Send a request to the server.
|
| 1059 |
+
|
| 1060 |
+
`method' specifies an HTTP request method, e.g. 'GET'.
|
| 1061 |
+
`url' specifies the object being requested, e.g. '/index.html'.
|
| 1062 |
+
`skip_host' if True does not add automatically a 'Host:' header
|
| 1063 |
+
`skip_accept_encoding' if True does not add automatically an
|
| 1064 |
+
'Accept-Encoding:' header
|
| 1065 |
+
"""
|
| 1066 |
+
|
| 1067 |
+
# if a prior response has been completed, then forget about it.
|
| 1068 |
+
if self.__response and self.__response.isclosed():
|
| 1069 |
+
self.__response = None
|
| 1070 |
+
|
| 1071 |
+
|
| 1072 |
+
# in certain cases, we cannot issue another request on this connection.
|
| 1073 |
+
# this occurs when:
|
| 1074 |
+
# 1) we are in the process of sending a request. (_CS_REQ_STARTED)
|
| 1075 |
+
# 2) a response to a previous request has signalled that it is going
|
| 1076 |
+
# to close the connection upon completion.
|
| 1077 |
+
# 3) the headers for the previous response have not been read, thus
|
| 1078 |
+
# we cannot determine whether point (2) is true. (_CS_REQ_SENT)
|
| 1079 |
+
#
|
| 1080 |
+
# if there is no prior response, then we can request at will.
|
| 1081 |
+
#
|
| 1082 |
+
# if point (2) is true, then we will have passed the socket to the
|
| 1083 |
+
# response (effectively meaning, "there is no prior response"), and
|
| 1084 |
+
# will open a new one when a new request is made.
|
| 1085 |
+
#
|
| 1086 |
+
# Note: if a prior response exists, then we *can* start a new request.
|
| 1087 |
+
# We are not allowed to begin fetching the response to this new
|
| 1088 |
+
# request, however, until that prior response is complete.
|
| 1089 |
+
#
|
| 1090 |
+
if self.__state == _CS_IDLE:
|
| 1091 |
+
self.__state = _CS_REQ_STARTED
|
| 1092 |
+
else:
|
| 1093 |
+
raise CannotSendRequest(self.__state)
|
| 1094 |
+
|
| 1095 |
+
self._validate_method(method)
|
| 1096 |
+
|
| 1097 |
+
# Save the method for use later in the response phase
|
| 1098 |
+
self._method = method
|
| 1099 |
+
|
| 1100 |
+
url = url or '/'
|
| 1101 |
+
self._validate_path(url)
|
| 1102 |
+
|
| 1103 |
+
request = '%s %s %s' % (method, url, self._http_vsn_str)
|
| 1104 |
+
|
| 1105 |
+
self._output(self._encode_request(request))
|
| 1106 |
+
|
| 1107 |
+
if self._http_vsn == 11:
|
| 1108 |
+
# Issue some standard headers for better HTTP/1.1 compliance
|
| 1109 |
+
|
| 1110 |
+
if not skip_host:
|
| 1111 |
+
# this header is issued *only* for HTTP/1.1
|
| 1112 |
+
# connections. more specifically, this means it is
|
| 1113 |
+
# only issued when the client uses the new
|
| 1114 |
+
# HTTPConnection() class. backwards-compat clients
|
| 1115 |
+
# will be using HTTP/1.0 and those clients may be
|
| 1116 |
+
# issuing this header themselves. we should NOT issue
|
| 1117 |
+
# it twice; some web servers (such as Apache) barf
|
| 1118 |
+
# when they see two Host: headers
|
| 1119 |
+
|
| 1120 |
+
# If we need a non-standard port,include it in the
|
| 1121 |
+
# header. If the request is going through a proxy,
|
| 1122 |
+
# but the host of the actual URL, not the host of the
|
| 1123 |
+
# proxy.
|
| 1124 |
+
|
| 1125 |
+
netloc = ''
|
| 1126 |
+
if url.startswith('http'):
|
| 1127 |
+
nil, netloc, nil, nil, nil = urlsplit(url)
|
| 1128 |
+
|
| 1129 |
+
if netloc:
|
| 1130 |
+
try:
|
| 1131 |
+
netloc_enc = netloc.encode("ascii")
|
| 1132 |
+
except UnicodeEncodeError:
|
| 1133 |
+
netloc_enc = netloc.encode("idna")
|
| 1134 |
+
self.putheader('Host', netloc_enc)
|
| 1135 |
+
else:
|
| 1136 |
+
if self._tunnel_host:
|
| 1137 |
+
host = self._tunnel_host
|
| 1138 |
+
port = self._tunnel_port
|
| 1139 |
+
else:
|
| 1140 |
+
host = self.host
|
| 1141 |
+
port = self.port
|
| 1142 |
+
|
| 1143 |
+
try:
|
| 1144 |
+
host_enc = host.encode("ascii")
|
| 1145 |
+
except UnicodeEncodeError:
|
| 1146 |
+
host_enc = host.encode("idna")
|
| 1147 |
+
|
| 1148 |
+
# As per RFC 273, IPv6 address should be wrapped with []
|
| 1149 |
+
# when used as Host header
|
| 1150 |
+
|
| 1151 |
+
if host.find(':') >= 0:
|
| 1152 |
+
host_enc = b'[' + host_enc + b']'
|
| 1153 |
+
|
| 1154 |
+
if port == self.default_port:
|
| 1155 |
+
self.putheader('Host', host_enc)
|
| 1156 |
+
else:
|
| 1157 |
+
host_enc = host_enc.decode("ascii")
|
| 1158 |
+
self.putheader('Host', "%s:%s" % (host_enc, port))
|
| 1159 |
+
|
| 1160 |
+
# note: we are assuming that clients will not attempt to set these
|
| 1161 |
+
# headers since *this* library must deal with the
|
| 1162 |
+
# consequences. this also means that when the supporting
|
| 1163 |
+
# libraries are updated to recognize other forms, then this
|
| 1164 |
+
# code should be changed (removed or updated).
|
| 1165 |
+
|
| 1166 |
+
# we only want a Content-Encoding of "identity" since we don't
|
| 1167 |
+
# support encodings such as x-gzip or x-deflate.
|
| 1168 |
+
if not skip_accept_encoding:
|
| 1169 |
+
self.putheader('Accept-Encoding', 'identity')
|
| 1170 |
+
|
| 1171 |
+
# we can accept "chunked" Transfer-Encodings, but no others
|
| 1172 |
+
# NOTE: no TE header implies *only* "chunked"
|
| 1173 |
+
#self.putheader('TE', 'chunked')
|
| 1174 |
+
|
| 1175 |
+
# if TE is supplied in the header, then it must appear in a
|
| 1176 |
+
# Connection header.
|
| 1177 |
+
#self.putheader('Connection', 'TE')
|
| 1178 |
+
|
| 1179 |
+
else:
|
| 1180 |
+
# For HTTP/1.0, the server will assume "not chunked"
|
| 1181 |
+
pass
|
| 1182 |
+
|
| 1183 |
+
def _encode_request(self, request):
|
| 1184 |
+
# ASCII also helps prevent CVE-2019-9740.
|
| 1185 |
+
return request.encode('ascii')
|
| 1186 |
+
|
| 1187 |
+
def _validate_method(self, method):
|
| 1188 |
+
"""Validate a method name for putrequest."""
|
| 1189 |
+
# prevent http header injection
|
| 1190 |
+
match = _contains_disallowed_method_pchar_re.search(method)
|
| 1191 |
+
if match:
|
| 1192 |
+
raise ValueError(
|
| 1193 |
+
f"method can't contain control characters. {method!r} "
|
| 1194 |
+
f"(found at least {match.group()!r})")
|
| 1195 |
+
|
| 1196 |
+
def _validate_path(self, url):
|
| 1197 |
+
"""Validate a url for putrequest."""
|
| 1198 |
+
# Prevent CVE-2019-9740.
|
| 1199 |
+
match = _contains_disallowed_url_pchar_re.search(url)
|
| 1200 |
+
if match:
|
| 1201 |
+
raise InvalidURL(f"URL can't contain control characters. {url!r} "
|
| 1202 |
+
f"(found at least {match.group()!r})")
|
| 1203 |
+
|
| 1204 |
+
def _validate_host(self, host):
|
| 1205 |
+
"""Validate a host so it doesn't contain control characters."""
|
| 1206 |
+
# Prevent CVE-2019-18348.
|
| 1207 |
+
match = _contains_disallowed_url_pchar_re.search(host)
|
| 1208 |
+
if match:
|
| 1209 |
+
raise InvalidURL(f"URL can't contain control characters. {host!r} "
|
| 1210 |
+
f"(found at least {match.group()!r})")
|
| 1211 |
+
|
| 1212 |
+
def putheader(self, header, *values):
|
| 1213 |
+
"""Send a request header line to the server.
|
| 1214 |
+
|
| 1215 |
+
For example: h.putheader('Accept', 'text/html')
|
| 1216 |
+
"""
|
| 1217 |
+
if self.__state != _CS_REQ_STARTED:
|
| 1218 |
+
raise CannotSendHeader()
|
| 1219 |
+
|
| 1220 |
+
if hasattr(header, 'encode'):
|
| 1221 |
+
header = header.encode('ascii')
|
| 1222 |
+
|
| 1223 |
+
if not _is_legal_header_name(header):
|
| 1224 |
+
raise ValueError('Invalid header name %r' % (header,))
|
| 1225 |
+
|
| 1226 |
+
values = list(values)
|
| 1227 |
+
for i, one_value in enumerate(values):
|
| 1228 |
+
if hasattr(one_value, 'encode'):
|
| 1229 |
+
values[i] = one_value.encode('latin-1')
|
| 1230 |
+
elif isinstance(one_value, int):
|
| 1231 |
+
values[i] = str(one_value).encode('ascii')
|
| 1232 |
+
|
| 1233 |
+
if _is_illegal_header_value(values[i]):
|
| 1234 |
+
raise ValueError('Invalid header value %r' % (values[i],))
|
| 1235 |
+
|
| 1236 |
+
value = b'\r\n\t'.join(values)
|
| 1237 |
+
header = header + b': ' + value
|
| 1238 |
+
self._output(header)
|
| 1239 |
+
|
| 1240 |
+
def endheaders(self, message_body=None, *, encode_chunked=False):
|
| 1241 |
+
"""Indicate that the last header line has been sent to the server.
|
| 1242 |
+
|
| 1243 |
+
This method sends the request to the server. The optional message_body
|
| 1244 |
+
argument can be used to pass a message body associated with the
|
| 1245 |
+
request.
|
| 1246 |
+
"""
|
| 1247 |
+
if self.__state == _CS_REQ_STARTED:
|
| 1248 |
+
self.__state = _CS_REQ_SENT
|
| 1249 |
+
else:
|
| 1250 |
+
raise CannotSendHeader()
|
| 1251 |
+
self._send_output(message_body, encode_chunked=encode_chunked)
|
| 1252 |
+
|
| 1253 |
+
def request(self, method, url, body=None, headers={}, *,
|
| 1254 |
+
encode_chunked=False):
|
| 1255 |
+
"""Send a complete request to the server."""
|
| 1256 |
+
self._send_request(method, url, body, headers, encode_chunked)
|
| 1257 |
+
|
| 1258 |
+
def _send_request(self, method, url, body, headers, encode_chunked):
|
| 1259 |
+
# Honor explicitly requested Host: and Accept-Encoding: headers.
|
| 1260 |
+
header_names = frozenset(k.lower() for k in headers)
|
| 1261 |
+
skips = {}
|
| 1262 |
+
if 'host' in header_names:
|
| 1263 |
+
skips['skip_host'] = 1
|
| 1264 |
+
if 'accept-encoding' in header_names:
|
| 1265 |
+
skips['skip_accept_encoding'] = 1
|
| 1266 |
+
|
| 1267 |
+
self.putrequest(method, url, **skips)
|
| 1268 |
+
|
| 1269 |
+
# chunked encoding will happen if HTTP/1.1 is used and either
|
| 1270 |
+
# the caller passes encode_chunked=True or the following
|
| 1271 |
+
# conditions hold:
|
| 1272 |
+
# 1. content-length has not been explicitly set
|
| 1273 |
+
# 2. the body is a file or iterable, but not a str or bytes-like
|
| 1274 |
+
# 3. Transfer-Encoding has NOT been explicitly set by the caller
|
| 1275 |
+
|
| 1276 |
+
if 'content-length' not in header_names:
|
| 1277 |
+
# only chunk body if not explicitly set for backwards
|
| 1278 |
+
# compatibility, assuming the client code is already handling the
|
| 1279 |
+
# chunking
|
| 1280 |
+
if 'transfer-encoding' not in header_names:
|
| 1281 |
+
# if content-length cannot be automatically determined, fall
|
| 1282 |
+
# back to chunked encoding
|
| 1283 |
+
encode_chunked = False
|
| 1284 |
+
content_length = self._get_content_length(body, method)
|
| 1285 |
+
if content_length is None:
|
| 1286 |
+
if body is not None:
|
| 1287 |
+
if self.debuglevel > 0:
|
| 1288 |
+
print('Unable to determine size of %r' % body)
|
| 1289 |
+
encode_chunked = True
|
| 1290 |
+
self.putheader('Transfer-Encoding', 'chunked')
|
| 1291 |
+
else:
|
| 1292 |
+
self.putheader('Content-Length', str(content_length))
|
| 1293 |
+
else:
|
| 1294 |
+
encode_chunked = False
|
| 1295 |
+
|
| 1296 |
+
for hdr, value in headers.items():
|
| 1297 |
+
self.putheader(hdr, value)
|
| 1298 |
+
if isinstance(body, str):
|
| 1299 |
+
# RFC 2616 Section 3.7.1 says that text default has a
|
| 1300 |
+
# default charset of iso-8859-1.
|
| 1301 |
+
body = _encode(body, 'body')
|
| 1302 |
+
self.endheaders(body, encode_chunked=encode_chunked)
|
| 1303 |
+
|
| 1304 |
+
def getresponse(self):
|
| 1305 |
+
"""Get the response from the server.
|
| 1306 |
+
|
| 1307 |
+
If the HTTPConnection is in the correct state, returns an
|
| 1308 |
+
instance of HTTPResponse or of whatever object is returned by
|
| 1309 |
+
the response_class variable.
|
| 1310 |
+
|
| 1311 |
+
If a request has not been sent or if a previous response has
|
| 1312 |
+
not be handled, ResponseNotReady is raised. If the HTTP
|
| 1313 |
+
response indicates that the connection should be closed, then
|
| 1314 |
+
it will be closed before the response is returned. When the
|
| 1315 |
+
connection is closed, the underlying socket is closed.
|
| 1316 |
+
"""
|
| 1317 |
+
|
| 1318 |
+
# if a prior response has been completed, then forget about it.
|
| 1319 |
+
if self.__response and self.__response.isclosed():
|
| 1320 |
+
self.__response = None
|
| 1321 |
+
|
| 1322 |
+
# if a prior response exists, then it must be completed (otherwise, we
|
| 1323 |
+
# cannot read this response's header to determine the connection-close
|
| 1324 |
+
# behavior)
|
| 1325 |
+
#
|
| 1326 |
+
# note: if a prior response existed, but was connection-close, then the
|
| 1327 |
+
# socket and response were made independent of this HTTPConnection
|
| 1328 |
+
# object since a new request requires that we open a whole new
|
| 1329 |
+
# connection
|
| 1330 |
+
#
|
| 1331 |
+
# this means the prior response had one of two states:
|
| 1332 |
+
# 1) will_close: this connection was reset and the prior socket and
|
| 1333 |
+
# response operate independently
|
| 1334 |
+
# 2) persistent: the response was retained and we await its
|
| 1335 |
+
# isclosed() status to become true.
|
| 1336 |
+
#
|
| 1337 |
+
if self.__state != _CS_REQ_SENT or self.__response:
|
| 1338 |
+
raise ResponseNotReady(self.__state)
|
| 1339 |
+
|
| 1340 |
+
if self.debuglevel > 0:
|
| 1341 |
+
response = self.response_class(self.sock, self.debuglevel,
|
| 1342 |
+
method=self._method)
|
| 1343 |
+
else:
|
| 1344 |
+
response = self.response_class(self.sock, method=self._method)
|
| 1345 |
+
|
| 1346 |
+
try:
|
| 1347 |
+
try:
|
| 1348 |
+
response.begin()
|
| 1349 |
+
except ConnectionError:
|
| 1350 |
+
self.close()
|
| 1351 |
+
raise
|
| 1352 |
+
assert response.will_close != _UNKNOWN
|
| 1353 |
+
self.__state = _CS_IDLE
|
| 1354 |
+
|
| 1355 |
+
if response.will_close:
|
| 1356 |
+
# this effectively passes the connection to the response
|
| 1357 |
+
self.close()
|
| 1358 |
+
else:
|
| 1359 |
+
# remember this, so we can tell when it is complete
|
| 1360 |
+
self.__response = response
|
| 1361 |
+
|
| 1362 |
+
return response
|
| 1363 |
+
except:
|
| 1364 |
+
response.close()
|
| 1365 |
+
raise
|
| 1366 |
+
|
| 1367 |
+
try:
|
| 1368 |
+
import ssl
|
| 1369 |
+
except ImportError:
|
| 1370 |
+
pass
|
| 1371 |
+
else:
|
| 1372 |
+
class HTTPSConnection(HTTPConnection):
|
| 1373 |
+
"This class allows communication via SSL."
|
| 1374 |
+
|
| 1375 |
+
default_port = HTTPS_PORT
|
| 1376 |
+
|
| 1377 |
+
# XXX Should key_file and cert_file be deprecated in favour of context?
|
| 1378 |
+
|
| 1379 |
+
def __init__(self, host, port=None, key_file=None, cert_file=None,
|
| 1380 |
+
timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
|
| 1381 |
+
source_address=None, *, context=None,
|
| 1382 |
+
check_hostname=None, blocksize=8192):
|
| 1383 |
+
super(HTTPSConnection, self).__init__(host, port, timeout,
|
| 1384 |
+
source_address,
|
| 1385 |
+
blocksize=blocksize)
|
| 1386 |
+
if (key_file is not None or cert_file is not None or
|
| 1387 |
+
check_hostname is not None):
|
| 1388 |
+
import warnings
|
| 1389 |
+
warnings.warn("key_file, cert_file and check_hostname are "
|
| 1390 |
+
"deprecated, use a custom context instead.",
|
| 1391 |
+
DeprecationWarning, 2)
|
| 1392 |
+
self.key_file = key_file
|
| 1393 |
+
self.cert_file = cert_file
|
| 1394 |
+
if context is None:
|
| 1395 |
+
context = ssl._create_default_https_context()
|
| 1396 |
+
# enable PHA for TLS 1.3 connections if available
|
| 1397 |
+
if context.post_handshake_auth is not None:
|
| 1398 |
+
context.post_handshake_auth = True
|
| 1399 |
+
will_verify = context.verify_mode != ssl.CERT_NONE
|
| 1400 |
+
if check_hostname is None:
|
| 1401 |
+
check_hostname = context.check_hostname
|
| 1402 |
+
if check_hostname and not will_verify:
|
| 1403 |
+
raise ValueError("check_hostname needs a SSL context with "
|
| 1404 |
+
"either CERT_OPTIONAL or CERT_REQUIRED")
|
| 1405 |
+
if key_file or cert_file:
|
| 1406 |
+
context.load_cert_chain(cert_file, key_file)
|
| 1407 |
+
# cert and key file means the user wants to authenticate.
|
| 1408 |
+
# enable TLS 1.3 PHA implicitly even for custom contexts.
|
| 1409 |
+
if context.post_handshake_auth is not None:
|
| 1410 |
+
context.post_handshake_auth = True
|
| 1411 |
+
self._context = context
|
| 1412 |
+
if check_hostname is not None:
|
| 1413 |
+
self._context.check_hostname = check_hostname
|
| 1414 |
+
|
| 1415 |
+
def connect(self):
|
| 1416 |
+
"Connect to a host on a given (SSL) port."
|
| 1417 |
+
|
| 1418 |
+
super().connect()
|
| 1419 |
+
|
| 1420 |
+
if self._tunnel_host:
|
| 1421 |
+
server_hostname = self._tunnel_host
|
| 1422 |
+
else:
|
| 1423 |
+
server_hostname = self.host
|
| 1424 |
+
|
| 1425 |
+
self.sock = self._context.wrap_socket(self.sock,
|
| 1426 |
+
server_hostname=server_hostname)
|
| 1427 |
+
|
| 1428 |
+
__all__.append("HTTPSConnection")
|
| 1429 |
+
|
| 1430 |
+
class HTTPException(Exception):
|
| 1431 |
+
# Subclasses that define an __init__ must call Exception.__init__
|
| 1432 |
+
# or define self.args. Otherwise, str() will fail.
|
| 1433 |
+
pass
|
| 1434 |
+
|
| 1435 |
+
class NotConnected(HTTPException):
|
| 1436 |
+
pass
|
| 1437 |
+
|
| 1438 |
+
class InvalidURL(HTTPException):
|
| 1439 |
+
pass
|
| 1440 |
+
|
| 1441 |
+
class UnknownProtocol(HTTPException):
|
| 1442 |
+
def __init__(self, version):
|
| 1443 |
+
self.args = version,
|
| 1444 |
+
self.version = version
|
| 1445 |
+
|
| 1446 |
+
class UnknownTransferEncoding(HTTPException):
|
| 1447 |
+
pass
|
| 1448 |
+
|
| 1449 |
+
class UnimplementedFileMode(HTTPException):
|
| 1450 |
+
pass
|
| 1451 |
+
|
| 1452 |
+
class IncompleteRead(HTTPException):
|
| 1453 |
+
def __init__(self, partial, expected=None):
|
| 1454 |
+
self.args = partial,
|
| 1455 |
+
self.partial = partial
|
| 1456 |
+
self.expected = expected
|
| 1457 |
+
def __repr__(self):
|
| 1458 |
+
if self.expected is not None:
|
| 1459 |
+
e = ', %i more expected' % self.expected
|
| 1460 |
+
else:
|
| 1461 |
+
e = ''
|
| 1462 |
+
return '%s(%i bytes read%s)' % (self.__class__.__name__,
|
| 1463 |
+
len(self.partial), e)
|
| 1464 |
+
__str__ = object.__str__
|
| 1465 |
+
|
| 1466 |
+
class ImproperConnectionState(HTTPException):
|
| 1467 |
+
pass
|
| 1468 |
+
|
| 1469 |
+
class CannotSendRequest(ImproperConnectionState):
|
| 1470 |
+
pass
|
| 1471 |
+
|
| 1472 |
+
class CannotSendHeader(ImproperConnectionState):
|
| 1473 |
+
pass
|
| 1474 |
+
|
| 1475 |
+
class ResponseNotReady(ImproperConnectionState):
|
| 1476 |
+
pass
|
| 1477 |
+
|
| 1478 |
+
class BadStatusLine(HTTPException):
|
| 1479 |
+
def __init__(self, line):
|
| 1480 |
+
if not line:
|
| 1481 |
+
line = repr(line)
|
| 1482 |
+
self.args = line,
|
| 1483 |
+
self.line = line
|
| 1484 |
+
|
| 1485 |
+
class LineTooLong(HTTPException):
|
| 1486 |
+
def __init__(self, line_type):
|
| 1487 |
+
HTTPException.__init__(self, "got more than %d bytes when reading %s"
|
| 1488 |
+
% (_MAXLINE, line_type))
|
| 1489 |
+
|
| 1490 |
+
class RemoteDisconnected(ConnectionResetError, BadStatusLine):
|
| 1491 |
+
def __init__(self, *pos, **kw):
|
| 1492 |
+
BadStatusLine.__init__(self, "")
|
| 1493 |
+
ConnectionResetError.__init__(self, *pos, **kw)
|
| 1494 |
+
|
| 1495 |
+
# for backwards compatibility
|
| 1496 |
+
error = HTTPException
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/http/cookiejar.py
ADDED
|
@@ -0,0 +1,2113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
r"""HTTP cookie handling for web clients.
|
| 2 |
+
|
| 3 |
+
This module has (now fairly distant) origins in Gisle Aas' Perl module
|
| 4 |
+
HTTP::Cookies, from the libwww-perl library.
|
| 5 |
+
|
| 6 |
+
Docstrings, comments and debug strings in this code refer to the
|
| 7 |
+
attributes of the HTTP cookie system as cookie-attributes, to distinguish
|
| 8 |
+
them clearly from Python attributes.
|
| 9 |
+
|
| 10 |
+
Class diagram (note that BSDDBCookieJar and the MSIE* classes are not
|
| 11 |
+
distributed with the Python standard library, but are available from
|
| 12 |
+
http://wwwsearch.sf.net/):
|
| 13 |
+
|
| 14 |
+
CookieJar____
|
| 15 |
+
/ \ \
|
| 16 |
+
FileCookieJar \ \
|
| 17 |
+
/ | \ \ \
|
| 18 |
+
MozillaCookieJar | LWPCookieJar \ \
|
| 19 |
+
| | \
|
| 20 |
+
| ---MSIEBase | \
|
| 21 |
+
| / | | \
|
| 22 |
+
| / MSIEDBCookieJar BSDDBCookieJar
|
| 23 |
+
|/
|
| 24 |
+
MSIECookieJar
|
| 25 |
+
|
| 26 |
+
"""
|
| 27 |
+
|
| 28 |
+
__all__ = ['Cookie', 'CookieJar', 'CookiePolicy', 'DefaultCookiePolicy',
|
| 29 |
+
'FileCookieJar', 'LWPCookieJar', 'LoadError', 'MozillaCookieJar']
|
| 30 |
+
|
| 31 |
+
import os
|
| 32 |
+
import copy
|
| 33 |
+
import datetime
|
| 34 |
+
import re
|
| 35 |
+
import time
|
| 36 |
+
import urllib.parse, urllib.request
|
| 37 |
+
import threading as _threading
|
| 38 |
+
import http.client # only for the default HTTP port
|
| 39 |
+
from calendar import timegm
|
| 40 |
+
|
| 41 |
+
debug = False # set to True to enable debugging via the logging module
|
| 42 |
+
logger = None
|
| 43 |
+
|
| 44 |
+
def _debug(*args):
|
| 45 |
+
if not debug:
|
| 46 |
+
return
|
| 47 |
+
global logger
|
| 48 |
+
if not logger:
|
| 49 |
+
import logging
|
| 50 |
+
logger = logging.getLogger("http.cookiejar")
|
| 51 |
+
return logger.debug(*args)
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
DEFAULT_HTTP_PORT = str(http.client.HTTP_PORT)
|
| 55 |
+
MISSING_FILENAME_TEXT = ("a filename was not supplied (nor was the CookieJar "
|
| 56 |
+
"instance initialised with one)")
|
| 57 |
+
|
| 58 |
+
def _warn_unhandled_exception():
|
| 59 |
+
# There are a few catch-all except: statements in this module, for
|
| 60 |
+
# catching input that's bad in unexpected ways. Warn if any
|
| 61 |
+
# exceptions are caught there.
|
| 62 |
+
import io, warnings, traceback
|
| 63 |
+
f = io.StringIO()
|
| 64 |
+
traceback.print_exc(None, f)
|
| 65 |
+
msg = f.getvalue()
|
| 66 |
+
warnings.warn("http.cookiejar bug!\n%s" % msg, stacklevel=2)
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
# Date/time conversion
|
| 70 |
+
# -----------------------------------------------------------------------------
|
| 71 |
+
|
| 72 |
+
EPOCH_YEAR = 1970
|
| 73 |
+
def _timegm(tt):
|
| 74 |
+
year, month, mday, hour, min, sec = tt[:6]
|
| 75 |
+
if ((year >= EPOCH_YEAR) and (1 <= month <= 12) and (1 <= mday <= 31) and
|
| 76 |
+
(0 <= hour <= 24) and (0 <= min <= 59) and (0 <= sec <= 61)):
|
| 77 |
+
return timegm(tt)
|
| 78 |
+
else:
|
| 79 |
+
return None
|
| 80 |
+
|
| 81 |
+
DAYS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
|
| 82 |
+
MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
| 83 |
+
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
|
| 84 |
+
MONTHS_LOWER = []
|
| 85 |
+
for month in MONTHS: MONTHS_LOWER.append(month.lower())
|
| 86 |
+
|
| 87 |
+
def time2isoz(t=None):
|
| 88 |
+
"""Return a string representing time in seconds since epoch, t.
|
| 89 |
+
|
| 90 |
+
If the function is called without an argument, it will use the current
|
| 91 |
+
time.
|
| 92 |
+
|
| 93 |
+
The format of the returned string is like "YYYY-MM-DD hh:mm:ssZ",
|
| 94 |
+
representing Universal Time (UTC, aka GMT). An example of this format is:
|
| 95 |
+
|
| 96 |
+
1994-11-24 08:49:37Z
|
| 97 |
+
|
| 98 |
+
"""
|
| 99 |
+
if t is None:
|
| 100 |
+
dt = datetime.datetime.utcnow()
|
| 101 |
+
else:
|
| 102 |
+
dt = datetime.datetime.utcfromtimestamp(t)
|
| 103 |
+
return "%04d-%02d-%02d %02d:%02d:%02dZ" % (
|
| 104 |
+
dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second)
|
| 105 |
+
|
| 106 |
+
def time2netscape(t=None):
|
| 107 |
+
"""Return a string representing time in seconds since epoch, t.
|
| 108 |
+
|
| 109 |
+
If the function is called without an argument, it will use the current
|
| 110 |
+
time.
|
| 111 |
+
|
| 112 |
+
The format of the returned string is like this:
|
| 113 |
+
|
| 114 |
+
Wed, DD-Mon-YYYY HH:MM:SS GMT
|
| 115 |
+
|
| 116 |
+
"""
|
| 117 |
+
if t is None:
|
| 118 |
+
dt = datetime.datetime.utcnow()
|
| 119 |
+
else:
|
| 120 |
+
dt = datetime.datetime.utcfromtimestamp(t)
|
| 121 |
+
return "%s, %02d-%s-%04d %02d:%02d:%02d GMT" % (
|
| 122 |
+
DAYS[dt.weekday()], dt.day, MONTHS[dt.month-1],
|
| 123 |
+
dt.year, dt.hour, dt.minute, dt.second)
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
UTC_ZONES = {"GMT": None, "UTC": None, "UT": None, "Z": None}
|
| 127 |
+
|
| 128 |
+
TIMEZONE_RE = re.compile(r"^([-+])?(\d\d?):?(\d\d)?$", re.ASCII)
|
| 129 |
+
def offset_from_tz_string(tz):
|
| 130 |
+
offset = None
|
| 131 |
+
if tz in UTC_ZONES:
|
| 132 |
+
offset = 0
|
| 133 |
+
else:
|
| 134 |
+
m = TIMEZONE_RE.search(tz)
|
| 135 |
+
if m:
|
| 136 |
+
offset = 3600 * int(m.group(2))
|
| 137 |
+
if m.group(3):
|
| 138 |
+
offset = offset + 60 * int(m.group(3))
|
| 139 |
+
if m.group(1) == '-':
|
| 140 |
+
offset = -offset
|
| 141 |
+
return offset
|
| 142 |
+
|
| 143 |
+
def _str2time(day, mon, yr, hr, min, sec, tz):
|
| 144 |
+
yr = int(yr)
|
| 145 |
+
if yr > datetime.MAXYEAR:
|
| 146 |
+
return None
|
| 147 |
+
|
| 148 |
+
# translate month name to number
|
| 149 |
+
# month numbers start with 1 (January)
|
| 150 |
+
try:
|
| 151 |
+
mon = MONTHS_LOWER.index(mon.lower())+1
|
| 152 |
+
except ValueError:
|
| 153 |
+
# maybe it's already a number
|
| 154 |
+
try:
|
| 155 |
+
imon = int(mon)
|
| 156 |
+
except ValueError:
|
| 157 |
+
return None
|
| 158 |
+
if 1 <= imon <= 12:
|
| 159 |
+
mon = imon
|
| 160 |
+
else:
|
| 161 |
+
return None
|
| 162 |
+
|
| 163 |
+
# make sure clock elements are defined
|
| 164 |
+
if hr is None: hr = 0
|
| 165 |
+
if min is None: min = 0
|
| 166 |
+
if sec is None: sec = 0
|
| 167 |
+
|
| 168 |
+
day = int(day)
|
| 169 |
+
hr = int(hr)
|
| 170 |
+
min = int(min)
|
| 171 |
+
sec = int(sec)
|
| 172 |
+
|
| 173 |
+
if yr < 1000:
|
| 174 |
+
# find "obvious" year
|
| 175 |
+
cur_yr = time.localtime(time.time())[0]
|
| 176 |
+
m = cur_yr % 100
|
| 177 |
+
tmp = yr
|
| 178 |
+
yr = yr + cur_yr - m
|
| 179 |
+
m = m - tmp
|
| 180 |
+
if abs(m) > 50:
|
| 181 |
+
if m > 0: yr = yr + 100
|
| 182 |
+
else: yr = yr - 100
|
| 183 |
+
|
| 184 |
+
# convert UTC time tuple to seconds since epoch (not timezone-adjusted)
|
| 185 |
+
t = _timegm((yr, mon, day, hr, min, sec, tz))
|
| 186 |
+
|
| 187 |
+
if t is not None:
|
| 188 |
+
# adjust time using timezone string, to get absolute time since epoch
|
| 189 |
+
if tz is None:
|
| 190 |
+
tz = "UTC"
|
| 191 |
+
tz = tz.upper()
|
| 192 |
+
offset = offset_from_tz_string(tz)
|
| 193 |
+
if offset is None:
|
| 194 |
+
return None
|
| 195 |
+
t = t - offset
|
| 196 |
+
|
| 197 |
+
return t
|
| 198 |
+
|
| 199 |
+
STRICT_DATE_RE = re.compile(
|
| 200 |
+
r"^[SMTWF][a-z][a-z], (\d\d) ([JFMASOND][a-z][a-z]) "
|
| 201 |
+
r"(\d\d\d\d) (\d\d):(\d\d):(\d\d) GMT$", re.ASCII)
|
| 202 |
+
WEEKDAY_RE = re.compile(
|
| 203 |
+
r"^(?:Sun|Mon|Tue|Wed|Thu|Fri|Sat)[a-z]*,?\s*", re.I | re.ASCII)
|
| 204 |
+
LOOSE_HTTP_DATE_RE = re.compile(
|
| 205 |
+
r"""^
|
| 206 |
+
(\d\d?) # day
|
| 207 |
+
(?:\s+|[-\/])
|
| 208 |
+
(\w+) # month
|
| 209 |
+
(?:\s+|[-\/])
|
| 210 |
+
(\d+) # year
|
| 211 |
+
(?:
|
| 212 |
+
(?:\s+|:) # separator before clock
|
| 213 |
+
(\d\d?):(\d\d) # hour:min
|
| 214 |
+
(?::(\d\d))? # optional seconds
|
| 215 |
+
)? # optional clock
|
| 216 |
+
\s*
|
| 217 |
+
(?:
|
| 218 |
+
([-+]?\d{2,4}|(?![APap][Mm]\b)[A-Za-z]+) # timezone
|
| 219 |
+
\s*
|
| 220 |
+
)?
|
| 221 |
+
(?:
|
| 222 |
+
\(\w+\) # ASCII representation of timezone in parens.
|
| 223 |
+
\s*
|
| 224 |
+
)?$""", re.X | re.ASCII)
|
| 225 |
+
def http2time(text):
|
| 226 |
+
"""Returns time in seconds since epoch of time represented by a string.
|
| 227 |
+
|
| 228 |
+
Return value is an integer.
|
| 229 |
+
|
| 230 |
+
None is returned if the format of str is unrecognized, the time is outside
|
| 231 |
+
the representable range, or the timezone string is not recognized. If the
|
| 232 |
+
string contains no timezone, UTC is assumed.
|
| 233 |
+
|
| 234 |
+
The timezone in the string may be numerical (like "-0800" or "+0100") or a
|
| 235 |
+
string timezone (like "UTC", "GMT", "BST" or "EST"). Currently, only the
|
| 236 |
+
timezone strings equivalent to UTC (zero offset) are known to the function.
|
| 237 |
+
|
| 238 |
+
The function loosely parses the following formats:
|
| 239 |
+
|
| 240 |
+
Wed, 09 Feb 1994 22:23:32 GMT -- HTTP format
|
| 241 |
+
Tuesday, 08-Feb-94 14:15:29 GMT -- old rfc850 HTTP format
|
| 242 |
+
Tuesday, 08-Feb-1994 14:15:29 GMT -- broken rfc850 HTTP format
|
| 243 |
+
09 Feb 1994 22:23:32 GMT -- HTTP format (no weekday)
|
| 244 |
+
08-Feb-94 14:15:29 GMT -- rfc850 format (no weekday)
|
| 245 |
+
08-Feb-1994 14:15:29 GMT -- broken rfc850 format (no weekday)
|
| 246 |
+
|
| 247 |
+
The parser ignores leading and trailing whitespace. The time may be
|
| 248 |
+
absent.
|
| 249 |
+
|
| 250 |
+
If the year is given with only 2 digits, the function will select the
|
| 251 |
+
century that makes the year closest to the current date.
|
| 252 |
+
|
| 253 |
+
"""
|
| 254 |
+
# fast exit for strictly conforming string
|
| 255 |
+
m = STRICT_DATE_RE.search(text)
|
| 256 |
+
if m:
|
| 257 |
+
g = m.groups()
|
| 258 |
+
mon = MONTHS_LOWER.index(g[1].lower()) + 1
|
| 259 |
+
tt = (int(g[2]), mon, int(g[0]),
|
| 260 |
+
int(g[3]), int(g[4]), float(g[5]))
|
| 261 |
+
return _timegm(tt)
|
| 262 |
+
|
| 263 |
+
# No, we need some messy parsing...
|
| 264 |
+
|
| 265 |
+
# clean up
|
| 266 |
+
text = text.lstrip()
|
| 267 |
+
text = WEEKDAY_RE.sub("", text, 1) # Useless weekday
|
| 268 |
+
|
| 269 |
+
# tz is time zone specifier string
|
| 270 |
+
day, mon, yr, hr, min, sec, tz = [None]*7
|
| 271 |
+
|
| 272 |
+
# loose regexp parse
|
| 273 |
+
m = LOOSE_HTTP_DATE_RE.search(text)
|
| 274 |
+
if m is not None:
|
| 275 |
+
day, mon, yr, hr, min, sec, tz = m.groups()
|
| 276 |
+
else:
|
| 277 |
+
return None # bad format
|
| 278 |
+
|
| 279 |
+
return _str2time(day, mon, yr, hr, min, sec, tz)
|
| 280 |
+
|
| 281 |
+
ISO_DATE_RE = re.compile(
|
| 282 |
+
r"""^
|
| 283 |
+
(\d{4}) # year
|
| 284 |
+
[-\/]?
|
| 285 |
+
(\d\d?) # numerical month
|
| 286 |
+
[-\/]?
|
| 287 |
+
(\d\d?) # day
|
| 288 |
+
(?:
|
| 289 |
+
(?:\s+|[-:Tt]) # separator before clock
|
| 290 |
+
(\d\d?):?(\d\d) # hour:min
|
| 291 |
+
(?::?(\d\d(?:\.\d*)?))? # optional seconds (and fractional)
|
| 292 |
+
)? # optional clock
|
| 293 |
+
\s*
|
| 294 |
+
(?:
|
| 295 |
+
([-+]?\d\d?:?(:?\d\d)?
|
| 296 |
+
|Z|z) # timezone (Z is "zero meridian", i.e. GMT)
|
| 297 |
+
\s*
|
| 298 |
+
)?$""", re.X | re. ASCII)
|
| 299 |
+
def iso2time(text):
|
| 300 |
+
"""
|
| 301 |
+
As for http2time, but parses the ISO 8601 formats:
|
| 302 |
+
|
| 303 |
+
1994-02-03 14:15:29 -0100 -- ISO 8601 format
|
| 304 |
+
1994-02-03 14:15:29 -- zone is optional
|
| 305 |
+
1994-02-03 -- only date
|
| 306 |
+
1994-02-03T14:15:29 -- Use T as separator
|
| 307 |
+
19940203T141529Z -- ISO 8601 compact format
|
| 308 |
+
19940203 -- only date
|
| 309 |
+
|
| 310 |
+
"""
|
| 311 |
+
# clean up
|
| 312 |
+
text = text.lstrip()
|
| 313 |
+
|
| 314 |
+
# tz is time zone specifier string
|
| 315 |
+
day, mon, yr, hr, min, sec, tz = [None]*7
|
| 316 |
+
|
| 317 |
+
# loose regexp parse
|
| 318 |
+
m = ISO_DATE_RE.search(text)
|
| 319 |
+
if m is not None:
|
| 320 |
+
# XXX there's an extra bit of the timezone I'm ignoring here: is
|
| 321 |
+
# this the right thing to do?
|
| 322 |
+
yr, mon, day, hr, min, sec, tz, _ = m.groups()
|
| 323 |
+
else:
|
| 324 |
+
return None # bad format
|
| 325 |
+
|
| 326 |
+
return _str2time(day, mon, yr, hr, min, sec, tz)
|
| 327 |
+
|
| 328 |
+
|
| 329 |
+
# Header parsing
|
| 330 |
+
# -----------------------------------------------------------------------------
|
| 331 |
+
|
| 332 |
+
def unmatched(match):
|
| 333 |
+
"""Return unmatched part of re.Match object."""
|
| 334 |
+
start, end = match.span(0)
|
| 335 |
+
return match.string[:start]+match.string[end:]
|
| 336 |
+
|
| 337 |
+
HEADER_TOKEN_RE = re.compile(r"^\s*([^=\s;,]+)")
|
| 338 |
+
HEADER_QUOTED_VALUE_RE = re.compile(r"^\s*=\s*\"([^\"\\]*(?:\\.[^\"\\]*)*)\"")
|
| 339 |
+
HEADER_VALUE_RE = re.compile(r"^\s*=\s*([^\s;,]*)")
|
| 340 |
+
HEADER_ESCAPE_RE = re.compile(r"\\(.)")
|
| 341 |
+
def split_header_words(header_values):
|
| 342 |
+
r"""Parse header values into a list of lists containing key,value pairs.
|
| 343 |
+
|
| 344 |
+
The function knows how to deal with ",", ";" and "=" as well as quoted
|
| 345 |
+
values after "=". A list of space separated tokens are parsed as if they
|
| 346 |
+
were separated by ";".
|
| 347 |
+
|
| 348 |
+
If the header_values passed as argument contains multiple values, then they
|
| 349 |
+
are treated as if they were a single value separated by comma ",".
|
| 350 |
+
|
| 351 |
+
This means that this function is useful for parsing header fields that
|
| 352 |
+
follow this syntax (BNF as from the HTTP/1.1 specification, but we relax
|
| 353 |
+
the requirement for tokens).
|
| 354 |
+
|
| 355 |
+
headers = #header
|
| 356 |
+
header = (token | parameter) *( [";"] (token | parameter))
|
| 357 |
+
|
| 358 |
+
token = 1*<any CHAR except CTLs or separators>
|
| 359 |
+
separators = "(" | ")" | "<" | ">" | "@"
|
| 360 |
+
| "," | ";" | ":" | "\" | <">
|
| 361 |
+
| "/" | "[" | "]" | "?" | "="
|
| 362 |
+
| "{" | "}" | SP | HT
|
| 363 |
+
|
| 364 |
+
quoted-string = ( <"> *(qdtext | quoted-pair ) <"> )
|
| 365 |
+
qdtext = <any TEXT except <">>
|
| 366 |
+
quoted-pair = "\" CHAR
|
| 367 |
+
|
| 368 |
+
parameter = attribute "=" value
|
| 369 |
+
attribute = token
|
| 370 |
+
value = token | quoted-string
|
| 371 |
+
|
| 372 |
+
Each header is represented by a list of key/value pairs. The value for a
|
| 373 |
+
simple token (not part of a parameter) is None. Syntactically incorrect
|
| 374 |
+
headers will not necessarily be parsed as you would want.
|
| 375 |
+
|
| 376 |
+
This is easier to describe with some examples:
|
| 377 |
+
|
| 378 |
+
>>> split_header_words(['foo="bar"; port="80,81"; discard, bar=baz'])
|
| 379 |
+
[[('foo', 'bar'), ('port', '80,81'), ('discard', None)], [('bar', 'baz')]]
|
| 380 |
+
>>> split_header_words(['text/html; charset="iso-8859-1"'])
|
| 381 |
+
[[('text/html', None), ('charset', 'iso-8859-1')]]
|
| 382 |
+
>>> split_header_words([r'Basic realm="\"foo\bar\""'])
|
| 383 |
+
[[('Basic', None), ('realm', '"foobar"')]]
|
| 384 |
+
|
| 385 |
+
"""
|
| 386 |
+
assert not isinstance(header_values, str)
|
| 387 |
+
result = []
|
| 388 |
+
for text in header_values:
|
| 389 |
+
orig_text = text
|
| 390 |
+
pairs = []
|
| 391 |
+
while text:
|
| 392 |
+
m = HEADER_TOKEN_RE.search(text)
|
| 393 |
+
if m:
|
| 394 |
+
text = unmatched(m)
|
| 395 |
+
name = m.group(1)
|
| 396 |
+
m = HEADER_QUOTED_VALUE_RE.search(text)
|
| 397 |
+
if m: # quoted value
|
| 398 |
+
text = unmatched(m)
|
| 399 |
+
value = m.group(1)
|
| 400 |
+
value = HEADER_ESCAPE_RE.sub(r"\1", value)
|
| 401 |
+
else:
|
| 402 |
+
m = HEADER_VALUE_RE.search(text)
|
| 403 |
+
if m: # unquoted value
|
| 404 |
+
text = unmatched(m)
|
| 405 |
+
value = m.group(1)
|
| 406 |
+
value = value.rstrip()
|
| 407 |
+
else:
|
| 408 |
+
# no value, a lone token
|
| 409 |
+
value = None
|
| 410 |
+
pairs.append((name, value))
|
| 411 |
+
elif text.lstrip().startswith(","):
|
| 412 |
+
# concatenated headers, as per RFC 2616 section 4.2
|
| 413 |
+
text = text.lstrip()[1:]
|
| 414 |
+
if pairs: result.append(pairs)
|
| 415 |
+
pairs = []
|
| 416 |
+
else:
|
| 417 |
+
# skip junk
|
| 418 |
+
non_junk, nr_junk_chars = re.subn(r"^[=\s;]*", "", text)
|
| 419 |
+
assert nr_junk_chars > 0, (
|
| 420 |
+
"split_header_words bug: '%s', '%s', %s" %
|
| 421 |
+
(orig_text, text, pairs))
|
| 422 |
+
text = non_junk
|
| 423 |
+
if pairs: result.append(pairs)
|
| 424 |
+
return result
|
| 425 |
+
|
| 426 |
+
HEADER_JOIN_ESCAPE_RE = re.compile(r"([\"\\])")
|
| 427 |
+
def join_header_words(lists):
|
| 428 |
+
"""Do the inverse (almost) of the conversion done by split_header_words.
|
| 429 |
+
|
| 430 |
+
Takes a list of lists of (key, value) pairs and produces a single header
|
| 431 |
+
value. Attribute values are quoted if needed.
|
| 432 |
+
|
| 433 |
+
>>> join_header_words([[("text/plain", None), ("charset", "iso-8859-1")]])
|
| 434 |
+
'text/plain; charset="iso-8859-1"'
|
| 435 |
+
>>> join_header_words([[("text/plain", None)], [("charset", "iso-8859-1")]])
|
| 436 |
+
'text/plain, charset="iso-8859-1"'
|
| 437 |
+
|
| 438 |
+
"""
|
| 439 |
+
headers = []
|
| 440 |
+
for pairs in lists:
|
| 441 |
+
attr = []
|
| 442 |
+
for k, v in pairs:
|
| 443 |
+
if v is not None:
|
| 444 |
+
if not re.search(r"^\w+$", v):
|
| 445 |
+
v = HEADER_JOIN_ESCAPE_RE.sub(r"\\\1", v) # escape " and \
|
| 446 |
+
v = '"%s"' % v
|
| 447 |
+
k = "%s=%s" % (k, v)
|
| 448 |
+
attr.append(k)
|
| 449 |
+
if attr: headers.append("; ".join(attr))
|
| 450 |
+
return ", ".join(headers)
|
| 451 |
+
|
| 452 |
+
def strip_quotes(text):
|
| 453 |
+
if text.startswith('"'):
|
| 454 |
+
text = text[1:]
|
| 455 |
+
if text.endswith('"'):
|
| 456 |
+
text = text[:-1]
|
| 457 |
+
return text
|
| 458 |
+
|
| 459 |
+
def parse_ns_headers(ns_headers):
|
| 460 |
+
"""Ad-hoc parser for Netscape protocol cookie-attributes.
|
| 461 |
+
|
| 462 |
+
The old Netscape cookie format for Set-Cookie can for instance contain
|
| 463 |
+
an unquoted "," in the expires field, so we have to use this ad-hoc
|
| 464 |
+
parser instead of split_header_words.
|
| 465 |
+
|
| 466 |
+
XXX This may not make the best possible effort to parse all the crap
|
| 467 |
+
that Netscape Cookie headers contain. Ronald Tschalar's HTTPClient
|
| 468 |
+
parser is probably better, so could do worse than following that if
|
| 469 |
+
this ever gives any trouble.
|
| 470 |
+
|
| 471 |
+
Currently, this is also used for parsing RFC 2109 cookies.
|
| 472 |
+
|
| 473 |
+
"""
|
| 474 |
+
known_attrs = ("expires", "domain", "path", "secure",
|
| 475 |
+
# RFC 2109 attrs (may turn up in Netscape cookies, too)
|
| 476 |
+
"version", "port", "max-age")
|
| 477 |
+
|
| 478 |
+
result = []
|
| 479 |
+
for ns_header in ns_headers:
|
| 480 |
+
pairs = []
|
| 481 |
+
version_set = False
|
| 482 |
+
|
| 483 |
+
# XXX: The following does not strictly adhere to RFCs in that empty
|
| 484 |
+
# names and values are legal (the former will only appear once and will
|
| 485 |
+
# be overwritten if multiple occurrences are present). This is
|
| 486 |
+
# mostly to deal with backwards compatibility.
|
| 487 |
+
for ii, param in enumerate(ns_header.split(';')):
|
| 488 |
+
param = param.strip()
|
| 489 |
+
|
| 490 |
+
key, sep, val = param.partition('=')
|
| 491 |
+
key = key.strip()
|
| 492 |
+
|
| 493 |
+
if not key:
|
| 494 |
+
if ii == 0:
|
| 495 |
+
break
|
| 496 |
+
else:
|
| 497 |
+
continue
|
| 498 |
+
|
| 499 |
+
# allow for a distinction between present and empty and missing
|
| 500 |
+
# altogether
|
| 501 |
+
val = val.strip() if sep else None
|
| 502 |
+
|
| 503 |
+
if ii != 0:
|
| 504 |
+
lc = key.lower()
|
| 505 |
+
if lc in known_attrs:
|
| 506 |
+
key = lc
|
| 507 |
+
|
| 508 |
+
if key == "version":
|
| 509 |
+
# This is an RFC 2109 cookie.
|
| 510 |
+
if val is not None:
|
| 511 |
+
val = strip_quotes(val)
|
| 512 |
+
version_set = True
|
| 513 |
+
elif key == "expires":
|
| 514 |
+
# convert expires date to seconds since epoch
|
| 515 |
+
if val is not None:
|
| 516 |
+
val = http2time(strip_quotes(val)) # None if invalid
|
| 517 |
+
pairs.append((key, val))
|
| 518 |
+
|
| 519 |
+
if pairs:
|
| 520 |
+
if not version_set:
|
| 521 |
+
pairs.append(("version", "0"))
|
| 522 |
+
result.append(pairs)
|
| 523 |
+
|
| 524 |
+
return result
|
| 525 |
+
|
| 526 |
+
|
| 527 |
+
IPV4_RE = re.compile(r"\.\d+$", re.ASCII)
|
| 528 |
+
def is_HDN(text):
|
| 529 |
+
"""Return True if text is a host domain name."""
|
| 530 |
+
# XXX
|
| 531 |
+
# This may well be wrong. Which RFC is HDN defined in, if any (for
|
| 532 |
+
# the purposes of RFC 2965)?
|
| 533 |
+
# For the current implementation, what about IPv6? Remember to look
|
| 534 |
+
# at other uses of IPV4_RE also, if change this.
|
| 535 |
+
if IPV4_RE.search(text):
|
| 536 |
+
return False
|
| 537 |
+
if text == "":
|
| 538 |
+
return False
|
| 539 |
+
if text[0] == "." or text[-1] == ".":
|
| 540 |
+
return False
|
| 541 |
+
return True
|
| 542 |
+
|
| 543 |
+
def domain_match(A, B):
|
| 544 |
+
"""Return True if domain A domain-matches domain B, according to RFC 2965.
|
| 545 |
+
|
| 546 |
+
A and B may be host domain names or IP addresses.
|
| 547 |
+
|
| 548 |
+
RFC 2965, section 1:
|
| 549 |
+
|
| 550 |
+
Host names can be specified either as an IP address or a HDN string.
|
| 551 |
+
Sometimes we compare one host name with another. (Such comparisons SHALL
|
| 552 |
+
be case-insensitive.) Host A's name domain-matches host B's if
|
| 553 |
+
|
| 554 |
+
* their host name strings string-compare equal; or
|
| 555 |
+
|
| 556 |
+
* A is a HDN string and has the form NB, where N is a non-empty
|
| 557 |
+
name string, B has the form .B', and B' is a HDN string. (So,
|
| 558 |
+
x.y.com domain-matches .Y.com but not Y.com.)
|
| 559 |
+
|
| 560 |
+
Note that domain-match is not a commutative operation: a.b.c.com
|
| 561 |
+
domain-matches .c.com, but not the reverse.
|
| 562 |
+
|
| 563 |
+
"""
|
| 564 |
+
# Note that, if A or B are IP addresses, the only relevant part of the
|
| 565 |
+
# definition of the domain-match algorithm is the direct string-compare.
|
| 566 |
+
A = A.lower()
|
| 567 |
+
B = B.lower()
|
| 568 |
+
if A == B:
|
| 569 |
+
return True
|
| 570 |
+
if not is_HDN(A):
|
| 571 |
+
return False
|
| 572 |
+
i = A.rfind(B)
|
| 573 |
+
if i == -1 or i == 0:
|
| 574 |
+
# A does not have form NB, or N is the empty string
|
| 575 |
+
return False
|
| 576 |
+
if not B.startswith("."):
|
| 577 |
+
return False
|
| 578 |
+
if not is_HDN(B[1:]):
|
| 579 |
+
return False
|
| 580 |
+
return True
|
| 581 |
+
|
| 582 |
+
def liberal_is_HDN(text):
|
| 583 |
+
"""Return True if text is a sort-of-like a host domain name.
|
| 584 |
+
|
| 585 |
+
For accepting/blocking domains.
|
| 586 |
+
|
| 587 |
+
"""
|
| 588 |
+
if IPV4_RE.search(text):
|
| 589 |
+
return False
|
| 590 |
+
return True
|
| 591 |
+
|
| 592 |
+
def user_domain_match(A, B):
|
| 593 |
+
"""For blocking/accepting domains.
|
| 594 |
+
|
| 595 |
+
A and B may be host domain names or IP addresses.
|
| 596 |
+
|
| 597 |
+
"""
|
| 598 |
+
A = A.lower()
|
| 599 |
+
B = B.lower()
|
| 600 |
+
if not (liberal_is_HDN(A) and liberal_is_HDN(B)):
|
| 601 |
+
if A == B:
|
| 602 |
+
# equal IP addresses
|
| 603 |
+
return True
|
| 604 |
+
return False
|
| 605 |
+
initial_dot = B.startswith(".")
|
| 606 |
+
if initial_dot and A.endswith(B):
|
| 607 |
+
return True
|
| 608 |
+
if not initial_dot and A == B:
|
| 609 |
+
return True
|
| 610 |
+
return False
|
| 611 |
+
|
| 612 |
+
cut_port_re = re.compile(r":\d+$", re.ASCII)
|
| 613 |
+
def request_host(request):
|
| 614 |
+
"""Return request-host, as defined by RFC 2965.
|
| 615 |
+
|
| 616 |
+
Variation from RFC: returned value is lowercased, for convenient
|
| 617 |
+
comparison.
|
| 618 |
+
|
| 619 |
+
"""
|
| 620 |
+
url = request.get_full_url()
|
| 621 |
+
host = urllib.parse.urlparse(url)[1]
|
| 622 |
+
if host == "":
|
| 623 |
+
host = request.get_header("Host", "")
|
| 624 |
+
|
| 625 |
+
# remove port, if present
|
| 626 |
+
host = cut_port_re.sub("", host, 1)
|
| 627 |
+
return host.lower()
|
| 628 |
+
|
| 629 |
+
def eff_request_host(request):
|
| 630 |
+
"""Return a tuple (request-host, effective request-host name).
|
| 631 |
+
|
| 632 |
+
As defined by RFC 2965, except both are lowercased.
|
| 633 |
+
|
| 634 |
+
"""
|
| 635 |
+
erhn = req_host = request_host(request)
|
| 636 |
+
if req_host.find(".") == -1 and not IPV4_RE.search(req_host):
|
| 637 |
+
erhn = req_host + ".local"
|
| 638 |
+
return req_host, erhn
|
| 639 |
+
|
| 640 |
+
def request_path(request):
|
| 641 |
+
"""Path component of request-URI, as defined by RFC 2965."""
|
| 642 |
+
url = request.get_full_url()
|
| 643 |
+
parts = urllib.parse.urlsplit(url)
|
| 644 |
+
path = escape_path(parts.path)
|
| 645 |
+
if not path.startswith("/"):
|
| 646 |
+
# fix bad RFC 2396 absoluteURI
|
| 647 |
+
path = "/" + path
|
| 648 |
+
return path
|
| 649 |
+
|
| 650 |
+
def request_port(request):
|
| 651 |
+
host = request.host
|
| 652 |
+
i = host.find(':')
|
| 653 |
+
if i >= 0:
|
| 654 |
+
port = host[i+1:]
|
| 655 |
+
try:
|
| 656 |
+
int(port)
|
| 657 |
+
except ValueError:
|
| 658 |
+
_debug("nonnumeric port: '%s'", port)
|
| 659 |
+
return None
|
| 660 |
+
else:
|
| 661 |
+
port = DEFAULT_HTTP_PORT
|
| 662 |
+
return port
|
| 663 |
+
|
| 664 |
+
# Characters in addition to A-Z, a-z, 0-9, '_', '.', and '-' that don't
|
| 665 |
+
# need to be escaped to form a valid HTTP URL (RFCs 2396 and 1738).
|
| 666 |
+
HTTP_PATH_SAFE = "%/;:@&=+$,!~*'()"
|
| 667 |
+
ESCAPED_CHAR_RE = re.compile(r"%([0-9a-fA-F][0-9a-fA-F])")
|
| 668 |
+
def uppercase_escaped_char(match):
|
| 669 |
+
return "%%%s" % match.group(1).upper()
|
| 670 |
+
def escape_path(path):
|
| 671 |
+
"""Escape any invalid characters in HTTP URL, and uppercase all escapes."""
|
| 672 |
+
# There's no knowing what character encoding was used to create URLs
|
| 673 |
+
# containing %-escapes, but since we have to pick one to escape invalid
|
| 674 |
+
# path characters, we pick UTF-8, as recommended in the HTML 4.0
|
| 675 |
+
# specification:
|
| 676 |
+
# http://www.w3.org/TR/REC-html40/appendix/notes.html#h-B.2.1
|
| 677 |
+
# And here, kind of: draft-fielding-uri-rfc2396bis-03
|
| 678 |
+
# (And in draft IRI specification: draft-duerst-iri-05)
|
| 679 |
+
# (And here, for new URI schemes: RFC 2718)
|
| 680 |
+
path = urllib.parse.quote(path, HTTP_PATH_SAFE)
|
| 681 |
+
path = ESCAPED_CHAR_RE.sub(uppercase_escaped_char, path)
|
| 682 |
+
return path
|
| 683 |
+
|
| 684 |
+
def reach(h):
|
| 685 |
+
"""Return reach of host h, as defined by RFC 2965, section 1.
|
| 686 |
+
|
| 687 |
+
The reach R of a host name H is defined as follows:
|
| 688 |
+
|
| 689 |
+
* If
|
| 690 |
+
|
| 691 |
+
- H is the host domain name of a host; and,
|
| 692 |
+
|
| 693 |
+
- H has the form A.B; and
|
| 694 |
+
|
| 695 |
+
- A has no embedded (that is, interior) dots; and
|
| 696 |
+
|
| 697 |
+
- B has at least one embedded dot, or B is the string "local".
|
| 698 |
+
then the reach of H is .B.
|
| 699 |
+
|
| 700 |
+
* Otherwise, the reach of H is H.
|
| 701 |
+
|
| 702 |
+
>>> reach("www.acme.com")
|
| 703 |
+
'.acme.com'
|
| 704 |
+
>>> reach("acme.com")
|
| 705 |
+
'acme.com'
|
| 706 |
+
>>> reach("acme.local")
|
| 707 |
+
'.local'
|
| 708 |
+
|
| 709 |
+
"""
|
| 710 |
+
i = h.find(".")
|
| 711 |
+
if i >= 0:
|
| 712 |
+
#a = h[:i] # this line is only here to show what a is
|
| 713 |
+
b = h[i+1:]
|
| 714 |
+
i = b.find(".")
|
| 715 |
+
if is_HDN(h) and (i >= 0 or b == "local"):
|
| 716 |
+
return "."+b
|
| 717 |
+
return h
|
| 718 |
+
|
| 719 |
+
def is_third_party(request):
|
| 720 |
+
"""
|
| 721 |
+
|
| 722 |
+
RFC 2965, section 3.3.6:
|
| 723 |
+
|
| 724 |
+
An unverifiable transaction is to a third-party host if its request-
|
| 725 |
+
host U does not domain-match the reach R of the request-host O in the
|
| 726 |
+
origin transaction.
|
| 727 |
+
|
| 728 |
+
"""
|
| 729 |
+
req_host = request_host(request)
|
| 730 |
+
if not domain_match(req_host, reach(request.origin_req_host)):
|
| 731 |
+
return True
|
| 732 |
+
else:
|
| 733 |
+
return False
|
| 734 |
+
|
| 735 |
+
|
| 736 |
+
class Cookie:
|
| 737 |
+
"""HTTP Cookie.
|
| 738 |
+
|
| 739 |
+
This class represents both Netscape and RFC 2965 cookies.
|
| 740 |
+
|
| 741 |
+
This is deliberately a very simple class. It just holds attributes. It's
|
| 742 |
+
possible to construct Cookie instances that don't comply with the cookie
|
| 743 |
+
standards. CookieJar.make_cookies is the factory function for Cookie
|
| 744 |
+
objects -- it deals with cookie parsing, supplying defaults, and
|
| 745 |
+
normalising to the representation used in this class. CookiePolicy is
|
| 746 |
+
responsible for checking them to see whether they should be accepted from
|
| 747 |
+
and returned to the server.
|
| 748 |
+
|
| 749 |
+
Note that the port may be present in the headers, but unspecified ("Port"
|
| 750 |
+
rather than"Port=80", for example); if this is the case, port is None.
|
| 751 |
+
|
| 752 |
+
"""
|
| 753 |
+
|
| 754 |
+
def __init__(self, version, name, value,
|
| 755 |
+
port, port_specified,
|
| 756 |
+
domain, domain_specified, domain_initial_dot,
|
| 757 |
+
path, path_specified,
|
| 758 |
+
secure,
|
| 759 |
+
expires,
|
| 760 |
+
discard,
|
| 761 |
+
comment,
|
| 762 |
+
comment_url,
|
| 763 |
+
rest,
|
| 764 |
+
rfc2109=False,
|
| 765 |
+
):
|
| 766 |
+
|
| 767 |
+
if version is not None: version = int(version)
|
| 768 |
+
if expires is not None: expires = int(float(expires))
|
| 769 |
+
if port is None and port_specified is True:
|
| 770 |
+
raise ValueError("if port is None, port_specified must be false")
|
| 771 |
+
|
| 772 |
+
self.version = version
|
| 773 |
+
self.name = name
|
| 774 |
+
self.value = value
|
| 775 |
+
self.port = port
|
| 776 |
+
self.port_specified = port_specified
|
| 777 |
+
# normalise case, as per RFC 2965 section 3.3.3
|
| 778 |
+
self.domain = domain.lower()
|
| 779 |
+
self.domain_specified = domain_specified
|
| 780 |
+
# Sigh. We need to know whether the domain given in the
|
| 781 |
+
# cookie-attribute had an initial dot, in order to follow RFC 2965
|
| 782 |
+
# (as clarified in draft errata). Needed for the returned $Domain
|
| 783 |
+
# value.
|
| 784 |
+
self.domain_initial_dot = domain_initial_dot
|
| 785 |
+
self.path = path
|
| 786 |
+
self.path_specified = path_specified
|
| 787 |
+
self.secure = secure
|
| 788 |
+
self.expires = expires
|
| 789 |
+
self.discard = discard
|
| 790 |
+
self.comment = comment
|
| 791 |
+
self.comment_url = comment_url
|
| 792 |
+
self.rfc2109 = rfc2109
|
| 793 |
+
|
| 794 |
+
self._rest = copy.copy(rest)
|
| 795 |
+
|
| 796 |
+
def has_nonstandard_attr(self, name):
|
| 797 |
+
return name in self._rest
|
| 798 |
+
def get_nonstandard_attr(self, name, default=None):
|
| 799 |
+
return self._rest.get(name, default)
|
| 800 |
+
def set_nonstandard_attr(self, name, value):
|
| 801 |
+
self._rest[name] = value
|
| 802 |
+
|
| 803 |
+
def is_expired(self, now=None):
|
| 804 |
+
if now is None: now = time.time()
|
| 805 |
+
if (self.expires is not None) and (self.expires <= now):
|
| 806 |
+
return True
|
| 807 |
+
return False
|
| 808 |
+
|
| 809 |
+
def __str__(self):
|
| 810 |
+
if self.port is None: p = ""
|
| 811 |
+
else: p = ":"+self.port
|
| 812 |
+
limit = self.domain + p + self.path
|
| 813 |
+
if self.value is not None:
|
| 814 |
+
namevalue = "%s=%s" % (self.name, self.value)
|
| 815 |
+
else:
|
| 816 |
+
namevalue = self.name
|
| 817 |
+
return "<Cookie %s for %s>" % (namevalue, limit)
|
| 818 |
+
|
| 819 |
+
def __repr__(self):
|
| 820 |
+
args = []
|
| 821 |
+
for name in ("version", "name", "value",
|
| 822 |
+
"port", "port_specified",
|
| 823 |
+
"domain", "domain_specified", "domain_initial_dot",
|
| 824 |
+
"path", "path_specified",
|
| 825 |
+
"secure", "expires", "discard", "comment", "comment_url",
|
| 826 |
+
):
|
| 827 |
+
attr = getattr(self, name)
|
| 828 |
+
args.append("%s=%s" % (name, repr(attr)))
|
| 829 |
+
args.append("rest=%s" % repr(self._rest))
|
| 830 |
+
args.append("rfc2109=%s" % repr(self.rfc2109))
|
| 831 |
+
return "%s(%s)" % (self.__class__.__name__, ", ".join(args))
|
| 832 |
+
|
| 833 |
+
|
| 834 |
+
class CookiePolicy:
|
| 835 |
+
"""Defines which cookies get accepted from and returned to server.
|
| 836 |
+
|
| 837 |
+
May also modify cookies, though this is probably a bad idea.
|
| 838 |
+
|
| 839 |
+
The subclass DefaultCookiePolicy defines the standard rules for Netscape
|
| 840 |
+
and RFC 2965 cookies -- override that if you want a customized policy.
|
| 841 |
+
|
| 842 |
+
"""
|
| 843 |
+
def set_ok(self, cookie, request):
|
| 844 |
+
"""Return true if (and only if) cookie should be accepted from server.
|
| 845 |
+
|
| 846 |
+
Currently, pre-expired cookies never get this far -- the CookieJar
|
| 847 |
+
class deletes such cookies itself.
|
| 848 |
+
|
| 849 |
+
"""
|
| 850 |
+
raise NotImplementedError()
|
| 851 |
+
|
| 852 |
+
def return_ok(self, cookie, request):
|
| 853 |
+
"""Return true if (and only if) cookie should be returned to server."""
|
| 854 |
+
raise NotImplementedError()
|
| 855 |
+
|
| 856 |
+
def domain_return_ok(self, domain, request):
|
| 857 |
+
"""Return false if cookies should not be returned, given cookie domain.
|
| 858 |
+
"""
|
| 859 |
+
return True
|
| 860 |
+
|
| 861 |
+
def path_return_ok(self, path, request):
|
| 862 |
+
"""Return false if cookies should not be returned, given cookie path.
|
| 863 |
+
"""
|
| 864 |
+
return True
|
| 865 |
+
|
| 866 |
+
|
| 867 |
+
class DefaultCookiePolicy(CookiePolicy):
|
| 868 |
+
"""Implements the standard rules for accepting and returning cookies."""
|
| 869 |
+
|
| 870 |
+
DomainStrictNoDots = 1
|
| 871 |
+
DomainStrictNonDomain = 2
|
| 872 |
+
DomainRFC2965Match = 4
|
| 873 |
+
|
| 874 |
+
DomainLiberal = 0
|
| 875 |
+
DomainStrict = DomainStrictNoDots|DomainStrictNonDomain
|
| 876 |
+
|
| 877 |
+
def __init__(self,
|
| 878 |
+
blocked_domains=None, allowed_domains=None,
|
| 879 |
+
netscape=True, rfc2965=False,
|
| 880 |
+
rfc2109_as_netscape=None,
|
| 881 |
+
hide_cookie2=False,
|
| 882 |
+
strict_domain=False,
|
| 883 |
+
strict_rfc2965_unverifiable=True,
|
| 884 |
+
strict_ns_unverifiable=False,
|
| 885 |
+
strict_ns_domain=DomainLiberal,
|
| 886 |
+
strict_ns_set_initial_dollar=False,
|
| 887 |
+
strict_ns_set_path=False,
|
| 888 |
+
secure_protocols=("https", "wss")
|
| 889 |
+
):
|
| 890 |
+
"""Constructor arguments should be passed as keyword arguments only."""
|
| 891 |
+
self.netscape = netscape
|
| 892 |
+
self.rfc2965 = rfc2965
|
| 893 |
+
self.rfc2109_as_netscape = rfc2109_as_netscape
|
| 894 |
+
self.hide_cookie2 = hide_cookie2
|
| 895 |
+
self.strict_domain = strict_domain
|
| 896 |
+
self.strict_rfc2965_unverifiable = strict_rfc2965_unverifiable
|
| 897 |
+
self.strict_ns_unverifiable = strict_ns_unverifiable
|
| 898 |
+
self.strict_ns_domain = strict_ns_domain
|
| 899 |
+
self.strict_ns_set_initial_dollar = strict_ns_set_initial_dollar
|
| 900 |
+
self.strict_ns_set_path = strict_ns_set_path
|
| 901 |
+
self.secure_protocols = secure_protocols
|
| 902 |
+
|
| 903 |
+
if blocked_domains is not None:
|
| 904 |
+
self._blocked_domains = tuple(blocked_domains)
|
| 905 |
+
else:
|
| 906 |
+
self._blocked_domains = ()
|
| 907 |
+
|
| 908 |
+
if allowed_domains is not None:
|
| 909 |
+
allowed_domains = tuple(allowed_domains)
|
| 910 |
+
self._allowed_domains = allowed_domains
|
| 911 |
+
|
| 912 |
+
def blocked_domains(self):
|
| 913 |
+
"""Return the sequence of blocked domains (as a tuple)."""
|
| 914 |
+
return self._blocked_domains
|
| 915 |
+
def set_blocked_domains(self, blocked_domains):
|
| 916 |
+
"""Set the sequence of blocked domains."""
|
| 917 |
+
self._blocked_domains = tuple(blocked_domains)
|
| 918 |
+
|
| 919 |
+
def is_blocked(self, domain):
|
| 920 |
+
for blocked_domain in self._blocked_domains:
|
| 921 |
+
if user_domain_match(domain, blocked_domain):
|
| 922 |
+
return True
|
| 923 |
+
return False
|
| 924 |
+
|
| 925 |
+
def allowed_domains(self):
|
| 926 |
+
"""Return None, or the sequence of allowed domains (as a tuple)."""
|
| 927 |
+
return self._allowed_domains
|
| 928 |
+
def set_allowed_domains(self, allowed_domains):
|
| 929 |
+
"""Set the sequence of allowed domains, or None."""
|
| 930 |
+
if allowed_domains is not None:
|
| 931 |
+
allowed_domains = tuple(allowed_domains)
|
| 932 |
+
self._allowed_domains = allowed_domains
|
| 933 |
+
|
| 934 |
+
def is_not_allowed(self, domain):
|
| 935 |
+
if self._allowed_domains is None:
|
| 936 |
+
return False
|
| 937 |
+
for allowed_domain in self._allowed_domains:
|
| 938 |
+
if user_domain_match(domain, allowed_domain):
|
| 939 |
+
return False
|
| 940 |
+
return True
|
| 941 |
+
|
| 942 |
+
def set_ok(self, cookie, request):
|
| 943 |
+
"""
|
| 944 |
+
If you override .set_ok(), be sure to call this method. If it returns
|
| 945 |
+
false, so should your subclass (assuming your subclass wants to be more
|
| 946 |
+
strict about which cookies to accept).
|
| 947 |
+
|
| 948 |
+
"""
|
| 949 |
+
_debug(" - checking cookie %s=%s", cookie.name, cookie.value)
|
| 950 |
+
|
| 951 |
+
assert cookie.name is not None
|
| 952 |
+
|
| 953 |
+
for n in "version", "verifiability", "name", "path", "domain", "port":
|
| 954 |
+
fn_name = "set_ok_"+n
|
| 955 |
+
fn = getattr(self, fn_name)
|
| 956 |
+
if not fn(cookie, request):
|
| 957 |
+
return False
|
| 958 |
+
|
| 959 |
+
return True
|
| 960 |
+
|
| 961 |
+
def set_ok_version(self, cookie, request):
|
| 962 |
+
if cookie.version is None:
|
| 963 |
+
# Version is always set to 0 by parse_ns_headers if it's a Netscape
|
| 964 |
+
# cookie, so this must be an invalid RFC 2965 cookie.
|
| 965 |
+
_debug(" Set-Cookie2 without version attribute (%s=%s)",
|
| 966 |
+
cookie.name, cookie.value)
|
| 967 |
+
return False
|
| 968 |
+
if cookie.version > 0 and not self.rfc2965:
|
| 969 |
+
_debug(" RFC 2965 cookies are switched off")
|
| 970 |
+
return False
|
| 971 |
+
elif cookie.version == 0 and not self.netscape:
|
| 972 |
+
_debug(" Netscape cookies are switched off")
|
| 973 |
+
return False
|
| 974 |
+
return True
|
| 975 |
+
|
| 976 |
+
def set_ok_verifiability(self, cookie, request):
|
| 977 |
+
if request.unverifiable and is_third_party(request):
|
| 978 |
+
if cookie.version > 0 and self.strict_rfc2965_unverifiable:
|
| 979 |
+
_debug(" third-party RFC 2965 cookie during "
|
| 980 |
+
"unverifiable transaction")
|
| 981 |
+
return False
|
| 982 |
+
elif cookie.version == 0 and self.strict_ns_unverifiable:
|
| 983 |
+
_debug(" third-party Netscape cookie during "
|
| 984 |
+
"unverifiable transaction")
|
| 985 |
+
return False
|
| 986 |
+
return True
|
| 987 |
+
|
| 988 |
+
def set_ok_name(self, cookie, request):
|
| 989 |
+
# Try and stop servers setting V0 cookies designed to hack other
|
| 990 |
+
# servers that know both V0 and V1 protocols.
|
| 991 |
+
if (cookie.version == 0 and self.strict_ns_set_initial_dollar and
|
| 992 |
+
cookie.name.startswith("$")):
|
| 993 |
+
_debug(" illegal name (starts with '$'): '%s'", cookie.name)
|
| 994 |
+
return False
|
| 995 |
+
return True
|
| 996 |
+
|
| 997 |
+
def set_ok_path(self, cookie, request):
|
| 998 |
+
if cookie.path_specified:
|
| 999 |
+
req_path = request_path(request)
|
| 1000 |
+
if ((cookie.version > 0 or
|
| 1001 |
+
(cookie.version == 0 and self.strict_ns_set_path)) and
|
| 1002 |
+
not self.path_return_ok(cookie.path, request)):
|
| 1003 |
+
_debug(" path attribute %s is not a prefix of request "
|
| 1004 |
+
"path %s", cookie.path, req_path)
|
| 1005 |
+
return False
|
| 1006 |
+
return True
|
| 1007 |
+
|
| 1008 |
+
def set_ok_domain(self, cookie, request):
|
| 1009 |
+
if self.is_blocked(cookie.domain):
|
| 1010 |
+
_debug(" domain %s is in user block-list", cookie.domain)
|
| 1011 |
+
return False
|
| 1012 |
+
if self.is_not_allowed(cookie.domain):
|
| 1013 |
+
_debug(" domain %s is not in user allow-list", cookie.domain)
|
| 1014 |
+
return False
|
| 1015 |
+
if cookie.domain_specified:
|
| 1016 |
+
req_host, erhn = eff_request_host(request)
|
| 1017 |
+
domain = cookie.domain
|
| 1018 |
+
if self.strict_domain and (domain.count(".") >= 2):
|
| 1019 |
+
# XXX This should probably be compared with the Konqueror
|
| 1020 |
+
# (kcookiejar.cpp) and Mozilla implementations, but it's a
|
| 1021 |
+
# losing battle.
|
| 1022 |
+
i = domain.rfind(".")
|
| 1023 |
+
j = domain.rfind(".", 0, i)
|
| 1024 |
+
if j == 0: # domain like .foo.bar
|
| 1025 |
+
tld = domain[i+1:]
|
| 1026 |
+
sld = domain[j+1:i]
|
| 1027 |
+
if sld.lower() in ("co", "ac", "com", "edu", "org", "net",
|
| 1028 |
+
"gov", "mil", "int", "aero", "biz", "cat", "coop",
|
| 1029 |
+
"info", "jobs", "mobi", "museum", "name", "pro",
|
| 1030 |
+
"travel", "eu") and len(tld) == 2:
|
| 1031 |
+
# domain like .co.uk
|
| 1032 |
+
_debug(" country-code second level domain %s", domain)
|
| 1033 |
+
return False
|
| 1034 |
+
if domain.startswith("."):
|
| 1035 |
+
undotted_domain = domain[1:]
|
| 1036 |
+
else:
|
| 1037 |
+
undotted_domain = domain
|
| 1038 |
+
embedded_dots = (undotted_domain.find(".") >= 0)
|
| 1039 |
+
if not embedded_dots and domain != ".local":
|
| 1040 |
+
_debug(" non-local domain %s contains no embedded dot",
|
| 1041 |
+
domain)
|
| 1042 |
+
return False
|
| 1043 |
+
if cookie.version == 0:
|
| 1044 |
+
if (not erhn.endswith(domain) and
|
| 1045 |
+
(not erhn.startswith(".") and
|
| 1046 |
+
not ("."+erhn).endswith(domain))):
|
| 1047 |
+
_debug(" effective request-host %s (even with added "
|
| 1048 |
+
"initial dot) does not end with %s",
|
| 1049 |
+
erhn, domain)
|
| 1050 |
+
return False
|
| 1051 |
+
if (cookie.version > 0 or
|
| 1052 |
+
(self.strict_ns_domain & self.DomainRFC2965Match)):
|
| 1053 |
+
if not domain_match(erhn, domain):
|
| 1054 |
+
_debug(" effective request-host %s does not domain-match "
|
| 1055 |
+
"%s", erhn, domain)
|
| 1056 |
+
return False
|
| 1057 |
+
if (cookie.version > 0 or
|
| 1058 |
+
(self.strict_ns_domain & self.DomainStrictNoDots)):
|
| 1059 |
+
host_prefix = req_host[:-len(domain)]
|
| 1060 |
+
if (host_prefix.find(".") >= 0 and
|
| 1061 |
+
not IPV4_RE.search(req_host)):
|
| 1062 |
+
_debug(" host prefix %s for domain %s contains a dot",
|
| 1063 |
+
host_prefix, domain)
|
| 1064 |
+
return False
|
| 1065 |
+
return True
|
| 1066 |
+
|
| 1067 |
+
def set_ok_port(self, cookie, request):
|
| 1068 |
+
if cookie.port_specified:
|
| 1069 |
+
req_port = request_port(request)
|
| 1070 |
+
if req_port is None:
|
| 1071 |
+
req_port = "80"
|
| 1072 |
+
else:
|
| 1073 |
+
req_port = str(req_port)
|
| 1074 |
+
for p in cookie.port.split(","):
|
| 1075 |
+
try:
|
| 1076 |
+
int(p)
|
| 1077 |
+
except ValueError:
|
| 1078 |
+
_debug(" bad port %s (not numeric)", p)
|
| 1079 |
+
return False
|
| 1080 |
+
if p == req_port:
|
| 1081 |
+
break
|
| 1082 |
+
else:
|
| 1083 |
+
_debug(" request port (%s) not found in %s",
|
| 1084 |
+
req_port, cookie.port)
|
| 1085 |
+
return False
|
| 1086 |
+
return True
|
| 1087 |
+
|
| 1088 |
+
def return_ok(self, cookie, request):
|
| 1089 |
+
"""
|
| 1090 |
+
If you override .return_ok(), be sure to call this method. If it
|
| 1091 |
+
returns false, so should your subclass (assuming your subclass wants to
|
| 1092 |
+
be more strict about which cookies to return).
|
| 1093 |
+
|
| 1094 |
+
"""
|
| 1095 |
+
# Path has already been checked by .path_return_ok(), and domain
|
| 1096 |
+
# blocking done by .domain_return_ok().
|
| 1097 |
+
_debug(" - checking cookie %s=%s", cookie.name, cookie.value)
|
| 1098 |
+
|
| 1099 |
+
for n in "version", "verifiability", "secure", "expires", "port", "domain":
|
| 1100 |
+
fn_name = "return_ok_"+n
|
| 1101 |
+
fn = getattr(self, fn_name)
|
| 1102 |
+
if not fn(cookie, request):
|
| 1103 |
+
return False
|
| 1104 |
+
return True
|
| 1105 |
+
|
| 1106 |
+
def return_ok_version(self, cookie, request):
|
| 1107 |
+
if cookie.version > 0 and not self.rfc2965:
|
| 1108 |
+
_debug(" RFC 2965 cookies are switched off")
|
| 1109 |
+
return False
|
| 1110 |
+
elif cookie.version == 0 and not self.netscape:
|
| 1111 |
+
_debug(" Netscape cookies are switched off")
|
| 1112 |
+
return False
|
| 1113 |
+
return True
|
| 1114 |
+
|
| 1115 |
+
def return_ok_verifiability(self, cookie, request):
|
| 1116 |
+
if request.unverifiable and is_third_party(request):
|
| 1117 |
+
if cookie.version > 0 and self.strict_rfc2965_unverifiable:
|
| 1118 |
+
_debug(" third-party RFC 2965 cookie during unverifiable "
|
| 1119 |
+
"transaction")
|
| 1120 |
+
return False
|
| 1121 |
+
elif cookie.version == 0 and self.strict_ns_unverifiable:
|
| 1122 |
+
_debug(" third-party Netscape cookie during unverifiable "
|
| 1123 |
+
"transaction")
|
| 1124 |
+
return False
|
| 1125 |
+
return True
|
| 1126 |
+
|
| 1127 |
+
def return_ok_secure(self, cookie, request):
|
| 1128 |
+
if cookie.secure and request.type not in self.secure_protocols:
|
| 1129 |
+
_debug(" secure cookie with non-secure request")
|
| 1130 |
+
return False
|
| 1131 |
+
return True
|
| 1132 |
+
|
| 1133 |
+
def return_ok_expires(self, cookie, request):
|
| 1134 |
+
if cookie.is_expired(self._now):
|
| 1135 |
+
_debug(" cookie expired")
|
| 1136 |
+
return False
|
| 1137 |
+
return True
|
| 1138 |
+
|
| 1139 |
+
def return_ok_port(self, cookie, request):
|
| 1140 |
+
if cookie.port:
|
| 1141 |
+
req_port = request_port(request)
|
| 1142 |
+
if req_port is None:
|
| 1143 |
+
req_port = "80"
|
| 1144 |
+
for p in cookie.port.split(","):
|
| 1145 |
+
if p == req_port:
|
| 1146 |
+
break
|
| 1147 |
+
else:
|
| 1148 |
+
_debug(" request port %s does not match cookie port %s",
|
| 1149 |
+
req_port, cookie.port)
|
| 1150 |
+
return False
|
| 1151 |
+
return True
|
| 1152 |
+
|
| 1153 |
+
def return_ok_domain(self, cookie, request):
|
| 1154 |
+
req_host, erhn = eff_request_host(request)
|
| 1155 |
+
domain = cookie.domain
|
| 1156 |
+
|
| 1157 |
+
if domain and not domain.startswith("."):
|
| 1158 |
+
dotdomain = "." + domain
|
| 1159 |
+
else:
|
| 1160 |
+
dotdomain = domain
|
| 1161 |
+
|
| 1162 |
+
# strict check of non-domain cookies: Mozilla does this, MSIE5 doesn't
|
| 1163 |
+
if (cookie.version == 0 and
|
| 1164 |
+
(self.strict_ns_domain & self.DomainStrictNonDomain) and
|
| 1165 |
+
not cookie.domain_specified and domain != erhn):
|
| 1166 |
+
_debug(" cookie with unspecified domain does not string-compare "
|
| 1167 |
+
"equal to request domain")
|
| 1168 |
+
return False
|
| 1169 |
+
|
| 1170 |
+
if cookie.version > 0 and not domain_match(erhn, domain):
|
| 1171 |
+
_debug(" effective request-host name %s does not domain-match "
|
| 1172 |
+
"RFC 2965 cookie domain %s", erhn, domain)
|
| 1173 |
+
return False
|
| 1174 |
+
if cookie.version == 0 and not ("."+erhn).endswith(dotdomain):
|
| 1175 |
+
_debug(" request-host %s does not match Netscape cookie domain "
|
| 1176 |
+
"%s", req_host, domain)
|
| 1177 |
+
return False
|
| 1178 |
+
return True
|
| 1179 |
+
|
| 1180 |
+
def domain_return_ok(self, domain, request):
|
| 1181 |
+
# Liberal check of. This is here as an optimization to avoid
|
| 1182 |
+
# having to load lots of MSIE cookie files unless necessary.
|
| 1183 |
+
req_host, erhn = eff_request_host(request)
|
| 1184 |
+
if not req_host.startswith("."):
|
| 1185 |
+
req_host = "."+req_host
|
| 1186 |
+
if not erhn.startswith("."):
|
| 1187 |
+
erhn = "."+erhn
|
| 1188 |
+
if domain and not domain.startswith("."):
|
| 1189 |
+
dotdomain = "." + domain
|
| 1190 |
+
else:
|
| 1191 |
+
dotdomain = domain
|
| 1192 |
+
if not (req_host.endswith(dotdomain) or erhn.endswith(dotdomain)):
|
| 1193 |
+
#_debug(" request domain %s does not match cookie domain %s",
|
| 1194 |
+
# req_host, domain)
|
| 1195 |
+
return False
|
| 1196 |
+
|
| 1197 |
+
if self.is_blocked(domain):
|
| 1198 |
+
_debug(" domain %s is in user block-list", domain)
|
| 1199 |
+
return False
|
| 1200 |
+
if self.is_not_allowed(domain):
|
| 1201 |
+
_debug(" domain %s is not in user allow-list", domain)
|
| 1202 |
+
return False
|
| 1203 |
+
|
| 1204 |
+
return True
|
| 1205 |
+
|
| 1206 |
+
def path_return_ok(self, path, request):
|
| 1207 |
+
_debug("- checking cookie path=%s", path)
|
| 1208 |
+
req_path = request_path(request)
|
| 1209 |
+
pathlen = len(path)
|
| 1210 |
+
if req_path == path:
|
| 1211 |
+
return True
|
| 1212 |
+
elif (req_path.startswith(path) and
|
| 1213 |
+
(path.endswith("/") or req_path[pathlen:pathlen+1] == "/")):
|
| 1214 |
+
return True
|
| 1215 |
+
|
| 1216 |
+
_debug(" %s does not path-match %s", req_path, path)
|
| 1217 |
+
return False
|
| 1218 |
+
|
| 1219 |
+
def vals_sorted_by_key(adict):
|
| 1220 |
+
keys = sorted(adict.keys())
|
| 1221 |
+
return map(adict.get, keys)
|
| 1222 |
+
|
| 1223 |
+
def deepvalues(mapping):
|
| 1224 |
+
"""Iterates over nested mapping, depth-first, in sorted order by key."""
|
| 1225 |
+
values = vals_sorted_by_key(mapping)
|
| 1226 |
+
for obj in values:
|
| 1227 |
+
mapping = False
|
| 1228 |
+
try:
|
| 1229 |
+
obj.items
|
| 1230 |
+
except AttributeError:
|
| 1231 |
+
pass
|
| 1232 |
+
else:
|
| 1233 |
+
mapping = True
|
| 1234 |
+
yield from deepvalues(obj)
|
| 1235 |
+
if not mapping:
|
| 1236 |
+
yield obj
|
| 1237 |
+
|
| 1238 |
+
|
| 1239 |
+
# Used as second parameter to dict.get() method, to distinguish absent
|
| 1240 |
+
# dict key from one with a None value.
|
| 1241 |
+
class Absent: pass
|
| 1242 |
+
|
| 1243 |
+
class CookieJar:
|
| 1244 |
+
"""Collection of HTTP cookies.
|
| 1245 |
+
|
| 1246 |
+
You may not need to know about this class: try
|
| 1247 |
+
urllib.request.build_opener(HTTPCookieProcessor).open(url).
|
| 1248 |
+
"""
|
| 1249 |
+
|
| 1250 |
+
non_word_re = re.compile(r"\W")
|
| 1251 |
+
quote_re = re.compile(r"([\"\\])")
|
| 1252 |
+
strict_domain_re = re.compile(r"\.?[^.]*")
|
| 1253 |
+
domain_re = re.compile(r"[^.]*")
|
| 1254 |
+
dots_re = re.compile(r"^\.+")
|
| 1255 |
+
|
| 1256 |
+
magic_re = re.compile(r"^\#LWP-Cookies-(\d+\.\d+)", re.ASCII)
|
| 1257 |
+
|
| 1258 |
+
def __init__(self, policy=None):
|
| 1259 |
+
if policy is None:
|
| 1260 |
+
policy = DefaultCookiePolicy()
|
| 1261 |
+
self._policy = policy
|
| 1262 |
+
|
| 1263 |
+
self._cookies_lock = _threading.RLock()
|
| 1264 |
+
self._cookies = {}
|
| 1265 |
+
|
| 1266 |
+
def set_policy(self, policy):
|
| 1267 |
+
self._policy = policy
|
| 1268 |
+
|
| 1269 |
+
def _cookies_for_domain(self, domain, request):
|
| 1270 |
+
cookies = []
|
| 1271 |
+
if not self._policy.domain_return_ok(domain, request):
|
| 1272 |
+
return []
|
| 1273 |
+
_debug("Checking %s for cookies to return", domain)
|
| 1274 |
+
cookies_by_path = self._cookies[domain]
|
| 1275 |
+
for path in cookies_by_path.keys():
|
| 1276 |
+
if not self._policy.path_return_ok(path, request):
|
| 1277 |
+
continue
|
| 1278 |
+
cookies_by_name = cookies_by_path[path]
|
| 1279 |
+
for cookie in cookies_by_name.values():
|
| 1280 |
+
if not self._policy.return_ok(cookie, request):
|
| 1281 |
+
_debug(" not returning cookie")
|
| 1282 |
+
continue
|
| 1283 |
+
_debug(" it's a match")
|
| 1284 |
+
cookies.append(cookie)
|
| 1285 |
+
return cookies
|
| 1286 |
+
|
| 1287 |
+
def _cookies_for_request(self, request):
|
| 1288 |
+
"""Return a list of cookies to be returned to server."""
|
| 1289 |
+
cookies = []
|
| 1290 |
+
for domain in self._cookies.keys():
|
| 1291 |
+
cookies.extend(self._cookies_for_domain(domain, request))
|
| 1292 |
+
return cookies
|
| 1293 |
+
|
| 1294 |
+
def _cookie_attrs(self, cookies):
|
| 1295 |
+
"""Return a list of cookie-attributes to be returned to server.
|
| 1296 |
+
|
| 1297 |
+
like ['foo="bar"; $Path="/"', ...]
|
| 1298 |
+
|
| 1299 |
+
The $Version attribute is also added when appropriate (currently only
|
| 1300 |
+
once per request).
|
| 1301 |
+
|
| 1302 |
+
"""
|
| 1303 |
+
# add cookies in order of most specific (ie. longest) path first
|
| 1304 |
+
cookies.sort(key=lambda a: len(a.path), reverse=True)
|
| 1305 |
+
|
| 1306 |
+
version_set = False
|
| 1307 |
+
|
| 1308 |
+
attrs = []
|
| 1309 |
+
for cookie in cookies:
|
| 1310 |
+
# set version of Cookie header
|
| 1311 |
+
# XXX
|
| 1312 |
+
# What should it be if multiple matching Set-Cookie headers have
|
| 1313 |
+
# different versions themselves?
|
| 1314 |
+
# Answer: there is no answer; was supposed to be settled by
|
| 1315 |
+
# RFC 2965 errata, but that may never appear...
|
| 1316 |
+
version = cookie.version
|
| 1317 |
+
if not version_set:
|
| 1318 |
+
version_set = True
|
| 1319 |
+
if version > 0:
|
| 1320 |
+
attrs.append("$Version=%s" % version)
|
| 1321 |
+
|
| 1322 |
+
# quote cookie value if necessary
|
| 1323 |
+
# (not for Netscape protocol, which already has any quotes
|
| 1324 |
+
# intact, due to the poorly-specified Netscape Cookie: syntax)
|
| 1325 |
+
if ((cookie.value is not None) and
|
| 1326 |
+
self.non_word_re.search(cookie.value) and version > 0):
|
| 1327 |
+
value = self.quote_re.sub(r"\\\1", cookie.value)
|
| 1328 |
+
else:
|
| 1329 |
+
value = cookie.value
|
| 1330 |
+
|
| 1331 |
+
# add cookie-attributes to be returned in Cookie header
|
| 1332 |
+
if cookie.value is None:
|
| 1333 |
+
attrs.append(cookie.name)
|
| 1334 |
+
else:
|
| 1335 |
+
attrs.append("%s=%s" % (cookie.name, value))
|
| 1336 |
+
if version > 0:
|
| 1337 |
+
if cookie.path_specified:
|
| 1338 |
+
attrs.append('$Path="%s"' % cookie.path)
|
| 1339 |
+
if cookie.domain.startswith("."):
|
| 1340 |
+
domain = cookie.domain
|
| 1341 |
+
if (not cookie.domain_initial_dot and
|
| 1342 |
+
domain.startswith(".")):
|
| 1343 |
+
domain = domain[1:]
|
| 1344 |
+
attrs.append('$Domain="%s"' % domain)
|
| 1345 |
+
if cookie.port is not None:
|
| 1346 |
+
p = "$Port"
|
| 1347 |
+
if cookie.port_specified:
|
| 1348 |
+
p = p + ('="%s"' % cookie.port)
|
| 1349 |
+
attrs.append(p)
|
| 1350 |
+
|
| 1351 |
+
return attrs
|
| 1352 |
+
|
| 1353 |
+
def add_cookie_header(self, request):
|
| 1354 |
+
"""Add correct Cookie: header to request (urllib.request.Request object).
|
| 1355 |
+
|
| 1356 |
+
The Cookie2 header is also added unless policy.hide_cookie2 is true.
|
| 1357 |
+
|
| 1358 |
+
"""
|
| 1359 |
+
_debug("add_cookie_header")
|
| 1360 |
+
self._cookies_lock.acquire()
|
| 1361 |
+
try:
|
| 1362 |
+
|
| 1363 |
+
self._policy._now = self._now = int(time.time())
|
| 1364 |
+
|
| 1365 |
+
cookies = self._cookies_for_request(request)
|
| 1366 |
+
|
| 1367 |
+
attrs = self._cookie_attrs(cookies)
|
| 1368 |
+
if attrs:
|
| 1369 |
+
if not request.has_header("Cookie"):
|
| 1370 |
+
request.add_unredirected_header(
|
| 1371 |
+
"Cookie", "; ".join(attrs))
|
| 1372 |
+
|
| 1373 |
+
# if necessary, advertise that we know RFC 2965
|
| 1374 |
+
if (self._policy.rfc2965 and not self._policy.hide_cookie2 and
|
| 1375 |
+
not request.has_header("Cookie2")):
|
| 1376 |
+
for cookie in cookies:
|
| 1377 |
+
if cookie.version != 1:
|
| 1378 |
+
request.add_unredirected_header("Cookie2", '$Version="1"')
|
| 1379 |
+
break
|
| 1380 |
+
|
| 1381 |
+
finally:
|
| 1382 |
+
self._cookies_lock.release()
|
| 1383 |
+
|
| 1384 |
+
self.clear_expired_cookies()
|
| 1385 |
+
|
| 1386 |
+
def _normalized_cookie_tuples(self, attrs_set):
|
| 1387 |
+
"""Return list of tuples containing normalised cookie information.
|
| 1388 |
+
|
| 1389 |
+
attrs_set is the list of lists of key,value pairs extracted from
|
| 1390 |
+
the Set-Cookie or Set-Cookie2 headers.
|
| 1391 |
+
|
| 1392 |
+
Tuples are name, value, standard, rest, where name and value are the
|
| 1393 |
+
cookie name and value, standard is a dictionary containing the standard
|
| 1394 |
+
cookie-attributes (discard, secure, version, expires or max-age,
|
| 1395 |
+
domain, path and port) and rest is a dictionary containing the rest of
|
| 1396 |
+
the cookie-attributes.
|
| 1397 |
+
|
| 1398 |
+
"""
|
| 1399 |
+
cookie_tuples = []
|
| 1400 |
+
|
| 1401 |
+
boolean_attrs = "discard", "secure"
|
| 1402 |
+
value_attrs = ("version",
|
| 1403 |
+
"expires", "max-age",
|
| 1404 |
+
"domain", "path", "port",
|
| 1405 |
+
"comment", "commenturl")
|
| 1406 |
+
|
| 1407 |
+
for cookie_attrs in attrs_set:
|
| 1408 |
+
name, value = cookie_attrs[0]
|
| 1409 |
+
|
| 1410 |
+
# Build dictionary of standard cookie-attributes (standard) and
|
| 1411 |
+
# dictionary of other cookie-attributes (rest).
|
| 1412 |
+
|
| 1413 |
+
# Note: expiry time is normalised to seconds since epoch. V0
|
| 1414 |
+
# cookies should have the Expires cookie-attribute, and V1 cookies
|
| 1415 |
+
# should have Max-Age, but since V1 includes RFC 2109 cookies (and
|
| 1416 |
+
# since V0 cookies may be a mish-mash of Netscape and RFC 2109), we
|
| 1417 |
+
# accept either (but prefer Max-Age).
|
| 1418 |
+
max_age_set = False
|
| 1419 |
+
|
| 1420 |
+
bad_cookie = False
|
| 1421 |
+
|
| 1422 |
+
standard = {}
|
| 1423 |
+
rest = {}
|
| 1424 |
+
for k, v in cookie_attrs[1:]:
|
| 1425 |
+
lc = k.lower()
|
| 1426 |
+
# don't lose case distinction for unknown fields
|
| 1427 |
+
if lc in value_attrs or lc in boolean_attrs:
|
| 1428 |
+
k = lc
|
| 1429 |
+
if k in boolean_attrs and v is None:
|
| 1430 |
+
# boolean cookie-attribute is present, but has no value
|
| 1431 |
+
# (like "discard", rather than "port=80")
|
| 1432 |
+
v = True
|
| 1433 |
+
if k in standard:
|
| 1434 |
+
# only first value is significant
|
| 1435 |
+
continue
|
| 1436 |
+
if k == "domain":
|
| 1437 |
+
if v is None:
|
| 1438 |
+
_debug(" missing value for domain attribute")
|
| 1439 |
+
bad_cookie = True
|
| 1440 |
+
break
|
| 1441 |
+
# RFC 2965 section 3.3.3
|
| 1442 |
+
v = v.lower()
|
| 1443 |
+
if k == "expires":
|
| 1444 |
+
if max_age_set:
|
| 1445 |
+
# Prefer max-age to expires (like Mozilla)
|
| 1446 |
+
continue
|
| 1447 |
+
if v is None:
|
| 1448 |
+
_debug(" missing or invalid value for expires "
|
| 1449 |
+
"attribute: treating as session cookie")
|
| 1450 |
+
continue
|
| 1451 |
+
if k == "max-age":
|
| 1452 |
+
max_age_set = True
|
| 1453 |
+
try:
|
| 1454 |
+
v = int(v)
|
| 1455 |
+
except ValueError:
|
| 1456 |
+
_debug(" missing or invalid (non-numeric) value for "
|
| 1457 |
+
"max-age attribute")
|
| 1458 |
+
bad_cookie = True
|
| 1459 |
+
break
|
| 1460 |
+
# convert RFC 2965 Max-Age to seconds since epoch
|
| 1461 |
+
# XXX Strictly you're supposed to follow RFC 2616
|
| 1462 |
+
# age-calculation rules. Remember that zero Max-Age
|
| 1463 |
+
# is a request to discard (old and new) cookie, though.
|
| 1464 |
+
k = "expires"
|
| 1465 |
+
v = self._now + v
|
| 1466 |
+
if (k in value_attrs) or (k in boolean_attrs):
|
| 1467 |
+
if (v is None and
|
| 1468 |
+
k not in ("port", "comment", "commenturl")):
|
| 1469 |
+
_debug(" missing value for %s attribute" % k)
|
| 1470 |
+
bad_cookie = True
|
| 1471 |
+
break
|
| 1472 |
+
standard[k] = v
|
| 1473 |
+
else:
|
| 1474 |
+
rest[k] = v
|
| 1475 |
+
|
| 1476 |
+
if bad_cookie:
|
| 1477 |
+
continue
|
| 1478 |
+
|
| 1479 |
+
cookie_tuples.append((name, value, standard, rest))
|
| 1480 |
+
|
| 1481 |
+
return cookie_tuples
|
| 1482 |
+
|
| 1483 |
+
def _cookie_from_cookie_tuple(self, tup, request):
|
| 1484 |
+
# standard is dict of standard cookie-attributes, rest is dict of the
|
| 1485 |
+
# rest of them
|
| 1486 |
+
name, value, standard, rest = tup
|
| 1487 |
+
|
| 1488 |
+
domain = standard.get("domain", Absent)
|
| 1489 |
+
path = standard.get("path", Absent)
|
| 1490 |
+
port = standard.get("port", Absent)
|
| 1491 |
+
expires = standard.get("expires", Absent)
|
| 1492 |
+
|
| 1493 |
+
# set the easy defaults
|
| 1494 |
+
version = standard.get("version", None)
|
| 1495 |
+
if version is not None:
|
| 1496 |
+
try:
|
| 1497 |
+
version = int(version)
|
| 1498 |
+
except ValueError:
|
| 1499 |
+
return None # invalid version, ignore cookie
|
| 1500 |
+
secure = standard.get("secure", False)
|
| 1501 |
+
# (discard is also set if expires is Absent)
|
| 1502 |
+
discard = standard.get("discard", False)
|
| 1503 |
+
comment = standard.get("comment", None)
|
| 1504 |
+
comment_url = standard.get("commenturl", None)
|
| 1505 |
+
|
| 1506 |
+
# set default path
|
| 1507 |
+
if path is not Absent and path != "":
|
| 1508 |
+
path_specified = True
|
| 1509 |
+
path = escape_path(path)
|
| 1510 |
+
else:
|
| 1511 |
+
path_specified = False
|
| 1512 |
+
path = request_path(request)
|
| 1513 |
+
i = path.rfind("/")
|
| 1514 |
+
if i != -1:
|
| 1515 |
+
if version == 0:
|
| 1516 |
+
# Netscape spec parts company from reality here
|
| 1517 |
+
path = path[:i]
|
| 1518 |
+
else:
|
| 1519 |
+
path = path[:i+1]
|
| 1520 |
+
if len(path) == 0: path = "/"
|
| 1521 |
+
|
| 1522 |
+
# set default domain
|
| 1523 |
+
domain_specified = domain is not Absent
|
| 1524 |
+
# but first we have to remember whether it starts with a dot
|
| 1525 |
+
domain_initial_dot = False
|
| 1526 |
+
if domain_specified:
|
| 1527 |
+
domain_initial_dot = bool(domain.startswith("."))
|
| 1528 |
+
if domain is Absent:
|
| 1529 |
+
req_host, erhn = eff_request_host(request)
|
| 1530 |
+
domain = erhn
|
| 1531 |
+
elif not domain.startswith("."):
|
| 1532 |
+
domain = "."+domain
|
| 1533 |
+
|
| 1534 |
+
# set default port
|
| 1535 |
+
port_specified = False
|
| 1536 |
+
if port is not Absent:
|
| 1537 |
+
if port is None:
|
| 1538 |
+
# Port attr present, but has no value: default to request port.
|
| 1539 |
+
# Cookie should then only be sent back on that port.
|
| 1540 |
+
port = request_port(request)
|
| 1541 |
+
else:
|
| 1542 |
+
port_specified = True
|
| 1543 |
+
port = re.sub(r"\s+", "", port)
|
| 1544 |
+
else:
|
| 1545 |
+
# No port attr present. Cookie can be sent back on any port.
|
| 1546 |
+
port = None
|
| 1547 |
+
|
| 1548 |
+
# set default expires and discard
|
| 1549 |
+
if expires is Absent:
|
| 1550 |
+
expires = None
|
| 1551 |
+
discard = True
|
| 1552 |
+
elif expires <= self._now:
|
| 1553 |
+
# Expiry date in past is request to delete cookie. This can't be
|
| 1554 |
+
# in DefaultCookiePolicy, because can't delete cookies there.
|
| 1555 |
+
try:
|
| 1556 |
+
self.clear(domain, path, name)
|
| 1557 |
+
except KeyError:
|
| 1558 |
+
pass
|
| 1559 |
+
_debug("Expiring cookie, domain='%s', path='%s', name='%s'",
|
| 1560 |
+
domain, path, name)
|
| 1561 |
+
return None
|
| 1562 |
+
|
| 1563 |
+
return Cookie(version,
|
| 1564 |
+
name, value,
|
| 1565 |
+
port, port_specified,
|
| 1566 |
+
domain, domain_specified, domain_initial_dot,
|
| 1567 |
+
path, path_specified,
|
| 1568 |
+
secure,
|
| 1569 |
+
expires,
|
| 1570 |
+
discard,
|
| 1571 |
+
comment,
|
| 1572 |
+
comment_url,
|
| 1573 |
+
rest)
|
| 1574 |
+
|
| 1575 |
+
def _cookies_from_attrs_set(self, attrs_set, request):
|
| 1576 |
+
cookie_tuples = self._normalized_cookie_tuples(attrs_set)
|
| 1577 |
+
|
| 1578 |
+
cookies = []
|
| 1579 |
+
for tup in cookie_tuples:
|
| 1580 |
+
cookie = self._cookie_from_cookie_tuple(tup, request)
|
| 1581 |
+
if cookie: cookies.append(cookie)
|
| 1582 |
+
return cookies
|
| 1583 |
+
|
| 1584 |
+
def _process_rfc2109_cookies(self, cookies):
|
| 1585 |
+
rfc2109_as_ns = getattr(self._policy, 'rfc2109_as_netscape', None)
|
| 1586 |
+
if rfc2109_as_ns is None:
|
| 1587 |
+
rfc2109_as_ns = not self._policy.rfc2965
|
| 1588 |
+
for cookie in cookies:
|
| 1589 |
+
if cookie.version == 1:
|
| 1590 |
+
cookie.rfc2109 = True
|
| 1591 |
+
if rfc2109_as_ns:
|
| 1592 |
+
# treat 2109 cookies as Netscape cookies rather than
|
| 1593 |
+
# as RFC2965 cookies
|
| 1594 |
+
cookie.version = 0
|
| 1595 |
+
|
| 1596 |
+
def make_cookies(self, response, request):
|
| 1597 |
+
"""Return sequence of Cookie objects extracted from response object."""
|
| 1598 |
+
# get cookie-attributes for RFC 2965 and Netscape protocols
|
| 1599 |
+
headers = response.info()
|
| 1600 |
+
rfc2965_hdrs = headers.get_all("Set-Cookie2", [])
|
| 1601 |
+
ns_hdrs = headers.get_all("Set-Cookie", [])
|
| 1602 |
+
self._policy._now = self._now = int(time.time())
|
| 1603 |
+
|
| 1604 |
+
rfc2965 = self._policy.rfc2965
|
| 1605 |
+
netscape = self._policy.netscape
|
| 1606 |
+
|
| 1607 |
+
if ((not rfc2965_hdrs and not ns_hdrs) or
|
| 1608 |
+
(not ns_hdrs and not rfc2965) or
|
| 1609 |
+
(not rfc2965_hdrs and not netscape) or
|
| 1610 |
+
(not netscape and not rfc2965)):
|
| 1611 |
+
return [] # no relevant cookie headers: quick exit
|
| 1612 |
+
|
| 1613 |
+
try:
|
| 1614 |
+
cookies = self._cookies_from_attrs_set(
|
| 1615 |
+
split_header_words(rfc2965_hdrs), request)
|
| 1616 |
+
except Exception:
|
| 1617 |
+
_warn_unhandled_exception()
|
| 1618 |
+
cookies = []
|
| 1619 |
+
|
| 1620 |
+
if ns_hdrs and netscape:
|
| 1621 |
+
try:
|
| 1622 |
+
# RFC 2109 and Netscape cookies
|
| 1623 |
+
ns_cookies = self._cookies_from_attrs_set(
|
| 1624 |
+
parse_ns_headers(ns_hdrs), request)
|
| 1625 |
+
except Exception:
|
| 1626 |
+
_warn_unhandled_exception()
|
| 1627 |
+
ns_cookies = []
|
| 1628 |
+
self._process_rfc2109_cookies(ns_cookies)
|
| 1629 |
+
|
| 1630 |
+
# Look for Netscape cookies (from Set-Cookie headers) that match
|
| 1631 |
+
# corresponding RFC 2965 cookies (from Set-Cookie2 headers).
|
| 1632 |
+
# For each match, keep the RFC 2965 cookie and ignore the Netscape
|
| 1633 |
+
# cookie (RFC 2965 section 9.1). Actually, RFC 2109 cookies are
|
| 1634 |
+
# bundled in with the Netscape cookies for this purpose, which is
|
| 1635 |
+
# reasonable behaviour.
|
| 1636 |
+
if rfc2965:
|
| 1637 |
+
lookup = {}
|
| 1638 |
+
for cookie in cookies:
|
| 1639 |
+
lookup[(cookie.domain, cookie.path, cookie.name)] = None
|
| 1640 |
+
|
| 1641 |
+
def no_matching_rfc2965(ns_cookie, lookup=lookup):
|
| 1642 |
+
key = ns_cookie.domain, ns_cookie.path, ns_cookie.name
|
| 1643 |
+
return key not in lookup
|
| 1644 |
+
ns_cookies = filter(no_matching_rfc2965, ns_cookies)
|
| 1645 |
+
|
| 1646 |
+
if ns_cookies:
|
| 1647 |
+
cookies.extend(ns_cookies)
|
| 1648 |
+
|
| 1649 |
+
return cookies
|
| 1650 |
+
|
| 1651 |
+
def set_cookie_if_ok(self, cookie, request):
|
| 1652 |
+
"""Set a cookie if policy says it's OK to do so."""
|
| 1653 |
+
self._cookies_lock.acquire()
|
| 1654 |
+
try:
|
| 1655 |
+
self._policy._now = self._now = int(time.time())
|
| 1656 |
+
|
| 1657 |
+
if self._policy.set_ok(cookie, request):
|
| 1658 |
+
self.set_cookie(cookie)
|
| 1659 |
+
|
| 1660 |
+
|
| 1661 |
+
finally:
|
| 1662 |
+
self._cookies_lock.release()
|
| 1663 |
+
|
| 1664 |
+
def set_cookie(self, cookie):
|
| 1665 |
+
"""Set a cookie, without checking whether or not it should be set."""
|
| 1666 |
+
c = self._cookies
|
| 1667 |
+
self._cookies_lock.acquire()
|
| 1668 |
+
try:
|
| 1669 |
+
if cookie.domain not in c: c[cookie.domain] = {}
|
| 1670 |
+
c2 = c[cookie.domain]
|
| 1671 |
+
if cookie.path not in c2: c2[cookie.path] = {}
|
| 1672 |
+
c3 = c2[cookie.path]
|
| 1673 |
+
c3[cookie.name] = cookie
|
| 1674 |
+
finally:
|
| 1675 |
+
self._cookies_lock.release()
|
| 1676 |
+
|
| 1677 |
+
def extract_cookies(self, response, request):
|
| 1678 |
+
"""Extract cookies from response, where allowable given the request."""
|
| 1679 |
+
_debug("extract_cookies: %s", response.info())
|
| 1680 |
+
self._cookies_lock.acquire()
|
| 1681 |
+
try:
|
| 1682 |
+
for cookie in self.make_cookies(response, request):
|
| 1683 |
+
if self._policy.set_ok(cookie, request):
|
| 1684 |
+
_debug(" setting cookie: %s", cookie)
|
| 1685 |
+
self.set_cookie(cookie)
|
| 1686 |
+
finally:
|
| 1687 |
+
self._cookies_lock.release()
|
| 1688 |
+
|
| 1689 |
+
def clear(self, domain=None, path=None, name=None):
|
| 1690 |
+
"""Clear some cookies.
|
| 1691 |
+
|
| 1692 |
+
Invoking this method without arguments will clear all cookies. If
|
| 1693 |
+
given a single argument, only cookies belonging to that domain will be
|
| 1694 |
+
removed. If given two arguments, cookies belonging to the specified
|
| 1695 |
+
path within that domain are removed. If given three arguments, then
|
| 1696 |
+
the cookie with the specified name, path and domain is removed.
|
| 1697 |
+
|
| 1698 |
+
Raises KeyError if no matching cookie exists.
|
| 1699 |
+
|
| 1700 |
+
"""
|
| 1701 |
+
if name is not None:
|
| 1702 |
+
if (domain is None) or (path is None):
|
| 1703 |
+
raise ValueError(
|
| 1704 |
+
"domain and path must be given to remove a cookie by name")
|
| 1705 |
+
del self._cookies[domain][path][name]
|
| 1706 |
+
elif path is not None:
|
| 1707 |
+
if domain is None:
|
| 1708 |
+
raise ValueError(
|
| 1709 |
+
"domain must be given to remove cookies by path")
|
| 1710 |
+
del self._cookies[domain][path]
|
| 1711 |
+
elif domain is not None:
|
| 1712 |
+
del self._cookies[domain]
|
| 1713 |
+
else:
|
| 1714 |
+
self._cookies = {}
|
| 1715 |
+
|
| 1716 |
+
def clear_session_cookies(self):
|
| 1717 |
+
"""Discard all session cookies.
|
| 1718 |
+
|
| 1719 |
+
Note that the .save() method won't save session cookies anyway, unless
|
| 1720 |
+
you ask otherwise by passing a true ignore_discard argument.
|
| 1721 |
+
|
| 1722 |
+
"""
|
| 1723 |
+
self._cookies_lock.acquire()
|
| 1724 |
+
try:
|
| 1725 |
+
for cookie in self:
|
| 1726 |
+
if cookie.discard:
|
| 1727 |
+
self.clear(cookie.domain, cookie.path, cookie.name)
|
| 1728 |
+
finally:
|
| 1729 |
+
self._cookies_lock.release()
|
| 1730 |
+
|
| 1731 |
+
def clear_expired_cookies(self):
|
| 1732 |
+
"""Discard all expired cookies.
|
| 1733 |
+
|
| 1734 |
+
You probably don't need to call this method: expired cookies are never
|
| 1735 |
+
sent back to the server (provided you're using DefaultCookiePolicy),
|
| 1736 |
+
this method is called by CookieJar itself every so often, and the
|
| 1737 |
+
.save() method won't save expired cookies anyway (unless you ask
|
| 1738 |
+
otherwise by passing a true ignore_expires argument).
|
| 1739 |
+
|
| 1740 |
+
"""
|
| 1741 |
+
self._cookies_lock.acquire()
|
| 1742 |
+
try:
|
| 1743 |
+
now = time.time()
|
| 1744 |
+
for cookie in self:
|
| 1745 |
+
if cookie.is_expired(now):
|
| 1746 |
+
self.clear(cookie.domain, cookie.path, cookie.name)
|
| 1747 |
+
finally:
|
| 1748 |
+
self._cookies_lock.release()
|
| 1749 |
+
|
| 1750 |
+
def __iter__(self):
|
| 1751 |
+
return deepvalues(self._cookies)
|
| 1752 |
+
|
| 1753 |
+
def __len__(self):
|
| 1754 |
+
"""Return number of contained cookies."""
|
| 1755 |
+
i = 0
|
| 1756 |
+
for cookie in self: i = i + 1
|
| 1757 |
+
return i
|
| 1758 |
+
|
| 1759 |
+
def __repr__(self):
|
| 1760 |
+
r = []
|
| 1761 |
+
for cookie in self: r.append(repr(cookie))
|
| 1762 |
+
return "<%s[%s]>" % (self.__class__.__name__, ", ".join(r))
|
| 1763 |
+
|
| 1764 |
+
def __str__(self):
|
| 1765 |
+
r = []
|
| 1766 |
+
for cookie in self: r.append(str(cookie))
|
| 1767 |
+
return "<%s[%s]>" % (self.__class__.__name__, ", ".join(r))
|
| 1768 |
+
|
| 1769 |
+
|
| 1770 |
+
# derives from OSError for backwards-compatibility with Python 2.4.0
|
| 1771 |
+
class LoadError(OSError): pass
|
| 1772 |
+
|
| 1773 |
+
class FileCookieJar(CookieJar):
|
| 1774 |
+
"""CookieJar that can be loaded from and saved to a file."""
|
| 1775 |
+
|
| 1776 |
+
def __init__(self, filename=None, delayload=False, policy=None):
|
| 1777 |
+
"""
|
| 1778 |
+
Cookies are NOT loaded from the named file until either the .load() or
|
| 1779 |
+
.revert() method is called.
|
| 1780 |
+
|
| 1781 |
+
"""
|
| 1782 |
+
CookieJar.__init__(self, policy)
|
| 1783 |
+
if filename is not None:
|
| 1784 |
+
filename = os.fspath(filename)
|
| 1785 |
+
self.filename = filename
|
| 1786 |
+
self.delayload = bool(delayload)
|
| 1787 |
+
|
| 1788 |
+
def save(self, filename=None, ignore_discard=False, ignore_expires=False):
|
| 1789 |
+
"""Save cookies to a file."""
|
| 1790 |
+
raise NotImplementedError()
|
| 1791 |
+
|
| 1792 |
+
def load(self, filename=None, ignore_discard=False, ignore_expires=False):
|
| 1793 |
+
"""Load cookies from a file."""
|
| 1794 |
+
if filename is None:
|
| 1795 |
+
if self.filename is not None: filename = self.filename
|
| 1796 |
+
else: raise ValueError(MISSING_FILENAME_TEXT)
|
| 1797 |
+
|
| 1798 |
+
with open(filename) as f:
|
| 1799 |
+
self._really_load(f, filename, ignore_discard, ignore_expires)
|
| 1800 |
+
|
| 1801 |
+
def revert(self, filename=None,
|
| 1802 |
+
ignore_discard=False, ignore_expires=False):
|
| 1803 |
+
"""Clear all cookies and reload cookies from a saved file.
|
| 1804 |
+
|
| 1805 |
+
Raises LoadError (or OSError) if reversion is not successful; the
|
| 1806 |
+
object's state will not be altered if this happens.
|
| 1807 |
+
|
| 1808 |
+
"""
|
| 1809 |
+
if filename is None:
|
| 1810 |
+
if self.filename is not None: filename = self.filename
|
| 1811 |
+
else: raise ValueError(MISSING_FILENAME_TEXT)
|
| 1812 |
+
|
| 1813 |
+
self._cookies_lock.acquire()
|
| 1814 |
+
try:
|
| 1815 |
+
|
| 1816 |
+
old_state = copy.deepcopy(self._cookies)
|
| 1817 |
+
self._cookies = {}
|
| 1818 |
+
try:
|
| 1819 |
+
self.load(filename, ignore_discard, ignore_expires)
|
| 1820 |
+
except OSError:
|
| 1821 |
+
self._cookies = old_state
|
| 1822 |
+
raise
|
| 1823 |
+
|
| 1824 |
+
finally:
|
| 1825 |
+
self._cookies_lock.release()
|
| 1826 |
+
|
| 1827 |
+
|
| 1828 |
+
def lwp_cookie_str(cookie):
|
| 1829 |
+
"""Return string representation of Cookie in the LWP cookie file format.
|
| 1830 |
+
|
| 1831 |
+
Actually, the format is extended a bit -- see module docstring.
|
| 1832 |
+
|
| 1833 |
+
"""
|
| 1834 |
+
h = [(cookie.name, cookie.value),
|
| 1835 |
+
("path", cookie.path),
|
| 1836 |
+
("domain", cookie.domain)]
|
| 1837 |
+
if cookie.port is not None: h.append(("port", cookie.port))
|
| 1838 |
+
if cookie.path_specified: h.append(("path_spec", None))
|
| 1839 |
+
if cookie.port_specified: h.append(("port_spec", None))
|
| 1840 |
+
if cookie.domain_initial_dot: h.append(("domain_dot", None))
|
| 1841 |
+
if cookie.secure: h.append(("secure", None))
|
| 1842 |
+
if cookie.expires: h.append(("expires",
|
| 1843 |
+
time2isoz(float(cookie.expires))))
|
| 1844 |
+
if cookie.discard: h.append(("discard", None))
|
| 1845 |
+
if cookie.comment: h.append(("comment", cookie.comment))
|
| 1846 |
+
if cookie.comment_url: h.append(("commenturl", cookie.comment_url))
|
| 1847 |
+
|
| 1848 |
+
keys = sorted(cookie._rest.keys())
|
| 1849 |
+
for k in keys:
|
| 1850 |
+
h.append((k, str(cookie._rest[k])))
|
| 1851 |
+
|
| 1852 |
+
h.append(("version", str(cookie.version)))
|
| 1853 |
+
|
| 1854 |
+
return join_header_words([h])
|
| 1855 |
+
|
| 1856 |
+
class LWPCookieJar(FileCookieJar):
|
| 1857 |
+
"""
|
| 1858 |
+
The LWPCookieJar saves a sequence of "Set-Cookie3" lines.
|
| 1859 |
+
"Set-Cookie3" is the format used by the libwww-perl library, not known
|
| 1860 |
+
to be compatible with any browser, but which is easy to read and
|
| 1861 |
+
doesn't lose information about RFC 2965 cookies.
|
| 1862 |
+
|
| 1863 |
+
Additional methods
|
| 1864 |
+
|
| 1865 |
+
as_lwp_str(ignore_discard=True, ignore_expired=True)
|
| 1866 |
+
|
| 1867 |
+
"""
|
| 1868 |
+
|
| 1869 |
+
def as_lwp_str(self, ignore_discard=True, ignore_expires=True):
|
| 1870 |
+
"""Return cookies as a string of "\\n"-separated "Set-Cookie3" headers.
|
| 1871 |
+
|
| 1872 |
+
ignore_discard and ignore_expires: see docstring for FileCookieJar.save
|
| 1873 |
+
|
| 1874 |
+
"""
|
| 1875 |
+
now = time.time()
|
| 1876 |
+
r = []
|
| 1877 |
+
for cookie in self:
|
| 1878 |
+
if not ignore_discard and cookie.discard:
|
| 1879 |
+
continue
|
| 1880 |
+
if not ignore_expires and cookie.is_expired(now):
|
| 1881 |
+
continue
|
| 1882 |
+
r.append("Set-Cookie3: %s" % lwp_cookie_str(cookie))
|
| 1883 |
+
return "\n".join(r+[""])
|
| 1884 |
+
|
| 1885 |
+
def save(self, filename=None, ignore_discard=False, ignore_expires=False):
|
| 1886 |
+
if filename is None:
|
| 1887 |
+
if self.filename is not None: filename = self.filename
|
| 1888 |
+
else: raise ValueError(MISSING_FILENAME_TEXT)
|
| 1889 |
+
|
| 1890 |
+
with open(filename, "w") as f:
|
| 1891 |
+
# There really isn't an LWP Cookies 2.0 format, but this indicates
|
| 1892 |
+
# that there is extra information in here (domain_dot and
|
| 1893 |
+
# port_spec) while still being compatible with libwww-perl, I hope.
|
| 1894 |
+
f.write("#LWP-Cookies-2.0\n")
|
| 1895 |
+
f.write(self.as_lwp_str(ignore_discard, ignore_expires))
|
| 1896 |
+
|
| 1897 |
+
def _really_load(self, f, filename, ignore_discard, ignore_expires):
|
| 1898 |
+
magic = f.readline()
|
| 1899 |
+
if not self.magic_re.search(magic):
|
| 1900 |
+
msg = ("%r does not look like a Set-Cookie3 (LWP) format "
|
| 1901 |
+
"file" % filename)
|
| 1902 |
+
raise LoadError(msg)
|
| 1903 |
+
|
| 1904 |
+
now = time.time()
|
| 1905 |
+
|
| 1906 |
+
header = "Set-Cookie3:"
|
| 1907 |
+
boolean_attrs = ("port_spec", "path_spec", "domain_dot",
|
| 1908 |
+
"secure", "discard")
|
| 1909 |
+
value_attrs = ("version",
|
| 1910 |
+
"port", "path", "domain",
|
| 1911 |
+
"expires",
|
| 1912 |
+
"comment", "commenturl")
|
| 1913 |
+
|
| 1914 |
+
try:
|
| 1915 |
+
while 1:
|
| 1916 |
+
line = f.readline()
|
| 1917 |
+
if line == "": break
|
| 1918 |
+
if not line.startswith(header):
|
| 1919 |
+
continue
|
| 1920 |
+
line = line[len(header):].strip()
|
| 1921 |
+
|
| 1922 |
+
for data in split_header_words([line]):
|
| 1923 |
+
name, value = data[0]
|
| 1924 |
+
standard = {}
|
| 1925 |
+
rest = {}
|
| 1926 |
+
for k in boolean_attrs:
|
| 1927 |
+
standard[k] = False
|
| 1928 |
+
for k, v in data[1:]:
|
| 1929 |
+
if k is not None:
|
| 1930 |
+
lc = k.lower()
|
| 1931 |
+
else:
|
| 1932 |
+
lc = None
|
| 1933 |
+
# don't lose case distinction for unknown fields
|
| 1934 |
+
if (lc in value_attrs) or (lc in boolean_attrs):
|
| 1935 |
+
k = lc
|
| 1936 |
+
if k in boolean_attrs:
|
| 1937 |
+
if v is None: v = True
|
| 1938 |
+
standard[k] = v
|
| 1939 |
+
elif k in value_attrs:
|
| 1940 |
+
standard[k] = v
|
| 1941 |
+
else:
|
| 1942 |
+
rest[k] = v
|
| 1943 |
+
|
| 1944 |
+
h = standard.get
|
| 1945 |
+
expires = h("expires")
|
| 1946 |
+
discard = h("discard")
|
| 1947 |
+
if expires is not None:
|
| 1948 |
+
expires = iso2time(expires)
|
| 1949 |
+
if expires is None:
|
| 1950 |
+
discard = True
|
| 1951 |
+
domain = h("domain")
|
| 1952 |
+
domain_specified = domain.startswith(".")
|
| 1953 |
+
c = Cookie(h("version"), name, value,
|
| 1954 |
+
h("port"), h("port_spec"),
|
| 1955 |
+
domain, domain_specified, h("domain_dot"),
|
| 1956 |
+
h("path"), h("path_spec"),
|
| 1957 |
+
h("secure"),
|
| 1958 |
+
expires,
|
| 1959 |
+
discard,
|
| 1960 |
+
h("comment"),
|
| 1961 |
+
h("commenturl"),
|
| 1962 |
+
rest)
|
| 1963 |
+
if not ignore_discard and c.discard:
|
| 1964 |
+
continue
|
| 1965 |
+
if not ignore_expires and c.is_expired(now):
|
| 1966 |
+
continue
|
| 1967 |
+
self.set_cookie(c)
|
| 1968 |
+
except OSError:
|
| 1969 |
+
raise
|
| 1970 |
+
except Exception:
|
| 1971 |
+
_warn_unhandled_exception()
|
| 1972 |
+
raise LoadError("invalid Set-Cookie3 format file %r: %r" %
|
| 1973 |
+
(filename, line))
|
| 1974 |
+
|
| 1975 |
+
|
| 1976 |
+
class MozillaCookieJar(FileCookieJar):
|
| 1977 |
+
"""
|
| 1978 |
+
|
| 1979 |
+
WARNING: you may want to backup your browser's cookies file if you use
|
| 1980 |
+
this class to save cookies. I *think* it works, but there have been
|
| 1981 |
+
bugs in the past!
|
| 1982 |
+
|
| 1983 |
+
This class differs from CookieJar only in the format it uses to save and
|
| 1984 |
+
load cookies to and from a file. This class uses the Mozilla/Netscape
|
| 1985 |
+
`cookies.txt' format. lynx uses this file format, too.
|
| 1986 |
+
|
| 1987 |
+
Don't expect cookies saved while the browser is running to be noticed by
|
| 1988 |
+
the browser (in fact, Mozilla on unix will overwrite your saved cookies if
|
| 1989 |
+
you change them on disk while it's running; on Windows, you probably can't
|
| 1990 |
+
save at all while the browser is running).
|
| 1991 |
+
|
| 1992 |
+
Note that the Mozilla/Netscape format will downgrade RFC2965 cookies to
|
| 1993 |
+
Netscape cookies on saving.
|
| 1994 |
+
|
| 1995 |
+
In particular, the cookie version and port number information is lost,
|
| 1996 |
+
together with information about whether or not Path, Port and Discard were
|
| 1997 |
+
specified by the Set-Cookie2 (or Set-Cookie) header, and whether or not the
|
| 1998 |
+
domain as set in the HTTP header started with a dot (yes, I'm aware some
|
| 1999 |
+
domains in Netscape files start with a dot and some don't -- trust me, you
|
| 2000 |
+
really don't want to know any more about this).
|
| 2001 |
+
|
| 2002 |
+
Note that though Mozilla and Netscape use the same format, they use
|
| 2003 |
+
slightly different headers. The class saves cookies using the Netscape
|
| 2004 |
+
header by default (Mozilla can cope with that).
|
| 2005 |
+
|
| 2006 |
+
"""
|
| 2007 |
+
magic_re = re.compile("#( Netscape)? HTTP Cookie File")
|
| 2008 |
+
header = """\
|
| 2009 |
+
# Netscape HTTP Cookie File
|
| 2010 |
+
# http://curl.haxx.se/rfc/cookie_spec.html
|
| 2011 |
+
# This is a generated file! Do not edit.
|
| 2012 |
+
|
| 2013 |
+
"""
|
| 2014 |
+
|
| 2015 |
+
def _really_load(self, f, filename, ignore_discard, ignore_expires):
|
| 2016 |
+
now = time.time()
|
| 2017 |
+
|
| 2018 |
+
magic = f.readline()
|
| 2019 |
+
if not self.magic_re.search(magic):
|
| 2020 |
+
raise LoadError(
|
| 2021 |
+
"%r does not look like a Netscape format cookies file" %
|
| 2022 |
+
filename)
|
| 2023 |
+
|
| 2024 |
+
try:
|
| 2025 |
+
while 1:
|
| 2026 |
+
line = f.readline()
|
| 2027 |
+
if line == "": break
|
| 2028 |
+
|
| 2029 |
+
# last field may be absent, so keep any trailing tab
|
| 2030 |
+
if line.endswith("\n"): line = line[:-1]
|
| 2031 |
+
|
| 2032 |
+
# skip comments and blank lines XXX what is $ for?
|
| 2033 |
+
if (line.strip().startswith(("#", "$")) or
|
| 2034 |
+
line.strip() == ""):
|
| 2035 |
+
continue
|
| 2036 |
+
|
| 2037 |
+
domain, domain_specified, path, secure, expires, name, value = \
|
| 2038 |
+
line.split("\t")
|
| 2039 |
+
secure = (secure == "TRUE")
|
| 2040 |
+
domain_specified = (domain_specified == "TRUE")
|
| 2041 |
+
if name == "":
|
| 2042 |
+
# cookies.txt regards 'Set-Cookie: foo' as a cookie
|
| 2043 |
+
# with no name, whereas http.cookiejar regards it as a
|
| 2044 |
+
# cookie with no value.
|
| 2045 |
+
name = value
|
| 2046 |
+
value = None
|
| 2047 |
+
|
| 2048 |
+
initial_dot = domain.startswith(".")
|
| 2049 |
+
assert domain_specified == initial_dot
|
| 2050 |
+
|
| 2051 |
+
discard = False
|
| 2052 |
+
if expires == "":
|
| 2053 |
+
expires = None
|
| 2054 |
+
discard = True
|
| 2055 |
+
|
| 2056 |
+
# assume path_specified is false
|
| 2057 |
+
c = Cookie(0, name, value,
|
| 2058 |
+
None, False,
|
| 2059 |
+
domain, domain_specified, initial_dot,
|
| 2060 |
+
path, False,
|
| 2061 |
+
secure,
|
| 2062 |
+
expires,
|
| 2063 |
+
discard,
|
| 2064 |
+
None,
|
| 2065 |
+
None,
|
| 2066 |
+
{})
|
| 2067 |
+
if not ignore_discard and c.discard:
|
| 2068 |
+
continue
|
| 2069 |
+
if not ignore_expires and c.is_expired(now):
|
| 2070 |
+
continue
|
| 2071 |
+
self.set_cookie(c)
|
| 2072 |
+
|
| 2073 |
+
except OSError:
|
| 2074 |
+
raise
|
| 2075 |
+
except Exception:
|
| 2076 |
+
_warn_unhandled_exception()
|
| 2077 |
+
raise LoadError("invalid Netscape format cookies file %r: %r" %
|
| 2078 |
+
(filename, line))
|
| 2079 |
+
|
| 2080 |
+
def save(self, filename=None, ignore_discard=False, ignore_expires=False):
|
| 2081 |
+
if filename is None:
|
| 2082 |
+
if self.filename is not None: filename = self.filename
|
| 2083 |
+
else: raise ValueError(MISSING_FILENAME_TEXT)
|
| 2084 |
+
|
| 2085 |
+
with open(filename, "w") as f:
|
| 2086 |
+
f.write(self.header)
|
| 2087 |
+
now = time.time()
|
| 2088 |
+
for cookie in self:
|
| 2089 |
+
if not ignore_discard and cookie.discard:
|
| 2090 |
+
continue
|
| 2091 |
+
if not ignore_expires and cookie.is_expired(now):
|
| 2092 |
+
continue
|
| 2093 |
+
if cookie.secure: secure = "TRUE"
|
| 2094 |
+
else: secure = "FALSE"
|
| 2095 |
+
if cookie.domain.startswith("."): initial_dot = "TRUE"
|
| 2096 |
+
else: initial_dot = "FALSE"
|
| 2097 |
+
if cookie.expires is not None:
|
| 2098 |
+
expires = str(cookie.expires)
|
| 2099 |
+
else:
|
| 2100 |
+
expires = ""
|
| 2101 |
+
if cookie.value is None:
|
| 2102 |
+
# cookies.txt regards 'Set-Cookie: foo' as a cookie
|
| 2103 |
+
# with no name, whereas http.cookiejar regards it as a
|
| 2104 |
+
# cookie with no value.
|
| 2105 |
+
name = ""
|
| 2106 |
+
value = cookie.name
|
| 2107 |
+
else:
|
| 2108 |
+
name = cookie.name
|
| 2109 |
+
value = cookie.value
|
| 2110 |
+
f.write(
|
| 2111 |
+
"\t".join([cookie.domain, initial_dot, cookie.path,
|
| 2112 |
+
secure, expires, name, value])+
|
| 2113 |
+
"\n")
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/http/cookies.py
ADDED
|
@@ -0,0 +1,609 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
####
|
| 2 |
+
# Copyright 2000 by Timothy O'Malley <timo@alum.mit.edu>
|
| 3 |
+
#
|
| 4 |
+
# All Rights Reserved
|
| 5 |
+
#
|
| 6 |
+
# Permission to use, copy, modify, and distribute this software
|
| 7 |
+
# and its documentation for any purpose and without fee is hereby
|
| 8 |
+
# granted, provided that the above copyright notice appear in all
|
| 9 |
+
# copies and that both that copyright notice and this permission
|
| 10 |
+
# notice appear in supporting documentation, and that the name of
|
| 11 |
+
# Timothy O'Malley not be used in advertising or publicity
|
| 12 |
+
# pertaining to distribution of the software without specific, written
|
| 13 |
+
# prior permission.
|
| 14 |
+
#
|
| 15 |
+
# Timothy O'Malley DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
|
| 16 |
+
# SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
| 17 |
+
# AND FITNESS, IN NO EVENT SHALL Timothy O'Malley BE LIABLE FOR
|
| 18 |
+
# ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
| 19 |
+
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
|
| 20 |
+
# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
|
| 21 |
+
# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
| 22 |
+
# PERFORMANCE OF THIS SOFTWARE.
|
| 23 |
+
#
|
| 24 |
+
####
|
| 25 |
+
#
|
| 26 |
+
# Id: Cookie.py,v 2.29 2000/08/23 05:28:49 timo Exp
|
| 27 |
+
# by Timothy O'Malley <timo@alum.mit.edu>
|
| 28 |
+
#
|
| 29 |
+
# Cookie.py is a Python module for the handling of HTTP
|
| 30 |
+
# cookies as a Python dictionary. See RFC 2109 for more
|
| 31 |
+
# information on cookies.
|
| 32 |
+
#
|
| 33 |
+
# The original idea to treat Cookies as a dictionary came from
|
| 34 |
+
# Dave Mitchell (davem@magnet.com) in 1995, when he released the
|
| 35 |
+
# first version of nscookie.py.
|
| 36 |
+
#
|
| 37 |
+
####
|
| 38 |
+
|
| 39 |
+
r"""
|
| 40 |
+
Here's a sample session to show how to use this module.
|
| 41 |
+
At the moment, this is the only documentation.
|
| 42 |
+
|
| 43 |
+
The Basics
|
| 44 |
+
----------
|
| 45 |
+
|
| 46 |
+
Importing is easy...
|
| 47 |
+
|
| 48 |
+
>>> from http import cookies
|
| 49 |
+
|
| 50 |
+
Most of the time you start by creating a cookie.
|
| 51 |
+
|
| 52 |
+
>>> C = cookies.SimpleCookie()
|
| 53 |
+
|
| 54 |
+
Once you've created your Cookie, you can add values just as if it were
|
| 55 |
+
a dictionary.
|
| 56 |
+
|
| 57 |
+
>>> C = cookies.SimpleCookie()
|
| 58 |
+
>>> C["fig"] = "newton"
|
| 59 |
+
>>> C["sugar"] = "wafer"
|
| 60 |
+
>>> C.output()
|
| 61 |
+
'Set-Cookie: fig=newton\r\nSet-Cookie: sugar=wafer'
|
| 62 |
+
|
| 63 |
+
Notice that the printable representation of a Cookie is the
|
| 64 |
+
appropriate format for a Set-Cookie: header. This is the
|
| 65 |
+
default behavior. You can change the header and printed
|
| 66 |
+
attributes by using the .output() function
|
| 67 |
+
|
| 68 |
+
>>> C = cookies.SimpleCookie()
|
| 69 |
+
>>> C["rocky"] = "road"
|
| 70 |
+
>>> C["rocky"]["path"] = "/cookie"
|
| 71 |
+
>>> print(C.output(header="Cookie:"))
|
| 72 |
+
Cookie: rocky=road; Path=/cookie
|
| 73 |
+
>>> print(C.output(attrs=[], header="Cookie:"))
|
| 74 |
+
Cookie: rocky=road
|
| 75 |
+
|
| 76 |
+
The load() method of a Cookie extracts cookies from a string. In a
|
| 77 |
+
CGI script, you would use this method to extract the cookies from the
|
| 78 |
+
HTTP_COOKIE environment variable.
|
| 79 |
+
|
| 80 |
+
>>> C = cookies.SimpleCookie()
|
| 81 |
+
>>> C.load("chips=ahoy; vienna=finger")
|
| 82 |
+
>>> C.output()
|
| 83 |
+
'Set-Cookie: chips=ahoy\r\nSet-Cookie: vienna=finger'
|
| 84 |
+
|
| 85 |
+
The load() method is darn-tootin smart about identifying cookies
|
| 86 |
+
within a string. Escaped quotation marks, nested semicolons, and other
|
| 87 |
+
such trickeries do not confuse it.
|
| 88 |
+
|
| 89 |
+
>>> C = cookies.SimpleCookie()
|
| 90 |
+
>>> C.load('keebler="E=everybody; L=\\"Loves\\"; fudge=\\012;";')
|
| 91 |
+
>>> print(C)
|
| 92 |
+
Set-Cookie: keebler="E=everybody; L=\"Loves\"; fudge=\012;"
|
| 93 |
+
|
| 94 |
+
Each element of the Cookie also supports all of the RFC 2109
|
| 95 |
+
Cookie attributes. Here's an example which sets the Path
|
| 96 |
+
attribute.
|
| 97 |
+
|
| 98 |
+
>>> C = cookies.SimpleCookie()
|
| 99 |
+
>>> C["oreo"] = "doublestuff"
|
| 100 |
+
>>> C["oreo"]["path"] = "/"
|
| 101 |
+
>>> print(C)
|
| 102 |
+
Set-Cookie: oreo=doublestuff; Path=/
|
| 103 |
+
|
| 104 |
+
Each dictionary element has a 'value' attribute, which gives you
|
| 105 |
+
back the value associated with the key.
|
| 106 |
+
|
| 107 |
+
>>> C = cookies.SimpleCookie()
|
| 108 |
+
>>> C["twix"] = "none for you"
|
| 109 |
+
>>> C["twix"].value
|
| 110 |
+
'none for you'
|
| 111 |
+
|
| 112 |
+
The SimpleCookie expects that all values should be standard strings.
|
| 113 |
+
Just to be sure, SimpleCookie invokes the str() builtin to convert
|
| 114 |
+
the value to a string, when the values are set dictionary-style.
|
| 115 |
+
|
| 116 |
+
>>> C = cookies.SimpleCookie()
|
| 117 |
+
>>> C["number"] = 7
|
| 118 |
+
>>> C["string"] = "seven"
|
| 119 |
+
>>> C["number"].value
|
| 120 |
+
'7'
|
| 121 |
+
>>> C["string"].value
|
| 122 |
+
'seven'
|
| 123 |
+
>>> C.output()
|
| 124 |
+
'Set-Cookie: number=7\r\nSet-Cookie: string=seven'
|
| 125 |
+
|
| 126 |
+
Finis.
|
| 127 |
+
"""
|
| 128 |
+
|
| 129 |
+
#
|
| 130 |
+
# Import our required modules
|
| 131 |
+
#
|
| 132 |
+
import re
|
| 133 |
+
import string
|
| 134 |
+
|
| 135 |
+
__all__ = ["CookieError", "BaseCookie", "SimpleCookie"]
|
| 136 |
+
|
| 137 |
+
_nulljoin = ''.join
|
| 138 |
+
_semispacejoin = '; '.join
|
| 139 |
+
_spacejoin = ' '.join
|
| 140 |
+
|
| 141 |
+
#
|
| 142 |
+
# Define an exception visible to External modules
|
| 143 |
+
#
|
| 144 |
+
class CookieError(Exception):
|
| 145 |
+
pass
|
| 146 |
+
|
| 147 |
+
|
| 148 |
+
# These quoting routines conform to the RFC2109 specification, which in
|
| 149 |
+
# turn references the character definitions from RFC2068. They provide
|
| 150 |
+
# a two-way quoting algorithm. Any non-text character is translated
|
| 151 |
+
# into a 4 character sequence: a forward-slash followed by the
|
| 152 |
+
# three-digit octal equivalent of the character. Any '\' or '"' is
|
| 153 |
+
# quoted with a preceding '\' slash.
|
| 154 |
+
# Because of the way browsers really handle cookies (as opposed to what
|
| 155 |
+
# the RFC says) we also encode "," and ";".
|
| 156 |
+
#
|
| 157 |
+
# These are taken from RFC2068 and RFC2109.
|
| 158 |
+
# _LegalChars is the list of chars which don't require "'s
|
| 159 |
+
# _Translator hash-table for fast quoting
|
| 160 |
+
#
|
| 161 |
+
_LegalChars = string.ascii_letters + string.digits + "!#$%&'*+-.^_`|~:"
|
| 162 |
+
_UnescapedChars = _LegalChars + ' ()/<=>?@[]{}'
|
| 163 |
+
|
| 164 |
+
_Translator = {n: '\\%03o' % n
|
| 165 |
+
for n in set(range(256)) - set(map(ord, _UnescapedChars))}
|
| 166 |
+
_Translator.update({
|
| 167 |
+
ord('"'): '\\"',
|
| 168 |
+
ord('\\'): '\\\\',
|
| 169 |
+
})
|
| 170 |
+
|
| 171 |
+
_is_legal_key = re.compile('[%s]+' % re.escape(_LegalChars)).fullmatch
|
| 172 |
+
|
| 173 |
+
def _quote(str):
|
| 174 |
+
r"""Quote a string for use in a cookie header.
|
| 175 |
+
|
| 176 |
+
If the string does not need to be double-quoted, then just return the
|
| 177 |
+
string. Otherwise, surround the string in doublequotes and quote
|
| 178 |
+
(with a \) special characters.
|
| 179 |
+
"""
|
| 180 |
+
if str is None or _is_legal_key(str):
|
| 181 |
+
return str
|
| 182 |
+
else:
|
| 183 |
+
return '"' + str.translate(_Translator) + '"'
|
| 184 |
+
|
| 185 |
+
|
| 186 |
+
_OctalPatt = re.compile(r"\\[0-3][0-7][0-7]")
|
| 187 |
+
_QuotePatt = re.compile(r"[\\].")
|
| 188 |
+
|
| 189 |
+
def _unquote(str):
|
| 190 |
+
# If there aren't any doublequotes,
|
| 191 |
+
# then there can't be any special characters. See RFC 2109.
|
| 192 |
+
if str is None or len(str) < 2:
|
| 193 |
+
return str
|
| 194 |
+
if str[0] != '"' or str[-1] != '"':
|
| 195 |
+
return str
|
| 196 |
+
|
| 197 |
+
# We have to assume that we must decode this string.
|
| 198 |
+
# Down to work.
|
| 199 |
+
|
| 200 |
+
# Remove the "s
|
| 201 |
+
str = str[1:-1]
|
| 202 |
+
|
| 203 |
+
# Check for special sequences. Examples:
|
| 204 |
+
# \012 --> \n
|
| 205 |
+
# \" --> "
|
| 206 |
+
#
|
| 207 |
+
i = 0
|
| 208 |
+
n = len(str)
|
| 209 |
+
res = []
|
| 210 |
+
while 0 <= i < n:
|
| 211 |
+
o_match = _OctalPatt.search(str, i)
|
| 212 |
+
q_match = _QuotePatt.search(str, i)
|
| 213 |
+
if not o_match and not q_match: # Neither matched
|
| 214 |
+
res.append(str[i:])
|
| 215 |
+
break
|
| 216 |
+
# else:
|
| 217 |
+
j = k = -1
|
| 218 |
+
if o_match:
|
| 219 |
+
j = o_match.start(0)
|
| 220 |
+
if q_match:
|
| 221 |
+
k = q_match.start(0)
|
| 222 |
+
if q_match and (not o_match or k < j): # QuotePatt matched
|
| 223 |
+
res.append(str[i:k])
|
| 224 |
+
res.append(str[k+1])
|
| 225 |
+
i = k + 2
|
| 226 |
+
else: # OctalPatt matched
|
| 227 |
+
res.append(str[i:j])
|
| 228 |
+
res.append(chr(int(str[j+1:j+4], 8)))
|
| 229 |
+
i = j + 4
|
| 230 |
+
return _nulljoin(res)
|
| 231 |
+
|
| 232 |
+
# The _getdate() routine is used to set the expiration time in the cookie's HTTP
|
| 233 |
+
# header. By default, _getdate() returns the current time in the appropriate
|
| 234 |
+
# "expires" format for a Set-Cookie header. The one optional argument is an
|
| 235 |
+
# offset from now, in seconds. For example, an offset of -3600 means "one hour
|
| 236 |
+
# ago". The offset may be a floating point number.
|
| 237 |
+
#
|
| 238 |
+
|
| 239 |
+
_weekdayname = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
|
| 240 |
+
|
| 241 |
+
_monthname = [None,
|
| 242 |
+
'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
|
| 243 |
+
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
|
| 244 |
+
|
| 245 |
+
def _getdate(future=0, weekdayname=_weekdayname, monthname=_monthname):
|
| 246 |
+
from time import gmtime, time
|
| 247 |
+
now = time()
|
| 248 |
+
year, month, day, hh, mm, ss, wd, y, z = gmtime(now + future)
|
| 249 |
+
return "%s, %02d %3s %4d %02d:%02d:%02d GMT" % \
|
| 250 |
+
(weekdayname[wd], day, monthname[month], year, hh, mm, ss)
|
| 251 |
+
|
| 252 |
+
|
| 253 |
+
class Morsel(dict):
|
| 254 |
+
"""A class to hold ONE (key, value) pair.
|
| 255 |
+
|
| 256 |
+
In a cookie, each such pair may have several attributes, so this class is
|
| 257 |
+
used to keep the attributes associated with the appropriate key,value pair.
|
| 258 |
+
This class also includes a coded_value attribute, which is used to hold
|
| 259 |
+
the network representation of the value.
|
| 260 |
+
"""
|
| 261 |
+
# RFC 2109 lists these attributes as reserved:
|
| 262 |
+
# path comment domain
|
| 263 |
+
# max-age secure version
|
| 264 |
+
#
|
| 265 |
+
# For historical reasons, these attributes are also reserved:
|
| 266 |
+
# expires
|
| 267 |
+
#
|
| 268 |
+
# This is an extension from Microsoft:
|
| 269 |
+
# httponly
|
| 270 |
+
#
|
| 271 |
+
# This dictionary provides a mapping from the lowercase
|
| 272 |
+
# variant on the left to the appropriate traditional
|
| 273 |
+
# formatting on the right.
|
| 274 |
+
_reserved = {
|
| 275 |
+
"expires" : "expires",
|
| 276 |
+
"path" : "Path",
|
| 277 |
+
"comment" : "Comment",
|
| 278 |
+
"domain" : "Domain",
|
| 279 |
+
"max-age" : "Max-Age",
|
| 280 |
+
"secure" : "Secure",
|
| 281 |
+
"httponly" : "HttpOnly",
|
| 282 |
+
"version" : "Version",
|
| 283 |
+
"samesite" : "SameSite",
|
| 284 |
+
}
|
| 285 |
+
|
| 286 |
+
_flags = {'secure', 'httponly'}
|
| 287 |
+
|
| 288 |
+
def __init__(self):
|
| 289 |
+
# Set defaults
|
| 290 |
+
self._key = self._value = self._coded_value = None
|
| 291 |
+
|
| 292 |
+
# Set default attributes
|
| 293 |
+
for key in self._reserved:
|
| 294 |
+
dict.__setitem__(self, key, "")
|
| 295 |
+
|
| 296 |
+
@property
|
| 297 |
+
def key(self):
|
| 298 |
+
return self._key
|
| 299 |
+
|
| 300 |
+
@property
|
| 301 |
+
def value(self):
|
| 302 |
+
return self._value
|
| 303 |
+
|
| 304 |
+
@property
|
| 305 |
+
def coded_value(self):
|
| 306 |
+
return self._coded_value
|
| 307 |
+
|
| 308 |
+
def __setitem__(self, K, V):
|
| 309 |
+
K = K.lower()
|
| 310 |
+
if not K in self._reserved:
|
| 311 |
+
raise CookieError("Invalid attribute %r" % (K,))
|
| 312 |
+
dict.__setitem__(self, K, V)
|
| 313 |
+
|
| 314 |
+
def setdefault(self, key, val=None):
|
| 315 |
+
key = key.lower()
|
| 316 |
+
if key not in self._reserved:
|
| 317 |
+
raise CookieError("Invalid attribute %r" % (key,))
|
| 318 |
+
return dict.setdefault(self, key, val)
|
| 319 |
+
|
| 320 |
+
def __eq__(self, morsel):
|
| 321 |
+
if not isinstance(morsel, Morsel):
|
| 322 |
+
return NotImplemented
|
| 323 |
+
return (dict.__eq__(self, morsel) and
|
| 324 |
+
self._value == morsel._value and
|
| 325 |
+
self._key == morsel._key and
|
| 326 |
+
self._coded_value == morsel._coded_value)
|
| 327 |
+
|
| 328 |
+
__ne__ = object.__ne__
|
| 329 |
+
|
| 330 |
+
def copy(self):
|
| 331 |
+
morsel = Morsel()
|
| 332 |
+
dict.update(morsel, self)
|
| 333 |
+
morsel.__dict__.update(self.__dict__)
|
| 334 |
+
return morsel
|
| 335 |
+
|
| 336 |
+
def update(self, values):
|
| 337 |
+
data = {}
|
| 338 |
+
for key, val in dict(values).items():
|
| 339 |
+
key = key.lower()
|
| 340 |
+
if key not in self._reserved:
|
| 341 |
+
raise CookieError("Invalid attribute %r" % (key,))
|
| 342 |
+
data[key] = val
|
| 343 |
+
dict.update(self, data)
|
| 344 |
+
|
| 345 |
+
def isReservedKey(self, K):
|
| 346 |
+
return K.lower() in self._reserved
|
| 347 |
+
|
| 348 |
+
def set(self, key, val, coded_val):
|
| 349 |
+
if key.lower() in self._reserved:
|
| 350 |
+
raise CookieError('Attempt to set a reserved key %r' % (key,))
|
| 351 |
+
if not _is_legal_key(key):
|
| 352 |
+
raise CookieError('Illegal key %r' % (key,))
|
| 353 |
+
|
| 354 |
+
# It's a good key, so save it.
|
| 355 |
+
self._key = key
|
| 356 |
+
self._value = val
|
| 357 |
+
self._coded_value = coded_val
|
| 358 |
+
|
| 359 |
+
def __getstate__(self):
|
| 360 |
+
return {
|
| 361 |
+
'key': self._key,
|
| 362 |
+
'value': self._value,
|
| 363 |
+
'coded_value': self._coded_value,
|
| 364 |
+
}
|
| 365 |
+
|
| 366 |
+
def __setstate__(self, state):
|
| 367 |
+
self._key = state['key']
|
| 368 |
+
self._value = state['value']
|
| 369 |
+
self._coded_value = state['coded_value']
|
| 370 |
+
|
| 371 |
+
def output(self, attrs=None, header="Set-Cookie:"):
|
| 372 |
+
return "%s %s" % (header, self.OutputString(attrs))
|
| 373 |
+
|
| 374 |
+
__str__ = output
|
| 375 |
+
|
| 376 |
+
def __repr__(self):
|
| 377 |
+
return '<%s: %s>' % (self.__class__.__name__, self.OutputString())
|
| 378 |
+
|
| 379 |
+
def js_output(self, attrs=None):
|
| 380 |
+
# Print javascript
|
| 381 |
+
return """
|
| 382 |
+
<script type="text/javascript">
|
| 383 |
+
<!-- begin hiding
|
| 384 |
+
document.cookie = \"%s\";
|
| 385 |
+
// end hiding -->
|
| 386 |
+
</script>
|
| 387 |
+
""" % (self.OutputString(attrs).replace('"', r'\"'))
|
| 388 |
+
|
| 389 |
+
def OutputString(self, attrs=None):
|
| 390 |
+
# Build up our result
|
| 391 |
+
#
|
| 392 |
+
result = []
|
| 393 |
+
append = result.append
|
| 394 |
+
|
| 395 |
+
# First, the key=value pair
|
| 396 |
+
append("%s=%s" % (self.key, self.coded_value))
|
| 397 |
+
|
| 398 |
+
# Now add any defined attributes
|
| 399 |
+
if attrs is None:
|
| 400 |
+
attrs = self._reserved
|
| 401 |
+
items = sorted(self.items())
|
| 402 |
+
for key, value in items:
|
| 403 |
+
if value == "":
|
| 404 |
+
continue
|
| 405 |
+
if key not in attrs:
|
| 406 |
+
continue
|
| 407 |
+
if key == "expires" and isinstance(value, int):
|
| 408 |
+
append("%s=%s" % (self._reserved[key], _getdate(value)))
|
| 409 |
+
elif key == "max-age" and isinstance(value, int):
|
| 410 |
+
append("%s=%d" % (self._reserved[key], value))
|
| 411 |
+
elif key == "comment" and isinstance(value, str):
|
| 412 |
+
append("%s=%s" % (self._reserved[key], _quote(value)))
|
| 413 |
+
elif key in self._flags:
|
| 414 |
+
if value:
|
| 415 |
+
append(str(self._reserved[key]))
|
| 416 |
+
else:
|
| 417 |
+
append("%s=%s" % (self._reserved[key], value))
|
| 418 |
+
|
| 419 |
+
# Return the result
|
| 420 |
+
return _semispacejoin(result)
|
| 421 |
+
|
| 422 |
+
|
| 423 |
+
#
|
| 424 |
+
# Pattern for finding cookie
|
| 425 |
+
#
|
| 426 |
+
# This used to be strict parsing based on the RFC2109 and RFC2068
|
| 427 |
+
# specifications. I have since discovered that MSIE 3.0x doesn't
|
| 428 |
+
# follow the character rules outlined in those specs. As a
|
| 429 |
+
# result, the parsing rules here are less strict.
|
| 430 |
+
#
|
| 431 |
+
|
| 432 |
+
_LegalKeyChars = r"\w\d!#%&'~_`><@,:/\$\*\+\-\.\^\|\)\(\?\}\{\="
|
| 433 |
+
_LegalValueChars = _LegalKeyChars + r'\[\]'
|
| 434 |
+
_CookiePattern = re.compile(r"""
|
| 435 |
+
\s* # Optional whitespace at start of cookie
|
| 436 |
+
(?P<key> # Start of group 'key'
|
| 437 |
+
[""" + _LegalKeyChars + r"""]+? # Any word of at least one letter
|
| 438 |
+
) # End of group 'key'
|
| 439 |
+
( # Optional group: there may not be a value.
|
| 440 |
+
\s*=\s* # Equal Sign
|
| 441 |
+
(?P<val> # Start of group 'val'
|
| 442 |
+
"(?:[^\\"]|\\.)*" # Any doublequoted string
|
| 443 |
+
| # or
|
| 444 |
+
\w{3},\s[\w\d\s-]{9,11}\s[\d:]{8}\sGMT # Special case for "expires" attr
|
| 445 |
+
| # or
|
| 446 |
+
[""" + _LegalValueChars + r"""]* # Any word or empty string
|
| 447 |
+
) # End of group 'val'
|
| 448 |
+
)? # End of optional value group
|
| 449 |
+
\s* # Any number of spaces.
|
| 450 |
+
(\s+|;|$) # Ending either at space, semicolon, or EOS.
|
| 451 |
+
""", re.ASCII | re.VERBOSE) # re.ASCII may be removed if safe.
|
| 452 |
+
|
| 453 |
+
|
| 454 |
+
# At long last, here is the cookie class. Using this class is almost just like
|
| 455 |
+
# using a dictionary. See this module's docstring for example usage.
|
| 456 |
+
#
|
| 457 |
+
class BaseCookie(dict):
|
| 458 |
+
"""A container class for a set of Morsels."""
|
| 459 |
+
|
| 460 |
+
def value_decode(self, val):
|
| 461 |
+
"""real_value, coded_value = value_decode(STRING)
|
| 462 |
+
Called prior to setting a cookie's value from the network
|
| 463 |
+
representation. The VALUE is the value read from HTTP
|
| 464 |
+
header.
|
| 465 |
+
Override this function to modify the behavior of cookies.
|
| 466 |
+
"""
|
| 467 |
+
return val, val
|
| 468 |
+
|
| 469 |
+
def value_encode(self, val):
|
| 470 |
+
"""real_value, coded_value = value_encode(VALUE)
|
| 471 |
+
Called prior to setting a cookie's value from the dictionary
|
| 472 |
+
representation. The VALUE is the value being assigned.
|
| 473 |
+
Override this function to modify the behavior of cookies.
|
| 474 |
+
"""
|
| 475 |
+
strval = str(val)
|
| 476 |
+
return strval, strval
|
| 477 |
+
|
| 478 |
+
def __init__(self, input=None):
|
| 479 |
+
if input:
|
| 480 |
+
self.load(input)
|
| 481 |
+
|
| 482 |
+
def __set(self, key, real_value, coded_value):
|
| 483 |
+
"""Private method for setting a cookie's value"""
|
| 484 |
+
M = self.get(key, Morsel())
|
| 485 |
+
M.set(key, real_value, coded_value)
|
| 486 |
+
dict.__setitem__(self, key, M)
|
| 487 |
+
|
| 488 |
+
def __setitem__(self, key, value):
|
| 489 |
+
"""Dictionary style assignment."""
|
| 490 |
+
if isinstance(value, Morsel):
|
| 491 |
+
# allow assignment of constructed Morsels (e.g. for pickling)
|
| 492 |
+
dict.__setitem__(self, key, value)
|
| 493 |
+
else:
|
| 494 |
+
rval, cval = self.value_encode(value)
|
| 495 |
+
self.__set(key, rval, cval)
|
| 496 |
+
|
| 497 |
+
def output(self, attrs=None, header="Set-Cookie:", sep="\015\012"):
|
| 498 |
+
"""Return a string suitable for HTTP."""
|
| 499 |
+
result = []
|
| 500 |
+
items = sorted(self.items())
|
| 501 |
+
for key, value in items:
|
| 502 |
+
result.append(value.output(attrs, header))
|
| 503 |
+
return sep.join(result)
|
| 504 |
+
|
| 505 |
+
__str__ = output
|
| 506 |
+
|
| 507 |
+
def __repr__(self):
|
| 508 |
+
l = []
|
| 509 |
+
items = sorted(self.items())
|
| 510 |
+
for key, value in items:
|
| 511 |
+
l.append('%s=%s' % (key, repr(value.value)))
|
| 512 |
+
return '<%s: %s>' % (self.__class__.__name__, _spacejoin(l))
|
| 513 |
+
|
| 514 |
+
def js_output(self, attrs=None):
|
| 515 |
+
"""Return a string suitable for JavaScript."""
|
| 516 |
+
result = []
|
| 517 |
+
items = sorted(self.items())
|
| 518 |
+
for key, value in items:
|
| 519 |
+
result.append(value.js_output(attrs))
|
| 520 |
+
return _nulljoin(result)
|
| 521 |
+
|
| 522 |
+
def load(self, rawdata):
|
| 523 |
+
"""Load cookies from a string (presumably HTTP_COOKIE) or
|
| 524 |
+
from a dictionary. Loading cookies from a dictionary 'd'
|
| 525 |
+
is equivalent to calling:
|
| 526 |
+
map(Cookie.__setitem__, d.keys(), d.values())
|
| 527 |
+
"""
|
| 528 |
+
if isinstance(rawdata, str):
|
| 529 |
+
self.__parse_string(rawdata)
|
| 530 |
+
else:
|
| 531 |
+
# self.update() wouldn't call our custom __setitem__
|
| 532 |
+
for key, value in rawdata.items():
|
| 533 |
+
self[key] = value
|
| 534 |
+
return
|
| 535 |
+
|
| 536 |
+
def __parse_string(self, str, patt=_CookiePattern):
|
| 537 |
+
i = 0 # Our starting point
|
| 538 |
+
n = len(str) # Length of string
|
| 539 |
+
parsed_items = [] # Parsed (type, key, value) triples
|
| 540 |
+
morsel_seen = False # A key=value pair was previously encountered
|
| 541 |
+
|
| 542 |
+
TYPE_ATTRIBUTE = 1
|
| 543 |
+
TYPE_KEYVALUE = 2
|
| 544 |
+
|
| 545 |
+
# We first parse the whole cookie string and reject it if it's
|
| 546 |
+
# syntactically invalid (this helps avoid some classes of injection
|
| 547 |
+
# attacks).
|
| 548 |
+
while 0 <= i < n:
|
| 549 |
+
# Start looking for a cookie
|
| 550 |
+
match = patt.match(str, i)
|
| 551 |
+
if not match:
|
| 552 |
+
# No more cookies
|
| 553 |
+
break
|
| 554 |
+
|
| 555 |
+
key, value = match.group("key"), match.group("val")
|
| 556 |
+
i = match.end(0)
|
| 557 |
+
|
| 558 |
+
if key[0] == "$":
|
| 559 |
+
if not morsel_seen:
|
| 560 |
+
# We ignore attributes which pertain to the cookie
|
| 561 |
+
# mechanism as a whole, such as "$Version".
|
| 562 |
+
# See RFC 2965. (Does anyone care?)
|
| 563 |
+
continue
|
| 564 |
+
parsed_items.append((TYPE_ATTRIBUTE, key[1:], value))
|
| 565 |
+
elif key.lower() in Morsel._reserved:
|
| 566 |
+
if not morsel_seen:
|
| 567 |
+
# Invalid cookie string
|
| 568 |
+
return
|
| 569 |
+
if value is None:
|
| 570 |
+
if key.lower() in Morsel._flags:
|
| 571 |
+
parsed_items.append((TYPE_ATTRIBUTE, key, True))
|
| 572 |
+
else:
|
| 573 |
+
# Invalid cookie string
|
| 574 |
+
return
|
| 575 |
+
else:
|
| 576 |
+
parsed_items.append((TYPE_ATTRIBUTE, key, _unquote(value)))
|
| 577 |
+
elif value is not None:
|
| 578 |
+
parsed_items.append((TYPE_KEYVALUE, key, self.value_decode(value)))
|
| 579 |
+
morsel_seen = True
|
| 580 |
+
else:
|
| 581 |
+
# Invalid cookie string
|
| 582 |
+
return
|
| 583 |
+
|
| 584 |
+
# The cookie string is valid, apply it.
|
| 585 |
+
M = None # current morsel
|
| 586 |
+
for tp, key, value in parsed_items:
|
| 587 |
+
if tp == TYPE_ATTRIBUTE:
|
| 588 |
+
assert M is not None
|
| 589 |
+
M[key] = value
|
| 590 |
+
else:
|
| 591 |
+
assert tp == TYPE_KEYVALUE
|
| 592 |
+
rval, cval = value
|
| 593 |
+
self.__set(key, rval, cval)
|
| 594 |
+
M = self[key]
|
| 595 |
+
|
| 596 |
+
|
| 597 |
+
class SimpleCookie(BaseCookie):
|
| 598 |
+
"""
|
| 599 |
+
SimpleCookie supports strings as cookie values. When setting
|
| 600 |
+
the value using the dictionary assignment notation, SimpleCookie
|
| 601 |
+
calls the builtin str() to convert the value to a string. Values
|
| 602 |
+
received from HTTP are kept as strings.
|
| 603 |
+
"""
|
| 604 |
+
def value_decode(self, val):
|
| 605 |
+
return _unquote(val), val
|
| 606 |
+
|
| 607 |
+
def value_encode(self, val):
|
| 608 |
+
strval = str(val)
|
| 609 |
+
return strval, _quote(strval)
|
my_container_sandbox/workspace/anaconda3/lib/python3.8/http/server.py
ADDED
|
@@ -0,0 +1,1316 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""HTTP server classes.
|
| 2 |
+
|
| 3 |
+
Note: BaseHTTPRequestHandler doesn't implement any HTTP request; see
|
| 4 |
+
SimpleHTTPRequestHandler for simple implementations of GET, HEAD and POST,
|
| 5 |
+
and CGIHTTPRequestHandler for CGI scripts.
|
| 6 |
+
|
| 7 |
+
It does, however, optionally implement HTTP/1.1 persistent connections,
|
| 8 |
+
as of version 0.3.
|
| 9 |
+
|
| 10 |
+
Notes on CGIHTTPRequestHandler
|
| 11 |
+
------------------------------
|
| 12 |
+
|
| 13 |
+
This class implements GET and POST requests to cgi-bin scripts.
|
| 14 |
+
|
| 15 |
+
If the os.fork() function is not present (e.g. on Windows),
|
| 16 |
+
subprocess.Popen() is used as a fallback, with slightly altered semantics.
|
| 17 |
+
|
| 18 |
+
In all cases, the implementation is intentionally naive -- all
|
| 19 |
+
requests are executed synchronously.
|
| 20 |
+
|
| 21 |
+
SECURITY WARNING: DON'T USE THIS CODE UNLESS YOU ARE INSIDE A FIREWALL
|
| 22 |
+
-- it may execute arbitrary Python code or external programs.
|
| 23 |
+
|
| 24 |
+
Note that status code 200 is sent prior to execution of a CGI script, so
|
| 25 |
+
scripts cannot send other status codes such as 302 (redirect).
|
| 26 |
+
|
| 27 |
+
XXX To do:
|
| 28 |
+
|
| 29 |
+
- log requests even later (to capture byte count)
|
| 30 |
+
- log user-agent header and other interesting goodies
|
| 31 |
+
- send error log to separate file
|
| 32 |
+
"""
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
# See also:
|
| 36 |
+
#
|
| 37 |
+
# HTTP Working Group T. Berners-Lee
|
| 38 |
+
# INTERNET-DRAFT R. T. Fielding
|
| 39 |
+
# <draft-ietf-http-v10-spec-00.txt> H. Frystyk Nielsen
|
| 40 |
+
# Expires September 8, 1995 March 8, 1995
|
| 41 |
+
#
|
| 42 |
+
# URL: http://www.ics.uci.edu/pub/ietf/http/draft-ietf-http-v10-spec-00.txt
|
| 43 |
+
#
|
| 44 |
+
# and
|
| 45 |
+
#
|
| 46 |
+
# Network Working Group R. Fielding
|
| 47 |
+
# Request for Comments: 2616 et al
|
| 48 |
+
# Obsoletes: 2068 June 1999
|
| 49 |
+
# Category: Standards Track
|
| 50 |
+
#
|
| 51 |
+
# URL: http://www.faqs.org/rfcs/rfc2616.html
|
| 52 |
+
|
| 53 |
+
# Log files
|
| 54 |
+
# ---------
|
| 55 |
+
#
|
| 56 |
+
# Here's a quote from the NCSA httpd docs about log file format.
|
| 57 |
+
#
|
| 58 |
+
# | The logfile format is as follows. Each line consists of:
|
| 59 |
+
# |
|
| 60 |
+
# | host rfc931 authuser [DD/Mon/YYYY:hh:mm:ss] "request" ddd bbbb
|
| 61 |
+
# |
|
| 62 |
+
# | host: Either the DNS name or the IP number of the remote client
|
| 63 |
+
# | rfc931: Any information returned by identd for this person,
|
| 64 |
+
# | - otherwise.
|
| 65 |
+
# | authuser: If user sent a userid for authentication, the user name,
|
| 66 |
+
# | - otherwise.
|
| 67 |
+
# | DD: Day
|
| 68 |
+
# | Mon: Month (calendar name)
|
| 69 |
+
# | YYYY: Year
|
| 70 |
+
# | hh: hour (24-hour format, the machine's timezone)
|
| 71 |
+
# | mm: minutes
|
| 72 |
+
# | ss: seconds
|
| 73 |
+
# | request: The first line of the HTTP request as sent by the client.
|
| 74 |
+
# | ddd: the status code returned by the server, - if not available.
|
| 75 |
+
# | bbbb: the total number of bytes sent,
|
| 76 |
+
# | *not including the HTTP/1.0 header*, - if not available
|
| 77 |
+
# |
|
| 78 |
+
# | You can determine the name of the file accessed through request.
|
| 79 |
+
#
|
| 80 |
+
# (Actually, the latter is only true if you know the server configuration
|
| 81 |
+
# at the time the request was made!)
|
| 82 |
+
|
| 83 |
+
__version__ = "0.6"
|
| 84 |
+
|
| 85 |
+
__all__ = [
|
| 86 |
+
"HTTPServer", "ThreadingHTTPServer", "BaseHTTPRequestHandler",
|
| 87 |
+
"SimpleHTTPRequestHandler", "CGIHTTPRequestHandler",
|
| 88 |
+
]
|
| 89 |
+
|
| 90 |
+
import copy
|
| 91 |
+
import datetime
|
| 92 |
+
import email.utils
|
| 93 |
+
import html
|
| 94 |
+
import http.client
|
| 95 |
+
import io
|
| 96 |
+
import itertools
|
| 97 |
+
import mimetypes
|
| 98 |
+
import os
|
| 99 |
+
import posixpath
|
| 100 |
+
import select
|
| 101 |
+
import shutil
|
| 102 |
+
import socket # For gethostbyaddr()
|
| 103 |
+
import socketserver
|
| 104 |
+
import sys
|
| 105 |
+
import time
|
| 106 |
+
import urllib.parse
|
| 107 |
+
import contextlib
|
| 108 |
+
from functools import partial
|
| 109 |
+
|
| 110 |
+
from http import HTTPStatus
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
# Default error message template
|
| 114 |
+
DEFAULT_ERROR_MESSAGE = """\
|
| 115 |
+
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
|
| 116 |
+
"http://www.w3.org/TR/html4/strict.dtd">
|
| 117 |
+
<html>
|
| 118 |
+
<head>
|
| 119 |
+
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
|
| 120 |
+
<title>Error response</title>
|
| 121 |
+
</head>
|
| 122 |
+
<body>
|
| 123 |
+
<h1>Error response</h1>
|
| 124 |
+
<p>Error code: %(code)d</p>
|
| 125 |
+
<p>Message: %(message)s.</p>
|
| 126 |
+
<p>Error code explanation: %(code)s - %(explain)s.</p>
|
| 127 |
+
</body>
|
| 128 |
+
</html>
|
| 129 |
+
"""
|
| 130 |
+
|
| 131 |
+
DEFAULT_ERROR_CONTENT_TYPE = "text/html;charset=utf-8"
|
| 132 |
+
|
| 133 |
+
class HTTPServer(socketserver.TCPServer):
|
| 134 |
+
|
| 135 |
+
allow_reuse_address = 1 # Seems to make sense in testing environment
|
| 136 |
+
|
| 137 |
+
def server_bind(self):
|
| 138 |
+
"""Override server_bind to store the server name."""
|
| 139 |
+
socketserver.TCPServer.server_bind(self)
|
| 140 |
+
host, port = self.server_address[:2]
|
| 141 |
+
self.server_name = socket.getfqdn(host)
|
| 142 |
+
self.server_port = port
|
| 143 |
+
|
| 144 |
+
|
| 145 |
+
class ThreadingHTTPServer(socketserver.ThreadingMixIn, HTTPServer):
|
| 146 |
+
daemon_threads = True
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
class BaseHTTPRequestHandler(socketserver.StreamRequestHandler):
|
| 150 |
+
|
| 151 |
+
"""HTTP request handler base class.
|
| 152 |
+
|
| 153 |
+
The following explanation of HTTP serves to guide you through the
|
| 154 |
+
code as well as to expose any misunderstandings I may have about
|
| 155 |
+
HTTP (so you don't need to read the code to figure out I'm wrong
|
| 156 |
+
:-).
|
| 157 |
+
|
| 158 |
+
HTTP (HyperText Transfer Protocol) is an extensible protocol on
|
| 159 |
+
top of a reliable stream transport (e.g. TCP/IP). The protocol
|
| 160 |
+
recognizes three parts to a request:
|
| 161 |
+
|
| 162 |
+
1. One line identifying the request type and path
|
| 163 |
+
2. An optional set of RFC-822-style headers
|
| 164 |
+
3. An optional data part
|
| 165 |
+
|
| 166 |
+
The headers and data are separated by a blank line.
|
| 167 |
+
|
| 168 |
+
The first line of the request has the form
|
| 169 |
+
|
| 170 |
+
<command> <path> <version>
|
| 171 |
+
|
| 172 |
+
where <command> is a (case-sensitive) keyword such as GET or POST,
|
| 173 |
+
<path> is a string containing path information for the request,
|
| 174 |
+
and <version> should be the string "HTTP/1.0" or "HTTP/1.1".
|
| 175 |
+
<path> is encoded using the URL encoding scheme (using %xx to signify
|
| 176 |
+
the ASCII character with hex code xx).
|
| 177 |
+
|
| 178 |
+
The specification specifies that lines are separated by CRLF but
|
| 179 |
+
for compatibility with the widest range of clients recommends
|
| 180 |
+
servers also handle LF. Similarly, whitespace in the request line
|
| 181 |
+
is treated sensibly (allowing multiple spaces between components
|
| 182 |
+
and allowing trailing whitespace).
|
| 183 |
+
|
| 184 |
+
Similarly, for output, lines ought to be separated by CRLF pairs
|
| 185 |
+
but most clients grok LF characters just fine.
|
| 186 |
+
|
| 187 |
+
If the first line of the request has the form
|
| 188 |
+
|
| 189 |
+
<command> <path>
|
| 190 |
+
|
| 191 |
+
(i.e. <version> is left out) then this is assumed to be an HTTP
|
| 192 |
+
0.9 request; this form has no optional headers and data part and
|
| 193 |
+
the reply consists of just the data.
|
| 194 |
+
|
| 195 |
+
The reply form of the HTTP 1.x protocol again has three parts:
|
| 196 |
+
|
| 197 |
+
1. One line giving the response code
|
| 198 |
+
2. An optional set of RFC-822-style headers
|
| 199 |
+
3. The data
|
| 200 |
+
|
| 201 |
+
Again, the headers and data are separated by a blank line.
|
| 202 |
+
|
| 203 |
+
The response code line has the form
|
| 204 |
+
|
| 205 |
+
<version> <responsecode> <responsestring>
|
| 206 |
+
|
| 207 |
+
where <version> is the protocol version ("HTTP/1.0" or "HTTP/1.1"),
|
| 208 |
+
<responsecode> is a 3-digit response code indicating success or
|
| 209 |
+
failure of the request, and <responsestring> is an optional
|
| 210 |
+
human-readable string explaining what the response code means.
|
| 211 |
+
|
| 212 |
+
This server parses the request and the headers, and then calls a
|
| 213 |
+
function specific to the request type (<command>). Specifically,
|
| 214 |
+
a request SPAM will be handled by a method do_SPAM(). If no
|
| 215 |
+
such method exists the server sends an error response to the
|
| 216 |
+
client. If it exists, it is called with no arguments:
|
| 217 |
+
|
| 218 |
+
do_SPAM()
|
| 219 |
+
|
| 220 |
+
Note that the request name is case sensitive (i.e. SPAM and spam
|
| 221 |
+
are different requests).
|
| 222 |
+
|
| 223 |
+
The various request details are stored in instance variables:
|
| 224 |
+
|
| 225 |
+
- client_address is the client IP address in the form (host,
|
| 226 |
+
port);
|
| 227 |
+
|
| 228 |
+
- command, path and version are the broken-down request line;
|
| 229 |
+
|
| 230 |
+
- headers is an instance of email.message.Message (or a derived
|
| 231 |
+
class) containing the header information;
|
| 232 |
+
|
| 233 |
+
- rfile is a file object open for reading positioned at the
|
| 234 |
+
start of the optional input data part;
|
| 235 |
+
|
| 236 |
+
- wfile is a file object open for writing.
|
| 237 |
+
|
| 238 |
+
IT IS IMPORTANT TO ADHERE TO THE PROTOCOL FOR WRITING!
|
| 239 |
+
|
| 240 |
+
The first thing to be written must be the response line. Then
|
| 241 |
+
follow 0 or more header lines, then a blank line, and then the
|
| 242 |
+
actual data (if any). The meaning of the header lines depends on
|
| 243 |
+
the command executed by the server; in most cases, when data is
|
| 244 |
+
returned, there should be at least one header line of the form
|
| 245 |
+
|
| 246 |
+
Content-type: <type>/<subtype>
|
| 247 |
+
|
| 248 |
+
where <type> and <subtype> should be registered MIME types,
|
| 249 |
+
e.g. "text/html" or "text/plain".
|
| 250 |
+
|
| 251 |
+
"""
|
| 252 |
+
|
| 253 |
+
# The Python system version, truncated to its first component.
|
| 254 |
+
sys_version = "Python/" + sys.version.split()[0]
|
| 255 |
+
|
| 256 |
+
# The server software version. You may want to override this.
|
| 257 |
+
# The format is multiple whitespace-separated strings,
|
| 258 |
+
# where each string is of the form name[/version].
|
| 259 |
+
server_version = "BaseHTTP/" + __version__
|
| 260 |
+
|
| 261 |
+
error_message_format = DEFAULT_ERROR_MESSAGE
|
| 262 |
+
error_content_type = DEFAULT_ERROR_CONTENT_TYPE
|
| 263 |
+
|
| 264 |
+
# The default request version. This only affects responses up until
|
| 265 |
+
# the point where the request line is parsed, so it mainly decides what
|
| 266 |
+
# the client gets back when sending a malformed request line.
|
| 267 |
+
# Most web servers default to HTTP 0.9, i.e. don't send a status line.
|
| 268 |
+
default_request_version = "HTTP/0.9"
|
| 269 |
+
|
| 270 |
+
def parse_request(self):
|
| 271 |
+
"""Parse a request (internal).
|
| 272 |
+
|
| 273 |
+
The request should be stored in self.raw_requestline; the results
|
| 274 |
+
are in self.command, self.path, self.request_version and
|
| 275 |
+
self.headers.
|
| 276 |
+
|
| 277 |
+
Return True for success, False for failure; on failure, any relevant
|
| 278 |
+
error response has already been sent back.
|
| 279 |
+
|
| 280 |
+
"""
|
| 281 |
+
self.command = None # set in case of error on the first line
|
| 282 |
+
self.request_version = version = self.default_request_version
|
| 283 |
+
self.close_connection = True
|
| 284 |
+
requestline = str(self.raw_requestline, 'iso-8859-1')
|
| 285 |
+
requestline = requestline.rstrip('\r\n')
|
| 286 |
+
self.requestline = requestline
|
| 287 |
+
words = requestline.split()
|
| 288 |
+
if len(words) == 0:
|
| 289 |
+
return False
|
| 290 |
+
|
| 291 |
+
if len(words) >= 3: # Enough to determine protocol version
|
| 292 |
+
version = words[-1]
|
| 293 |
+
try:
|
| 294 |
+
if not version.startswith('HTTP/'):
|
| 295 |
+
raise ValueError
|
| 296 |
+
base_version_number = version.split('/', 1)[1]
|
| 297 |
+
version_number = base_version_number.split(".")
|
| 298 |
+
# RFC 2145 section 3.1 says there can be only one "." and
|
| 299 |
+
# - major and minor numbers MUST be treated as
|
| 300 |
+
# separate integers;
|
| 301 |
+
# - HTTP/2.4 is a lower version than HTTP/2.13, which in
|
| 302 |
+
# turn is lower than HTTP/12.3;
|
| 303 |
+
# - Leading zeros MUST be ignored by recipients.
|
| 304 |
+
if len(version_number) != 2:
|
| 305 |
+
raise ValueError
|
| 306 |
+
version_number = int(version_number[0]), int(version_number[1])
|
| 307 |
+
except (ValueError, IndexError):
|
| 308 |
+
self.send_error(
|
| 309 |
+
HTTPStatus.BAD_REQUEST,
|
| 310 |
+
"Bad request version (%r)" % version)
|
| 311 |
+
return False
|
| 312 |
+
if version_number >= (1, 1) and self.protocol_version >= "HTTP/1.1":
|
| 313 |
+
self.close_connection = False
|
| 314 |
+
if version_number >= (2, 0):
|
| 315 |
+
self.send_error(
|
| 316 |
+
HTTPStatus.HTTP_VERSION_NOT_SUPPORTED,
|
| 317 |
+
"Invalid HTTP version (%s)" % base_version_number)
|
| 318 |
+
return False
|
| 319 |
+
self.request_version = version
|
| 320 |
+
|
| 321 |
+
if not 2 <= len(words) <= 3:
|
| 322 |
+
self.send_error(
|
| 323 |
+
HTTPStatus.BAD_REQUEST,
|
| 324 |
+
"Bad request syntax (%r)" % requestline)
|
| 325 |
+
return False
|
| 326 |
+
command, path = words[:2]
|
| 327 |
+
if len(words) == 2:
|
| 328 |
+
self.close_connection = True
|
| 329 |
+
if command != 'GET':
|
| 330 |
+
self.send_error(
|
| 331 |
+
HTTPStatus.BAD_REQUEST,
|
| 332 |
+
"Bad HTTP/0.9 request type (%r)" % command)
|
| 333 |
+
return False
|
| 334 |
+
self.command, self.path = command, path
|
| 335 |
+
|
| 336 |
+
# gh-87389: The purpose of replacing '//' with '/' is to protect
|
| 337 |
+
# against open redirect attacks possibly triggered if the path starts
|
| 338 |
+
# with '//' because http clients treat //path as an absolute URI
|
| 339 |
+
# without scheme (similar to http://path) rather than a path.
|
| 340 |
+
if self.path.startswith('//'):
|
| 341 |
+
self.path = '/' + self.path.lstrip('/') # Reduce to a single /
|
| 342 |
+
|
| 343 |
+
# Examine the headers and look for a Connection directive.
|
| 344 |
+
try:
|
| 345 |
+
self.headers = http.client.parse_headers(self.rfile,
|
| 346 |
+
_class=self.MessageClass)
|
| 347 |
+
except http.client.LineTooLong as err:
|
| 348 |
+
self.send_error(
|
| 349 |
+
HTTPStatus.REQUEST_HEADER_FIELDS_TOO_LARGE,
|
| 350 |
+
"Line too long",
|
| 351 |
+
str(err))
|
| 352 |
+
return False
|
| 353 |
+
except http.client.HTTPException as err:
|
| 354 |
+
self.send_error(
|
| 355 |
+
HTTPStatus.REQUEST_HEADER_FIELDS_TOO_LARGE,
|
| 356 |
+
"Too many headers",
|
| 357 |
+
str(err)
|
| 358 |
+
)
|
| 359 |
+
return False
|
| 360 |
+
|
| 361 |
+
conntype = self.headers.get('Connection', "")
|
| 362 |
+
if conntype.lower() == 'close':
|
| 363 |
+
self.close_connection = True
|
| 364 |
+
elif (conntype.lower() == 'keep-alive' and
|
| 365 |
+
self.protocol_version >= "HTTP/1.1"):
|
| 366 |
+
self.close_connection = False
|
| 367 |
+
# Examine the headers and look for an Expect directive
|
| 368 |
+
expect = self.headers.get('Expect', "")
|
| 369 |
+
if (expect.lower() == "100-continue" and
|
| 370 |
+
self.protocol_version >= "HTTP/1.1" and
|
| 371 |
+
self.request_version >= "HTTP/1.1"):
|
| 372 |
+
if not self.handle_expect_100():
|
| 373 |
+
return False
|
| 374 |
+
return True
|
| 375 |
+
|
| 376 |
+
def handle_expect_100(self):
|
| 377 |
+
"""Decide what to do with an "Expect: 100-continue" header.
|
| 378 |
+
|
| 379 |
+
If the client is expecting a 100 Continue response, we must
|
| 380 |
+
respond with either a 100 Continue or a final response before
|
| 381 |
+
waiting for the request body. The default is to always respond
|
| 382 |
+
with a 100 Continue. You can behave differently (for example,
|
| 383 |
+
reject unauthorized requests) by overriding this method.
|
| 384 |
+
|
| 385 |
+
This method should either return True (possibly after sending
|
| 386 |
+
a 100 Continue response) or send an error response and return
|
| 387 |
+
False.
|
| 388 |
+
|
| 389 |
+
"""
|
| 390 |
+
self.send_response_only(HTTPStatus.CONTINUE)
|
| 391 |
+
self.end_headers()
|
| 392 |
+
return True
|
| 393 |
+
|
| 394 |
+
def handle_one_request(self):
|
| 395 |
+
"""Handle a single HTTP request.
|
| 396 |
+
|
| 397 |
+
You normally don't need to override this method; see the class
|
| 398 |
+
__doc__ string for information on how to handle specific HTTP
|
| 399 |
+
commands such as GET and POST.
|
| 400 |
+
|
| 401 |
+
"""
|
| 402 |
+
try:
|
| 403 |
+
self.raw_requestline = self.rfile.readline(65537)
|
| 404 |
+
if len(self.raw_requestline) > 65536:
|
| 405 |
+
self.requestline = ''
|
| 406 |
+
self.request_version = ''
|
| 407 |
+
self.command = ''
|
| 408 |
+
self.send_error(HTTPStatus.REQUEST_URI_TOO_LONG)
|
| 409 |
+
return
|
| 410 |
+
if not self.raw_requestline:
|
| 411 |
+
self.close_connection = True
|
| 412 |
+
return
|
| 413 |
+
if not self.parse_request():
|
| 414 |
+
# An error code has been sent, just exit
|
| 415 |
+
return
|
| 416 |
+
mname = 'do_' + self.command
|
| 417 |
+
if not hasattr(self, mname):
|
| 418 |
+
self.send_error(
|
| 419 |
+
HTTPStatus.NOT_IMPLEMENTED,
|
| 420 |
+
"Unsupported method (%r)" % self.command)
|
| 421 |
+
return
|
| 422 |
+
method = getattr(self, mname)
|
| 423 |
+
method()
|
| 424 |
+
self.wfile.flush() #actually send the response if not already done.
|
| 425 |
+
except socket.timeout as e:
|
| 426 |
+
#a read or a write timed out. Discard this connection
|
| 427 |
+
self.log_error("Request timed out: %r", e)
|
| 428 |
+
self.close_connection = True
|
| 429 |
+
return
|
| 430 |
+
|
| 431 |
+
def handle(self):
|
| 432 |
+
"""Handle multiple requests if necessary."""
|
| 433 |
+
self.close_connection = True
|
| 434 |
+
|
| 435 |
+
self.handle_one_request()
|
| 436 |
+
while not self.close_connection:
|
| 437 |
+
self.handle_one_request()
|
| 438 |
+
|
| 439 |
+
def send_error(self, code, message=None, explain=None):
|
| 440 |
+
"""Send and log an error reply.
|
| 441 |
+
|
| 442 |
+
Arguments are
|
| 443 |
+
* code: an HTTP error code
|
| 444 |
+
3 digits
|
| 445 |
+
* message: a simple optional 1 line reason phrase.
|
| 446 |
+
*( HTAB / SP / VCHAR / %x80-FF )
|
| 447 |
+
defaults to short entry matching the response code
|
| 448 |
+
* explain: a detailed message defaults to the long entry
|
| 449 |
+
matching the response code.
|
| 450 |
+
|
| 451 |
+
This sends an error response (so it must be called before any
|
| 452 |
+
output has been generated), logs the error, and finally sends
|
| 453 |
+
a piece of HTML explaining the error to the user.
|
| 454 |
+
|
| 455 |
+
"""
|
| 456 |
+
|
| 457 |
+
try:
|
| 458 |
+
shortmsg, longmsg = self.responses[code]
|
| 459 |
+
except KeyError:
|
| 460 |
+
shortmsg, longmsg = '???', '???'
|
| 461 |
+
if message is None:
|
| 462 |
+
message = shortmsg
|
| 463 |
+
if explain is None:
|
| 464 |
+
explain = longmsg
|
| 465 |
+
self.log_error("code %d, message %s", code, message)
|
| 466 |
+
self.send_response(code, message)
|
| 467 |
+
self.send_header('Connection', 'close')
|
| 468 |
+
|
| 469 |
+
# Message body is omitted for cases described in:
|
| 470 |
+
# - RFC7230: 3.3. 1xx, 204(No Content), 304(Not Modified)
|
| 471 |
+
# - RFC7231: 6.3.6. 205(Reset Content)
|
| 472 |
+
body = None
|
| 473 |
+
if (code >= 200 and
|
| 474 |
+
code not in (HTTPStatus.NO_CONTENT,
|
| 475 |
+
HTTPStatus.RESET_CONTENT,
|
| 476 |
+
HTTPStatus.NOT_MODIFIED)):
|
| 477 |
+
# HTML encode to prevent Cross Site Scripting attacks
|
| 478 |
+
# (see bug #1100201)
|
| 479 |
+
content = (self.error_message_format % {
|
| 480 |
+
'code': code,
|
| 481 |
+
'message': html.escape(message, quote=False),
|
| 482 |
+
'explain': html.escape(explain, quote=False)
|
| 483 |
+
})
|
| 484 |
+
body = content.encode('UTF-8', 'replace')
|
| 485 |
+
self.send_header("Content-Type", self.error_content_type)
|
| 486 |
+
self.send_header('Content-Length', str(len(body)))
|
| 487 |
+
self.end_headers()
|
| 488 |
+
|
| 489 |
+
if self.command != 'HEAD' and body:
|
| 490 |
+
self.wfile.write(body)
|
| 491 |
+
|
| 492 |
+
def send_response(self, code, message=None):
|
| 493 |
+
"""Add the response header to the headers buffer and log the
|
| 494 |
+
response code.
|
| 495 |
+
|
| 496 |
+
Also send two standard headers with the server software
|
| 497 |
+
version and the current date.
|
| 498 |
+
|
| 499 |
+
"""
|
| 500 |
+
self.log_request(code)
|
| 501 |
+
self.send_response_only(code, message)
|
| 502 |
+
self.send_header('Server', self.version_string())
|
| 503 |
+
self.send_header('Date', self.date_time_string())
|
| 504 |
+
|
| 505 |
+
def send_response_only(self, code, message=None):
|
| 506 |
+
"""Send the response header only."""
|
| 507 |
+
if self.request_version != 'HTTP/0.9':
|
| 508 |
+
if message is None:
|
| 509 |
+
if code in self.responses:
|
| 510 |
+
message = self.responses[code][0]
|
| 511 |
+
else:
|
| 512 |
+
message = ''
|
| 513 |
+
if not hasattr(self, '_headers_buffer'):
|
| 514 |
+
self._headers_buffer = []
|
| 515 |
+
self._headers_buffer.append(("%s %d %s\r\n" %
|
| 516 |
+
(self.protocol_version, code, message)).encode(
|
| 517 |
+
'latin-1', 'strict'))
|
| 518 |
+
|
| 519 |
+
def send_header(self, keyword, value):
|
| 520 |
+
"""Send a MIME header to the headers buffer."""
|
| 521 |
+
if self.request_version != 'HTTP/0.9':
|
| 522 |
+
if not hasattr(self, '_headers_buffer'):
|
| 523 |
+
self._headers_buffer = []
|
| 524 |
+
self._headers_buffer.append(
|
| 525 |
+
("%s: %s\r\n" % (keyword, value)).encode('latin-1', 'strict'))
|
| 526 |
+
|
| 527 |
+
if keyword.lower() == 'connection':
|
| 528 |
+
if value.lower() == 'close':
|
| 529 |
+
self.close_connection = True
|
| 530 |
+
elif value.lower() == 'keep-alive':
|
| 531 |
+
self.close_connection = False
|
| 532 |
+
|
| 533 |
+
def end_headers(self):
|
| 534 |
+
"""Send the blank line ending the MIME headers."""
|
| 535 |
+
if self.request_version != 'HTTP/0.9':
|
| 536 |
+
self._headers_buffer.append(b"\r\n")
|
| 537 |
+
self.flush_headers()
|
| 538 |
+
|
| 539 |
+
def flush_headers(self):
|
| 540 |
+
if hasattr(self, '_headers_buffer'):
|
| 541 |
+
self.wfile.write(b"".join(self._headers_buffer))
|
| 542 |
+
self._headers_buffer = []
|
| 543 |
+
|
| 544 |
+
def log_request(self, code='-', size='-'):
|
| 545 |
+
"""Log an accepted request.
|
| 546 |
+
|
| 547 |
+
This is called by send_response().
|
| 548 |
+
|
| 549 |
+
"""
|
| 550 |
+
if isinstance(code, HTTPStatus):
|
| 551 |
+
code = code.value
|
| 552 |
+
self.log_message('"%s" %s %s',
|
| 553 |
+
self.requestline, str(code), str(size))
|
| 554 |
+
|
| 555 |
+
def log_error(self, format, *args):
|
| 556 |
+
"""Log an error.
|
| 557 |
+
|
| 558 |
+
This is called when a request cannot be fulfilled. By
|
| 559 |
+
default it passes the message on to log_message().
|
| 560 |
+
|
| 561 |
+
Arguments are the same as for log_message().
|
| 562 |
+
|
| 563 |
+
XXX This should go to the separate error log.
|
| 564 |
+
|
| 565 |
+
"""
|
| 566 |
+
|
| 567 |
+
self.log_message(format, *args)
|
| 568 |
+
|
| 569 |
+
# https://en.wikipedia.org/wiki/List_of_Unicode_characters#Control_codes
|
| 570 |
+
_control_char_table = str.maketrans(
|
| 571 |
+
{c: fr'\x{c:02x}' for c in itertools.chain(range(0x20), range(0x7f,0xa0))})
|
| 572 |
+
_control_char_table[ord('\\')] = r'\\'
|
| 573 |
+
|
| 574 |
+
def log_message(self, format, *args):
|
| 575 |
+
"""Log an arbitrary message.
|
| 576 |
+
|
| 577 |
+
This is used by all other logging functions. Override
|
| 578 |
+
it if you have specific logging wishes.
|
| 579 |
+
|
| 580 |
+
The first argument, FORMAT, is a format string for the
|
| 581 |
+
message to be logged. If the format string contains
|
| 582 |
+
any % escapes requiring parameters, they should be
|
| 583 |
+
specified as subsequent arguments (it's just like
|
| 584 |
+
printf!).
|
| 585 |
+
|
| 586 |
+
The client ip and current date/time are prefixed to
|
| 587 |
+
every message.
|
| 588 |
+
|
| 589 |
+
Unicode control characters are replaced with escaped hex
|
| 590 |
+
before writing the output to stderr.
|
| 591 |
+
|
| 592 |
+
"""
|
| 593 |
+
|
| 594 |
+
message = format % args
|
| 595 |
+
sys.stderr.write("%s - - [%s] %s\n" %
|
| 596 |
+
(self.address_string(),
|
| 597 |
+
self.log_date_time_string(),
|
| 598 |
+
message.translate(self._control_char_table)))
|
| 599 |
+
|
| 600 |
+
def version_string(self):
|
| 601 |
+
"""Return the server software version string."""
|
| 602 |
+
return self.server_version + ' ' + self.sys_version
|
| 603 |
+
|
| 604 |
+
def date_time_string(self, timestamp=None):
|
| 605 |
+
"""Return the current date and time formatted for a message header."""
|
| 606 |
+
if timestamp is None:
|
| 607 |
+
timestamp = time.time()
|
| 608 |
+
return email.utils.formatdate(timestamp, usegmt=True)
|
| 609 |
+
|
| 610 |
+
def log_date_time_string(self):
|
| 611 |
+
"""Return the current time formatted for logging."""
|
| 612 |
+
now = time.time()
|
| 613 |
+
year, month, day, hh, mm, ss, x, y, z = time.localtime(now)
|
| 614 |
+
s = "%02d/%3s/%04d %02d:%02d:%02d" % (
|
| 615 |
+
day, self.monthname[month], year, hh, mm, ss)
|
| 616 |
+
return s
|
| 617 |
+
|
| 618 |
+
weekdayname = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
|
| 619 |
+
|
| 620 |
+
monthname = [None,
|
| 621 |
+
'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
|
| 622 |
+
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
|
| 623 |
+
|
| 624 |
+
def address_string(self):
|
| 625 |
+
"""Return the client address."""
|
| 626 |
+
|
| 627 |
+
return self.client_address[0]
|
| 628 |
+
|
| 629 |
+
# Essentially static class variables
|
| 630 |
+
|
| 631 |
+
# The version of the HTTP protocol we support.
|
| 632 |
+
# Set this to HTTP/1.1 to enable automatic keepalive
|
| 633 |
+
protocol_version = "HTTP/1.0"
|
| 634 |
+
|
| 635 |
+
# MessageClass used to parse headers
|
| 636 |
+
MessageClass = http.client.HTTPMessage
|
| 637 |
+
|
| 638 |
+
# hack to maintain backwards compatibility
|
| 639 |
+
responses = {
|
| 640 |
+
v: (v.phrase, v.description)
|
| 641 |
+
for v in HTTPStatus.__members__.values()
|
| 642 |
+
}
|
| 643 |
+
|
| 644 |
+
|
| 645 |
+
class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
|
| 646 |
+
|
| 647 |
+
"""Simple HTTP request handler with GET and HEAD commands.
|
| 648 |
+
|
| 649 |
+
This serves files from the current directory and any of its
|
| 650 |
+
subdirectories. The MIME type for files is determined by
|
| 651 |
+
calling the .guess_type() method.
|
| 652 |
+
|
| 653 |
+
The GET and HEAD requests are identical except that the HEAD
|
| 654 |
+
request omits the actual contents of the file.
|
| 655 |
+
|
| 656 |
+
"""
|
| 657 |
+
|
| 658 |
+
server_version = "SimpleHTTP/" + __version__
|
| 659 |
+
|
| 660 |
+
def __init__(self, *args, directory=None, **kwargs):
|
| 661 |
+
if directory is None:
|
| 662 |
+
directory = os.getcwd()
|
| 663 |
+
self.directory = directory
|
| 664 |
+
super().__init__(*args, **kwargs)
|
| 665 |
+
|
| 666 |
+
def do_GET(self):
|
| 667 |
+
"""Serve a GET request."""
|
| 668 |
+
f = self.send_head()
|
| 669 |
+
if f:
|
| 670 |
+
try:
|
| 671 |
+
self.copyfile(f, self.wfile)
|
| 672 |
+
finally:
|
| 673 |
+
f.close()
|
| 674 |
+
|
| 675 |
+
def do_HEAD(self):
|
| 676 |
+
"""Serve a HEAD request."""
|
| 677 |
+
f = self.send_head()
|
| 678 |
+
if f:
|
| 679 |
+
f.close()
|
| 680 |
+
|
| 681 |
+
def send_head(self):
|
| 682 |
+
"""Common code for GET and HEAD commands.
|
| 683 |
+
|
| 684 |
+
This sends the response code and MIME headers.
|
| 685 |
+
|
| 686 |
+
Return value is either a file object (which has to be copied
|
| 687 |
+
to the outputfile by the caller unless the command was HEAD,
|
| 688 |
+
and must be closed by the caller under all circumstances), or
|
| 689 |
+
None, in which case the caller has nothing further to do.
|
| 690 |
+
|
| 691 |
+
"""
|
| 692 |
+
path = self.translate_path(self.path)
|
| 693 |
+
f = None
|
| 694 |
+
if os.path.isdir(path):
|
| 695 |
+
parts = urllib.parse.urlsplit(self.path)
|
| 696 |
+
if not parts.path.endswith('/'):
|
| 697 |
+
# redirect browser - doing basically what apache does
|
| 698 |
+
self.send_response(HTTPStatus.MOVED_PERMANENTLY)
|
| 699 |
+
new_parts = (parts[0], parts[1], parts[2] + '/',
|
| 700 |
+
parts[3], parts[4])
|
| 701 |
+
new_url = urllib.parse.urlunsplit(new_parts)
|
| 702 |
+
self.send_header("Location", new_url)
|
| 703 |
+
self.end_headers()
|
| 704 |
+
return None
|
| 705 |
+
for index in "index.html", "index.htm":
|
| 706 |
+
index = os.path.join(path, index)
|
| 707 |
+
if os.path.exists(index):
|
| 708 |
+
path = index
|
| 709 |
+
break
|
| 710 |
+
else:
|
| 711 |
+
return self.list_directory(path)
|
| 712 |
+
ctype = self.guess_type(path)
|
| 713 |
+
# check for trailing "/" which should return 404. See Issue17324
|
| 714 |
+
# The test for this was added in test_httpserver.py
|
| 715 |
+
# However, some OS platforms accept a trailingSlash as a filename
|
| 716 |
+
# See discussion on python-dev and Issue34711 regarding
|
| 717 |
+
# parseing and rejection of filenames with a trailing slash
|
| 718 |
+
if path.endswith("/"):
|
| 719 |
+
self.send_error(HTTPStatus.NOT_FOUND, "File not found")
|
| 720 |
+
return None
|
| 721 |
+
try:
|
| 722 |
+
f = open(path, 'rb')
|
| 723 |
+
except OSError:
|
| 724 |
+
self.send_error(HTTPStatus.NOT_FOUND, "File not found")
|
| 725 |
+
return None
|
| 726 |
+
|
| 727 |
+
try:
|
| 728 |
+
fs = os.fstat(f.fileno())
|
| 729 |
+
# Use browser cache if possible
|
| 730 |
+
if ("If-Modified-Since" in self.headers
|
| 731 |
+
and "If-None-Match" not in self.headers):
|
| 732 |
+
# compare If-Modified-Since and time of last file modification
|
| 733 |
+
try:
|
| 734 |
+
ims = email.utils.parsedate_to_datetime(
|
| 735 |
+
self.headers["If-Modified-Since"])
|
| 736 |
+
except (TypeError, IndexError, OverflowError, ValueError):
|
| 737 |
+
# ignore ill-formed values
|
| 738 |
+
pass
|
| 739 |
+
else:
|
| 740 |
+
if ims.tzinfo is None:
|
| 741 |
+
# obsolete format with no timezone, cf.
|
| 742 |
+
# https://tools.ietf.org/html/rfc7231#section-7.1.1.1
|
| 743 |
+
ims = ims.replace(tzinfo=datetime.timezone.utc)
|
| 744 |
+
if ims.tzinfo is datetime.timezone.utc:
|
| 745 |
+
# compare to UTC datetime of last modification
|
| 746 |
+
last_modif = datetime.datetime.fromtimestamp(
|
| 747 |
+
fs.st_mtime, datetime.timezone.utc)
|
| 748 |
+
# remove microseconds, like in If-Modified-Since
|
| 749 |
+
last_modif = last_modif.replace(microsecond=0)
|
| 750 |
+
|
| 751 |
+
if last_modif <= ims:
|
| 752 |
+
self.send_response(HTTPStatus.NOT_MODIFIED)
|
| 753 |
+
self.end_headers()
|
| 754 |
+
f.close()
|
| 755 |
+
return None
|
| 756 |
+
|
| 757 |
+
self.send_response(HTTPStatus.OK)
|
| 758 |
+
self.send_header("Content-type", ctype)
|
| 759 |
+
self.send_header("Content-Length", str(fs[6]))
|
| 760 |
+
self.send_header("Last-Modified",
|
| 761 |
+
self.date_time_string(fs.st_mtime))
|
| 762 |
+
self.end_headers()
|
| 763 |
+
return f
|
| 764 |
+
except:
|
| 765 |
+
f.close()
|
| 766 |
+
raise
|
| 767 |
+
|
| 768 |
+
def list_directory(self, path):
|
| 769 |
+
"""Helper to produce a directory listing (absent index.html).
|
| 770 |
+
|
| 771 |
+
Return value is either a file object, or None (indicating an
|
| 772 |
+
error). In either case, the headers are sent, making the
|
| 773 |
+
interface the same as for send_head().
|
| 774 |
+
|
| 775 |
+
"""
|
| 776 |
+
try:
|
| 777 |
+
list = os.listdir(path)
|
| 778 |
+
except OSError:
|
| 779 |
+
self.send_error(
|
| 780 |
+
HTTPStatus.NOT_FOUND,
|
| 781 |
+
"No permission to list directory")
|
| 782 |
+
return None
|
| 783 |
+
list.sort(key=lambda a: a.lower())
|
| 784 |
+
r = []
|
| 785 |
+
try:
|
| 786 |
+
displaypath = urllib.parse.unquote(self.path,
|
| 787 |
+
errors='surrogatepass')
|
| 788 |
+
except UnicodeDecodeError:
|
| 789 |
+
displaypath = urllib.parse.unquote(self.path)
|
| 790 |
+
displaypath = html.escape(displaypath, quote=False)
|
| 791 |
+
enc = sys.getfilesystemencoding()
|
| 792 |
+
title = 'Directory listing for %s' % displaypath
|
| 793 |
+
r.append('<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" '
|
| 794 |
+
'"http://www.w3.org/TR/html4/strict.dtd">')
|
| 795 |
+
r.append('<html>\n<head>')
|
| 796 |
+
r.append('<meta http-equiv="Content-Type" '
|
| 797 |
+
'content="text/html; charset=%s">' % enc)
|
| 798 |
+
r.append('<title>%s</title>\n</head>' % title)
|
| 799 |
+
r.append('<body>\n<h1>%s</h1>' % title)
|
| 800 |
+
r.append('<hr>\n<ul>')
|
| 801 |
+
for name in list:
|
| 802 |
+
fullname = os.path.join(path, name)
|
| 803 |
+
displayname = linkname = name
|
| 804 |
+
# Append / for directories or @ for symbolic links
|
| 805 |
+
if os.path.isdir(fullname):
|
| 806 |
+
displayname = name + "/"
|
| 807 |
+
linkname = name + "/"
|
| 808 |
+
if os.path.islink(fullname):
|
| 809 |
+
displayname = name + "@"
|
| 810 |
+
# Note: a link to a directory displays with @ and links with /
|
| 811 |
+
r.append('<li><a href="%s">%s</a></li>'
|
| 812 |
+
% (urllib.parse.quote(linkname,
|
| 813 |
+
errors='surrogatepass'),
|
| 814 |
+
html.escape(displayname, quote=False)))
|
| 815 |
+
r.append('</ul>\n<hr>\n</body>\n</html>\n')
|
| 816 |
+
encoded = '\n'.join(r).encode(enc, 'surrogateescape')
|
| 817 |
+
f = io.BytesIO()
|
| 818 |
+
f.write(encoded)
|
| 819 |
+
f.seek(0)
|
| 820 |
+
self.send_response(HTTPStatus.OK)
|
| 821 |
+
self.send_header("Content-type", "text/html; charset=%s" % enc)
|
| 822 |
+
self.send_header("Content-Length", str(len(encoded)))
|
| 823 |
+
self.end_headers()
|
| 824 |
+
return f
|
| 825 |
+
|
| 826 |
+
def translate_path(self, path):
|
| 827 |
+
"""Translate a /-separated PATH to the local filename syntax.
|
| 828 |
+
|
| 829 |
+
Components that mean special things to the local file system
|
| 830 |
+
(e.g. drive or directory names) are ignored. (XXX They should
|
| 831 |
+
probably be diagnosed.)
|
| 832 |
+
|
| 833 |
+
"""
|
| 834 |
+
# abandon query parameters
|
| 835 |
+
path = path.split('?',1)[0]
|
| 836 |
+
path = path.split('#',1)[0]
|
| 837 |
+
# Don't forget explicit trailing slash when normalizing. Issue17324
|
| 838 |
+
trailing_slash = path.rstrip().endswith('/')
|
| 839 |
+
try:
|
| 840 |
+
path = urllib.parse.unquote(path, errors='surrogatepass')
|
| 841 |
+
except UnicodeDecodeError:
|
| 842 |
+
path = urllib.parse.unquote(path)
|
| 843 |
+
path = posixpath.normpath(path)
|
| 844 |
+
words = path.split('/')
|
| 845 |
+
words = filter(None, words)
|
| 846 |
+
path = self.directory
|
| 847 |
+
for word in words:
|
| 848 |
+
if os.path.dirname(word) or word in (os.curdir, os.pardir):
|
| 849 |
+
# Ignore components that are not a simple file/directory name
|
| 850 |
+
continue
|
| 851 |
+
path = os.path.join(path, word)
|
| 852 |
+
if trailing_slash:
|
| 853 |
+
path += '/'
|
| 854 |
+
return path
|
| 855 |
+
|
| 856 |
+
def copyfile(self, source, outputfile):
|
| 857 |
+
"""Copy all data between two file objects.
|
| 858 |
+
|
| 859 |
+
The SOURCE argument is a file object open for reading
|
| 860 |
+
(or anything with a read() method) and the DESTINATION
|
| 861 |
+
argument is a file object open for writing (or
|
| 862 |
+
anything with a write() method).
|
| 863 |
+
|
| 864 |
+
The only reason for overriding this would be to change
|
| 865 |
+
the block size or perhaps to replace newlines by CRLF
|
| 866 |
+
-- note however that this the default server uses this
|
| 867 |
+
to copy binary data as well.
|
| 868 |
+
|
| 869 |
+
"""
|
| 870 |
+
shutil.copyfileobj(source, outputfile)
|
| 871 |
+
|
| 872 |
+
def guess_type(self, path):
|
| 873 |
+
"""Guess the type of a file.
|
| 874 |
+
|
| 875 |
+
Argument is a PATH (a filename).
|
| 876 |
+
|
| 877 |
+
Return value is a string of the form type/subtype,
|
| 878 |
+
usable for a MIME Content-type header.
|
| 879 |
+
|
| 880 |
+
The default implementation looks the file's extension
|
| 881 |
+
up in the table self.extensions_map, using application/octet-stream
|
| 882 |
+
as a default; however it would be permissible (if
|
| 883 |
+
slow) to look inside the data to make a better guess.
|
| 884 |
+
|
| 885 |
+
"""
|
| 886 |
+
|
| 887 |
+
base, ext = posixpath.splitext(path)
|
| 888 |
+
if ext in self.extensions_map:
|
| 889 |
+
return self.extensions_map[ext]
|
| 890 |
+
ext = ext.lower()
|
| 891 |
+
if ext in self.extensions_map:
|
| 892 |
+
return self.extensions_map[ext]
|
| 893 |
+
else:
|
| 894 |
+
return self.extensions_map['']
|
| 895 |
+
|
| 896 |
+
if not mimetypes.inited:
|
| 897 |
+
mimetypes.init() # try to read system mime.types
|
| 898 |
+
extensions_map = mimetypes.types_map.copy()
|
| 899 |
+
extensions_map.update({
|
| 900 |
+
'': 'application/octet-stream', # Default
|
| 901 |
+
'.py': 'text/plain',
|
| 902 |
+
'.c': 'text/plain',
|
| 903 |
+
'.h': 'text/plain',
|
| 904 |
+
})
|
| 905 |
+
|
| 906 |
+
|
| 907 |
+
# Utilities for CGIHTTPRequestHandler
|
| 908 |
+
|
| 909 |
+
def _url_collapse_path(path):
|
| 910 |
+
"""
|
| 911 |
+
Given a URL path, remove extra '/'s and '.' path elements and collapse
|
| 912 |
+
any '..' references and returns a collapsed path.
|
| 913 |
+
|
| 914 |
+
Implements something akin to RFC-2396 5.2 step 6 to parse relative paths.
|
| 915 |
+
The utility of this function is limited to is_cgi method and helps
|
| 916 |
+
preventing some security attacks.
|
| 917 |
+
|
| 918 |
+
Returns: The reconstituted URL, which will always start with a '/'.
|
| 919 |
+
|
| 920 |
+
Raises: IndexError if too many '..' occur within the path.
|
| 921 |
+
|
| 922 |
+
"""
|
| 923 |
+
# Query component should not be involved.
|
| 924 |
+
path, _, query = path.partition('?')
|
| 925 |
+
path = urllib.parse.unquote(path)
|
| 926 |
+
|
| 927 |
+
# Similar to os.path.split(os.path.normpath(path)) but specific to URL
|
| 928 |
+
# path semantics rather than local operating system semantics.
|
| 929 |
+
path_parts = path.split('/')
|
| 930 |
+
head_parts = []
|
| 931 |
+
for part in path_parts[:-1]:
|
| 932 |
+
if part == '..':
|
| 933 |
+
head_parts.pop() # IndexError if more '..' than prior parts
|
| 934 |
+
elif part and part != '.':
|
| 935 |
+
head_parts.append( part )
|
| 936 |
+
if path_parts:
|
| 937 |
+
tail_part = path_parts.pop()
|
| 938 |
+
if tail_part:
|
| 939 |
+
if tail_part == '..':
|
| 940 |
+
head_parts.pop()
|
| 941 |
+
tail_part = ''
|
| 942 |
+
elif tail_part == '.':
|
| 943 |
+
tail_part = ''
|
| 944 |
+
else:
|
| 945 |
+
tail_part = ''
|
| 946 |
+
|
| 947 |
+
if query:
|
| 948 |
+
tail_part = '?'.join((tail_part, query))
|
| 949 |
+
|
| 950 |
+
splitpath = ('/' + '/'.join(head_parts), tail_part)
|
| 951 |
+
collapsed_path = "/".join(splitpath)
|
| 952 |
+
|
| 953 |
+
return collapsed_path
|
| 954 |
+
|
| 955 |
+
|
| 956 |
+
|
| 957 |
+
nobody = None
|
| 958 |
+
|
| 959 |
+
def nobody_uid():
|
| 960 |
+
"""Internal routine to get nobody's uid"""
|
| 961 |
+
global nobody
|
| 962 |
+
if nobody:
|
| 963 |
+
return nobody
|
| 964 |
+
try:
|
| 965 |
+
import pwd
|
| 966 |
+
except ImportError:
|
| 967 |
+
return -1
|
| 968 |
+
try:
|
| 969 |
+
nobody = pwd.getpwnam('nobody')[2]
|
| 970 |
+
except KeyError:
|
| 971 |
+
nobody = 1 + max(x[2] for x in pwd.getpwall())
|
| 972 |
+
return nobody
|
| 973 |
+
|
| 974 |
+
|
| 975 |
+
def executable(path):
|
| 976 |
+
"""Test for executable file."""
|
| 977 |
+
return os.access(path, os.X_OK)
|
| 978 |
+
|
| 979 |
+
|
| 980 |
+
class CGIHTTPRequestHandler(SimpleHTTPRequestHandler):
|
| 981 |
+
|
| 982 |
+
"""Complete HTTP server with GET, HEAD and POST commands.
|
| 983 |
+
|
| 984 |
+
GET and HEAD also support running CGI scripts.
|
| 985 |
+
|
| 986 |
+
The POST command is *only* implemented for CGI scripts.
|
| 987 |
+
|
| 988 |
+
"""
|
| 989 |
+
|
| 990 |
+
# Determine platform specifics
|
| 991 |
+
have_fork = hasattr(os, 'fork')
|
| 992 |
+
|
| 993 |
+
# Make rfile unbuffered -- we need to read one line and then pass
|
| 994 |
+
# the rest to a subprocess, so we can't use buffered input.
|
| 995 |
+
rbufsize = 0
|
| 996 |
+
|
| 997 |
+
def do_POST(self):
|
| 998 |
+
"""Serve a POST request.
|
| 999 |
+
|
| 1000 |
+
This is only implemented for CGI scripts.
|
| 1001 |
+
|
| 1002 |
+
"""
|
| 1003 |
+
|
| 1004 |
+
if self.is_cgi():
|
| 1005 |
+
self.run_cgi()
|
| 1006 |
+
else:
|
| 1007 |
+
self.send_error(
|
| 1008 |
+
HTTPStatus.NOT_IMPLEMENTED,
|
| 1009 |
+
"Can only POST to CGI scripts")
|
| 1010 |
+
|
| 1011 |
+
def send_head(self):
|
| 1012 |
+
"""Version of send_head that support CGI scripts"""
|
| 1013 |
+
if self.is_cgi():
|
| 1014 |
+
return self.run_cgi()
|
| 1015 |
+
else:
|
| 1016 |
+
return SimpleHTTPRequestHandler.send_head(self)
|
| 1017 |
+
|
| 1018 |
+
def is_cgi(self):
|
| 1019 |
+
"""Test whether self.path corresponds to a CGI script.
|
| 1020 |
+
|
| 1021 |
+
Returns True and updates the cgi_info attribute to the tuple
|
| 1022 |
+
(dir, rest) if self.path requires running a CGI script.
|
| 1023 |
+
Returns False otherwise.
|
| 1024 |
+
|
| 1025 |
+
If any exception is raised, the caller should assume that
|
| 1026 |
+
self.path was rejected as invalid and act accordingly.
|
| 1027 |
+
|
| 1028 |
+
The default implementation tests whether the normalized url
|
| 1029 |
+
path begins with one of the strings in self.cgi_directories
|
| 1030 |
+
(and the next character is a '/' or the end of the string).
|
| 1031 |
+
|
| 1032 |
+
"""
|
| 1033 |
+
collapsed_path = _url_collapse_path(self.path)
|
| 1034 |
+
dir_sep = collapsed_path.find('/', 1)
|
| 1035 |
+
head, tail = collapsed_path[:dir_sep], collapsed_path[dir_sep+1:]
|
| 1036 |
+
if head in self.cgi_directories:
|
| 1037 |
+
self.cgi_info = head, tail
|
| 1038 |
+
return True
|
| 1039 |
+
return False
|
| 1040 |
+
|
| 1041 |
+
|
| 1042 |
+
cgi_directories = ['/cgi-bin', '/htbin']
|
| 1043 |
+
|
| 1044 |
+
def is_executable(self, path):
|
| 1045 |
+
"""Test whether argument path is an executable file."""
|
| 1046 |
+
return executable(path)
|
| 1047 |
+
|
| 1048 |
+
def is_python(self, path):
|
| 1049 |
+
"""Test whether argument path is a Python script."""
|
| 1050 |
+
head, tail = os.path.splitext(path)
|
| 1051 |
+
return tail.lower() in (".py", ".pyw")
|
| 1052 |
+
|
| 1053 |
+
def run_cgi(self):
|
| 1054 |
+
"""Execute a CGI script."""
|
| 1055 |
+
dir, rest = self.cgi_info
|
| 1056 |
+
path = dir + '/' + rest
|
| 1057 |
+
i = path.find('/', len(dir)+1)
|
| 1058 |
+
while i >= 0:
|
| 1059 |
+
nextdir = path[:i]
|
| 1060 |
+
nextrest = path[i+1:]
|
| 1061 |
+
|
| 1062 |
+
scriptdir = self.translate_path(nextdir)
|
| 1063 |
+
if os.path.isdir(scriptdir):
|
| 1064 |
+
dir, rest = nextdir, nextrest
|
| 1065 |
+
i = path.find('/', len(dir)+1)
|
| 1066 |
+
else:
|
| 1067 |
+
break
|
| 1068 |
+
|
| 1069 |
+
# find an explicit query string, if present.
|
| 1070 |
+
rest, _, query = rest.partition('?')
|
| 1071 |
+
|
| 1072 |
+
# dissect the part after the directory name into a script name &
|
| 1073 |
+
# a possible additional path, to be stored in PATH_INFO.
|
| 1074 |
+
i = rest.find('/')
|
| 1075 |
+
if i >= 0:
|
| 1076 |
+
script, rest = rest[:i], rest[i:]
|
| 1077 |
+
else:
|
| 1078 |
+
script, rest = rest, ''
|
| 1079 |
+
|
| 1080 |
+
scriptname = dir + '/' + script
|
| 1081 |
+
scriptfile = self.translate_path(scriptname)
|
| 1082 |
+
if not os.path.exists(scriptfile):
|
| 1083 |
+
self.send_error(
|
| 1084 |
+
HTTPStatus.NOT_FOUND,
|
| 1085 |
+
"No such CGI script (%r)" % scriptname)
|
| 1086 |
+
return
|
| 1087 |
+
if not os.path.isfile(scriptfile):
|
| 1088 |
+
self.send_error(
|
| 1089 |
+
HTTPStatus.FORBIDDEN,
|
| 1090 |
+
"CGI script is not a plain file (%r)" % scriptname)
|
| 1091 |
+
return
|
| 1092 |
+
ispy = self.is_python(scriptname)
|
| 1093 |
+
if self.have_fork or not ispy:
|
| 1094 |
+
if not self.is_executable(scriptfile):
|
| 1095 |
+
self.send_error(
|
| 1096 |
+
HTTPStatus.FORBIDDEN,
|
| 1097 |
+
"CGI script is not executable (%r)" % scriptname)
|
| 1098 |
+
return
|
| 1099 |
+
|
| 1100 |
+
# Reference: http://hoohoo.ncsa.uiuc.edu/cgi/env.html
|
| 1101 |
+
# XXX Much of the following could be prepared ahead of time!
|
| 1102 |
+
env = copy.deepcopy(os.environ)
|
| 1103 |
+
env['SERVER_SOFTWARE'] = self.version_string()
|
| 1104 |
+
env['SERVER_NAME'] = self.server.server_name
|
| 1105 |
+
env['GATEWAY_INTERFACE'] = 'CGI/1.1'
|
| 1106 |
+
env['SERVER_PROTOCOL'] = self.protocol_version
|
| 1107 |
+
env['SERVER_PORT'] = str(self.server.server_port)
|
| 1108 |
+
env['REQUEST_METHOD'] = self.command
|
| 1109 |
+
uqrest = urllib.parse.unquote(rest)
|
| 1110 |
+
env['PATH_INFO'] = uqrest
|
| 1111 |
+
env['PATH_TRANSLATED'] = self.translate_path(uqrest)
|
| 1112 |
+
env['SCRIPT_NAME'] = scriptname
|
| 1113 |
+
if query:
|
| 1114 |
+
env['QUERY_STRING'] = query
|
| 1115 |
+
env['REMOTE_ADDR'] = self.client_address[0]
|
| 1116 |
+
authorization = self.headers.get("authorization")
|
| 1117 |
+
if authorization:
|
| 1118 |
+
authorization = authorization.split()
|
| 1119 |
+
if len(authorization) == 2:
|
| 1120 |
+
import base64, binascii
|
| 1121 |
+
env['AUTH_TYPE'] = authorization[0]
|
| 1122 |
+
if authorization[0].lower() == "basic":
|
| 1123 |
+
try:
|
| 1124 |
+
authorization = authorization[1].encode('ascii')
|
| 1125 |
+
authorization = base64.decodebytes(authorization).\
|
| 1126 |
+
decode('ascii')
|
| 1127 |
+
except (binascii.Error, UnicodeError):
|
| 1128 |
+
pass
|
| 1129 |
+
else:
|
| 1130 |
+
authorization = authorization.split(':')
|
| 1131 |
+
if len(authorization) == 2:
|
| 1132 |
+
env['REMOTE_USER'] = authorization[0]
|
| 1133 |
+
# XXX REMOTE_IDENT
|
| 1134 |
+
if self.headers.get('content-type') is None:
|
| 1135 |
+
env['CONTENT_TYPE'] = self.headers.get_content_type()
|
| 1136 |
+
else:
|
| 1137 |
+
env['CONTENT_TYPE'] = self.headers['content-type']
|
| 1138 |
+
length = self.headers.get('content-length')
|
| 1139 |
+
if length:
|
| 1140 |
+
env['CONTENT_LENGTH'] = length
|
| 1141 |
+
referer = self.headers.get('referer')
|
| 1142 |
+
if referer:
|
| 1143 |
+
env['HTTP_REFERER'] = referer
|
| 1144 |
+
accept = []
|
| 1145 |
+
for line in self.headers.getallmatchingheaders('accept'):
|
| 1146 |
+
if line[:1] in "\t\n\r ":
|
| 1147 |
+
accept.append(line.strip())
|
| 1148 |
+
else:
|
| 1149 |
+
accept = accept + line[7:].split(',')
|
| 1150 |
+
env['HTTP_ACCEPT'] = ','.join(accept)
|
| 1151 |
+
ua = self.headers.get('user-agent')
|
| 1152 |
+
if ua:
|
| 1153 |
+
env['HTTP_USER_AGENT'] = ua
|
| 1154 |
+
co = filter(None, self.headers.get_all('cookie', []))
|
| 1155 |
+
cookie_str = ', '.join(co)
|
| 1156 |
+
if cookie_str:
|
| 1157 |
+
env['HTTP_COOKIE'] = cookie_str
|
| 1158 |
+
# XXX Other HTTP_* headers
|
| 1159 |
+
# Since we're setting the env in the parent, provide empty
|
| 1160 |
+
# values to override previously set values
|
| 1161 |
+
for k in ('QUERY_STRING', 'REMOTE_HOST', 'CONTENT_LENGTH',
|
| 1162 |
+
'HTTP_USER_AGENT', 'HTTP_COOKIE', 'HTTP_REFERER'):
|
| 1163 |
+
env.setdefault(k, "")
|
| 1164 |
+
|
| 1165 |
+
self.send_response(HTTPStatus.OK, "Script output follows")
|
| 1166 |
+
self.flush_headers()
|
| 1167 |
+
|
| 1168 |
+
decoded_query = query.replace('+', ' ')
|
| 1169 |
+
|
| 1170 |
+
if self.have_fork:
|
| 1171 |
+
# Unix -- fork as we should
|
| 1172 |
+
args = [script]
|
| 1173 |
+
if '=' not in decoded_query:
|
| 1174 |
+
args.append(decoded_query)
|
| 1175 |
+
nobody = nobody_uid()
|
| 1176 |
+
self.wfile.flush() # Always flush before forking
|
| 1177 |
+
pid = os.fork()
|
| 1178 |
+
if pid != 0:
|
| 1179 |
+
# Parent
|
| 1180 |
+
pid, sts = os.waitpid(pid, 0)
|
| 1181 |
+
# throw away additional data [see bug #427345]
|
| 1182 |
+
while select.select([self.rfile], [], [], 0)[0]:
|
| 1183 |
+
if not self.rfile.read(1):
|
| 1184 |
+
break
|
| 1185 |
+
if sts:
|
| 1186 |
+
self.log_error("CGI script exit status %#x", sts)
|
| 1187 |
+
return
|
| 1188 |
+
# Child
|
| 1189 |
+
try:
|
| 1190 |
+
try:
|
| 1191 |
+
os.setuid(nobody)
|
| 1192 |
+
except OSError:
|
| 1193 |
+
pass
|
| 1194 |
+
os.dup2(self.rfile.fileno(), 0)
|
| 1195 |
+
os.dup2(self.wfile.fileno(), 1)
|
| 1196 |
+
os.execve(scriptfile, args, env)
|
| 1197 |
+
except:
|
| 1198 |
+
self.server.handle_error(self.request, self.client_address)
|
| 1199 |
+
os._exit(127)
|
| 1200 |
+
|
| 1201 |
+
else:
|
| 1202 |
+
# Non-Unix -- use subprocess
|
| 1203 |
+
import subprocess
|
| 1204 |
+
cmdline = [scriptfile]
|
| 1205 |
+
if self.is_python(scriptfile):
|
| 1206 |
+
interp = sys.executable
|
| 1207 |
+
if interp.lower().endswith("w.exe"):
|
| 1208 |
+
# On Windows, use python.exe, not pythonw.exe
|
| 1209 |
+
interp = interp[:-5] + interp[-4:]
|
| 1210 |
+
cmdline = [interp, '-u'] + cmdline
|
| 1211 |
+
if '=' not in query:
|
| 1212 |
+
cmdline.append(query)
|
| 1213 |
+
self.log_message("command: %s", subprocess.list2cmdline(cmdline))
|
| 1214 |
+
try:
|
| 1215 |
+
nbytes = int(length)
|
| 1216 |
+
except (TypeError, ValueError):
|
| 1217 |
+
nbytes = 0
|
| 1218 |
+
p = subprocess.Popen(cmdline,
|
| 1219 |
+
stdin=subprocess.PIPE,
|
| 1220 |
+
stdout=subprocess.PIPE,
|
| 1221 |
+
stderr=subprocess.PIPE,
|
| 1222 |
+
env = env
|
| 1223 |
+
)
|
| 1224 |
+
if self.command.lower() == "post" and nbytes > 0:
|
| 1225 |
+
data = self.rfile.read(nbytes)
|
| 1226 |
+
else:
|
| 1227 |
+
data = None
|
| 1228 |
+
# throw away additional data [see bug #427345]
|
| 1229 |
+
while select.select([self.rfile._sock], [], [], 0)[0]:
|
| 1230 |
+
if not self.rfile._sock.recv(1):
|
| 1231 |
+
break
|
| 1232 |
+
stdout, stderr = p.communicate(data)
|
| 1233 |
+
self.wfile.write(stdout)
|
| 1234 |
+
if stderr:
|
| 1235 |
+
self.log_error('%s', stderr)
|
| 1236 |
+
p.stderr.close()
|
| 1237 |
+
p.stdout.close()
|
| 1238 |
+
status = p.returncode
|
| 1239 |
+
if status:
|
| 1240 |
+
self.log_error("CGI script exit status %#x", status)
|
| 1241 |
+
else:
|
| 1242 |
+
self.log_message("CGI script exited OK")
|
| 1243 |
+
|
| 1244 |
+
|
| 1245 |
+
def _get_best_family(*address):
|
| 1246 |
+
infos = socket.getaddrinfo(
|
| 1247 |
+
*address,
|
| 1248 |
+
type=socket.SOCK_STREAM,
|
| 1249 |
+
flags=socket.AI_PASSIVE,
|
| 1250 |
+
)
|
| 1251 |
+
family, type, proto, canonname, sockaddr = next(iter(infos))
|
| 1252 |
+
return family, sockaddr
|
| 1253 |
+
|
| 1254 |
+
|
| 1255 |
+
def test(HandlerClass=BaseHTTPRequestHandler,
|
| 1256 |
+
ServerClass=ThreadingHTTPServer,
|
| 1257 |
+
protocol="HTTP/1.0", port=8000, bind=None):
|
| 1258 |
+
"""Test the HTTP request handler class.
|
| 1259 |
+
|
| 1260 |
+
This runs an HTTP server on port 8000 (or the port argument).
|
| 1261 |
+
|
| 1262 |
+
"""
|
| 1263 |
+
ServerClass.address_family, addr = _get_best_family(bind, port)
|
| 1264 |
+
|
| 1265 |
+
HandlerClass.protocol_version = protocol
|
| 1266 |
+
with ServerClass(addr, HandlerClass) as httpd:
|
| 1267 |
+
host, port = httpd.socket.getsockname()[:2]
|
| 1268 |
+
url_host = f'[{host}]' if ':' in host else host
|
| 1269 |
+
print(
|
| 1270 |
+
f"Serving HTTP on {host} port {port} "
|
| 1271 |
+
f"(http://{url_host}:{port}/) ..."
|
| 1272 |
+
)
|
| 1273 |
+
try:
|
| 1274 |
+
httpd.serve_forever()
|
| 1275 |
+
except KeyboardInterrupt:
|
| 1276 |
+
print("\nKeyboard interrupt received, exiting.")
|
| 1277 |
+
sys.exit(0)
|
| 1278 |
+
|
| 1279 |
+
if __name__ == '__main__':
|
| 1280 |
+
import argparse
|
| 1281 |
+
|
| 1282 |
+
parser = argparse.ArgumentParser()
|
| 1283 |
+
parser.add_argument('--cgi', action='store_true',
|
| 1284 |
+
help='Run as CGI Server')
|
| 1285 |
+
parser.add_argument('--bind', '-b', metavar='ADDRESS',
|
| 1286 |
+
help='Specify alternate bind address '
|
| 1287 |
+
'[default: all interfaces]')
|
| 1288 |
+
parser.add_argument('--directory', '-d', default=os.getcwd(),
|
| 1289 |
+
help='Specify alternative directory '
|
| 1290 |
+
'[default:current directory]')
|
| 1291 |
+
parser.add_argument('port', action='store',
|
| 1292 |
+
default=8000, type=int,
|
| 1293 |
+
nargs='?',
|
| 1294 |
+
help='Specify alternate port [default: 8000]')
|
| 1295 |
+
args = parser.parse_args()
|
| 1296 |
+
if args.cgi:
|
| 1297 |
+
handler_class = CGIHTTPRequestHandler
|
| 1298 |
+
else:
|
| 1299 |
+
handler_class = partial(SimpleHTTPRequestHandler,
|
| 1300 |
+
directory=args.directory)
|
| 1301 |
+
|
| 1302 |
+
# ensure dual-stack is not disabled; ref #38907
|
| 1303 |
+
class DualStackServer(ThreadingHTTPServer):
|
| 1304 |
+
def server_bind(self):
|
| 1305 |
+
# suppress exception when protocol is IPv4
|
| 1306 |
+
with contextlib.suppress(Exception):
|
| 1307 |
+
self.socket.setsockopt(
|
| 1308 |
+
socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
|
| 1309 |
+
return super().server_bind()
|
| 1310 |
+
|
| 1311 |
+
test(
|
| 1312 |
+
HandlerClass=handler_class,
|
| 1313 |
+
ServerClass=DualStackServer,
|
| 1314 |
+
port=args.port,
|
| 1315 |
+
bind=args.bind,
|
| 1316 |
+
)
|
my_container_sandbox/workspace/anaconda3/lib/tcl8/8.4/platform-1.0.18.tm
ADDED
|
@@ -0,0 +1,439 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- tcl -*-
|
| 2 |
+
# ### ### ### ######### ######### #########
|
| 3 |
+
## Overview
|
| 4 |
+
|
| 5 |
+
# Heuristics to assemble a platform identifier from publicly available
|
| 6 |
+
# information. The identifier describes the platform of the currently
|
| 7 |
+
# running tcl shell. This is a mixture of the runtime environment and
|
| 8 |
+
# of build-time properties of the executable itself.
|
| 9 |
+
#
|
| 10 |
+
# Examples:
|
| 11 |
+
# <1> A tcl shell executing on a x86_64 processor, but having a
|
| 12 |
+
# wordsize of 4 was compiled for the x86 environment, i.e. 32
|
| 13 |
+
# bit, and loaded packages have to match that, and not the
|
| 14 |
+
# actual cpu.
|
| 15 |
+
#
|
| 16 |
+
# <2> The hp/solaris 32/64 bit builds of the core cannot be
|
| 17 |
+
# distinguished by looking at tcl_platform. As packages have to
|
| 18 |
+
# match the 32/64 information we have to look in more places. In
|
| 19 |
+
# this case we inspect the executable itself (magic numbers,
|
| 20 |
+
# i.e. fileutil::magic::filetype).
|
| 21 |
+
#
|
| 22 |
+
# The basic information used comes out of the 'os' and 'machine'
|
| 23 |
+
# entries of the 'tcl_platform' array. A number of general and
|
| 24 |
+
# os/machine specific transformation are applied to get a canonical
|
| 25 |
+
# result.
|
| 26 |
+
#
|
| 27 |
+
# General
|
| 28 |
+
# Only the first element of 'os' is used - we don't care whether we
|
| 29 |
+
# are on "Windows NT" or "Windows XP" or whatever.
|
| 30 |
+
#
|
| 31 |
+
# Machine specific
|
| 32 |
+
# % amd64 -> x86_64
|
| 33 |
+
# % arm* -> arm
|
| 34 |
+
# % sun4* -> sparc
|
| 35 |
+
# % ia32* -> ix86
|
| 36 |
+
# % intel -> ix86
|
| 37 |
+
# % i*86* -> ix86
|
| 38 |
+
# % Power* -> powerpc
|
| 39 |
+
# % x86_64 + wordSize 4 => x86 code
|
| 40 |
+
#
|
| 41 |
+
# OS specific
|
| 42 |
+
# % AIX are always powerpc machines
|
| 43 |
+
# % HP-UX 9000/800 etc means parisc
|
| 44 |
+
# % linux has to take glibc version into account
|
| 45 |
+
# % sunos -> solaris, and keep version number
|
| 46 |
+
#
|
| 47 |
+
# NOTE: A platform like linux glibc 2.3, which can use glibc 2.2 stuff
|
| 48 |
+
# has to provide all possible allowed platform identifiers when
|
| 49 |
+
# searching search. Ditto a solaris 2.8 platform can use solaris 2.6
|
| 50 |
+
# packages. Etc. This is handled by the other procedure, see below.
|
| 51 |
+
|
| 52 |
+
# ### ### ### ######### ######### #########
|
| 53 |
+
## Requirements
|
| 54 |
+
|
| 55 |
+
namespace eval ::platform {}
|
| 56 |
+
|
| 57 |
+
# ### ### ### ######### ######### #########
|
| 58 |
+
## Implementation
|
| 59 |
+
|
| 60 |
+
# -- platform::generic
|
| 61 |
+
#
|
| 62 |
+
# Assembles an identifier for the generic platform. It leaves out
|
| 63 |
+
# details like kernel version, libc version, etc.
|
| 64 |
+
|
| 65 |
+
proc ::platform::generic {} {
|
| 66 |
+
global tcl_platform
|
| 67 |
+
|
| 68 |
+
set plat [string tolower [lindex $tcl_platform(os) 0]]
|
| 69 |
+
set cpu $tcl_platform(machine)
|
| 70 |
+
|
| 71 |
+
switch -glob -- $cpu {
|
| 72 |
+
sun4* {
|
| 73 |
+
set cpu sparc
|
| 74 |
+
}
|
| 75 |
+
intel -
|
| 76 |
+
ia32* -
|
| 77 |
+
i*86* {
|
| 78 |
+
set cpu ix86
|
| 79 |
+
}
|
| 80 |
+
x86_64 {
|
| 81 |
+
if {$tcl_platform(wordSize) == 4} {
|
| 82 |
+
# See Example <1> at the top of this file.
|
| 83 |
+
set cpu ix86
|
| 84 |
+
}
|
| 85 |
+
}
|
| 86 |
+
ppc -
|
| 87 |
+
"Power*" {
|
| 88 |
+
set cpu powerpc
|
| 89 |
+
}
|
| 90 |
+
"arm*" {
|
| 91 |
+
set cpu arm
|
| 92 |
+
}
|
| 93 |
+
ia64 {
|
| 94 |
+
if {$tcl_platform(wordSize) == 4} {
|
| 95 |
+
append cpu _32
|
| 96 |
+
}
|
| 97 |
+
}
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
switch -glob -- $plat {
|
| 101 |
+
windows {
|
| 102 |
+
if {$tcl_platform(platform) == "unix"} {
|
| 103 |
+
set plat cygwin
|
| 104 |
+
} else {
|
| 105 |
+
set plat win32
|
| 106 |
+
}
|
| 107 |
+
if {$cpu eq "amd64"} {
|
| 108 |
+
# Do not check wordSize, win32-x64 is an IL32P64 platform.
|
| 109 |
+
set cpu x86_64
|
| 110 |
+
}
|
| 111 |
+
}
|
| 112 |
+
sunos {
|
| 113 |
+
set plat solaris
|
| 114 |
+
if {[string match "ix86" $cpu]} {
|
| 115 |
+
if {$tcl_platform(wordSize) == 8} {
|
| 116 |
+
set cpu x86_64
|
| 117 |
+
}
|
| 118 |
+
} elseif {![string match "ia64*" $cpu]} {
|
| 119 |
+
# sparc
|
| 120 |
+
if {$tcl_platform(wordSize) == 8} {
|
| 121 |
+
append cpu 64
|
| 122 |
+
}
|
| 123 |
+
}
|
| 124 |
+
}
|
| 125 |
+
darwin {
|
| 126 |
+
set plat macosx
|
| 127 |
+
# Correctly identify the cpu when running as a 64bit
|
| 128 |
+
# process on a machine with a 32bit kernel
|
| 129 |
+
if {$cpu eq "ix86"} {
|
| 130 |
+
if {$tcl_platform(wordSize) == 8} {
|
| 131 |
+
set cpu x86_64
|
| 132 |
+
}
|
| 133 |
+
}
|
| 134 |
+
}
|
| 135 |
+
aix {
|
| 136 |
+
set cpu powerpc
|
| 137 |
+
if {$tcl_platform(wordSize) == 8} {
|
| 138 |
+
append cpu 64
|
| 139 |
+
}
|
| 140 |
+
}
|
| 141 |
+
hp-ux {
|
| 142 |
+
set plat hpux
|
| 143 |
+
if {![string match "ia64*" $cpu]} {
|
| 144 |
+
set cpu parisc
|
| 145 |
+
if {$tcl_platform(wordSize) == 8} {
|
| 146 |
+
append cpu 64
|
| 147 |
+
}
|
| 148 |
+
}
|
| 149 |
+
}
|
| 150 |
+
osf1 {
|
| 151 |
+
set plat tru64
|
| 152 |
+
}
|
| 153 |
+
default {
|
| 154 |
+
set plat [lindex [split $plat _-] 0]
|
| 155 |
+
}
|
| 156 |
+
}
|
| 157 |
+
|
| 158 |
+
return "${plat}-${cpu}"
|
| 159 |
+
}
|
| 160 |
+
|
| 161 |
+
# -- platform::identify
|
| 162 |
+
#
|
| 163 |
+
# Assembles an identifier for the exact platform, by extending the
|
| 164 |
+
# generic identifier. I.e. it adds in details like kernel version,
|
| 165 |
+
# libc version, etc., if they are relevant for the loading of
|
| 166 |
+
# packages on the platform.
|
| 167 |
+
|
| 168 |
+
proc ::platform::identify {} {
|
| 169 |
+
global tcl_platform
|
| 170 |
+
|
| 171 |
+
set id [generic]
|
| 172 |
+
regexp {^([^-]+)-([^-]+)$} $id -> plat cpu
|
| 173 |
+
|
| 174 |
+
switch -- $plat {
|
| 175 |
+
solaris {
|
| 176 |
+
regsub {^5} $tcl_platform(osVersion) 2 text
|
| 177 |
+
append plat $text
|
| 178 |
+
return "${plat}-${cpu}"
|
| 179 |
+
}
|
| 180 |
+
macosx {
|
| 181 |
+
set major [lindex [split $tcl_platform(osVersion) .] 0]
|
| 182 |
+
if {$major > 19} {
|
| 183 |
+
set minor [lindex [split $tcl_platform(osVersion) .] 1]
|
| 184 |
+
incr major -9
|
| 185 |
+
append plat $major.[expr {$minor - 1}]
|
| 186 |
+
} else {
|
| 187 |
+
incr major -4
|
| 188 |
+
append plat 10.$major
|
| 189 |
+
return "${plat}-${cpu}"
|
| 190 |
+
}
|
| 191 |
+
return "${plat}-${cpu}"
|
| 192 |
+
}
|
| 193 |
+
linux {
|
| 194 |
+
# Look for the libc*.so and determine its version
|
| 195 |
+
# (libc5/6, libc6 further glibc 2.X)
|
| 196 |
+
|
| 197 |
+
set v unknown
|
| 198 |
+
|
| 199 |
+
# Determine in which directory to look. /lib, or /lib64.
|
| 200 |
+
# For that we use the tcl_platform(wordSize).
|
| 201 |
+
#
|
| 202 |
+
# We could use the 'cpu' info, per the equivalence below,
|
| 203 |
+
# that however would be restricted to intel. And this may
|
| 204 |
+
# be a arm, mips, etc. system. The wordsize is more
|
| 205 |
+
# fundamental.
|
| 206 |
+
#
|
| 207 |
+
# ix86 <=> (wordSize == 4) <=> 32 bit ==> /lib
|
| 208 |
+
# x86_64 <=> (wordSize == 8) <=> 64 bit ==> /lib64
|
| 209 |
+
#
|
| 210 |
+
# Do not look into /lib64 even if present, if the cpu
|
| 211 |
+
# doesn't fit.
|
| 212 |
+
|
| 213 |
+
# TODO: Determine the prefixes (i386, x86_64, ...) for
|
| 214 |
+
# other cpus. The path after the generic one is utterly
|
| 215 |
+
# specific to intel right now. Ok, on Ubuntu, possibly
|
| 216 |
+
# other Debian systems we may apparently be able to query
|
| 217 |
+
# the necessary CPU code. If we can't we simply use the
|
| 218 |
+
# hardwired fallback.
|
| 219 |
+
|
| 220 |
+
switch -exact -- $tcl_platform(wordSize) {
|
| 221 |
+
4 {
|
| 222 |
+
lappend bases /lib
|
| 223 |
+
if {[catch {
|
| 224 |
+
exec dpkg-architecture -qDEB_HOST_MULTIARCH
|
| 225 |
+
} res]} {
|
| 226 |
+
lappend bases /lib/i386-linux-gnu
|
| 227 |
+
} else {
|
| 228 |
+
# dpkg-arch returns the full tripled, not just cpu.
|
| 229 |
+
lappend bases /lib/$res
|
| 230 |
+
}
|
| 231 |
+
}
|
| 232 |
+
8 {
|
| 233 |
+
lappend bases /lib64
|
| 234 |
+
if {[catch {
|
| 235 |
+
exec dpkg-architecture -qDEB_HOST_MULTIARCH
|
| 236 |
+
} res]} {
|
| 237 |
+
lappend bases /lib/x86_64-linux-gnu
|
| 238 |
+
} else {
|
| 239 |
+
# dpkg-arch returns the full tripled, not just cpu.
|
| 240 |
+
lappend bases /lib/$res
|
| 241 |
+
}
|
| 242 |
+
}
|
| 243 |
+
default {
|
| 244 |
+
return -code error "Bad wordSize $tcl_platform(wordSize), expected 4 or 8"
|
| 245 |
+
}
|
| 246 |
+
}
|
| 247 |
+
|
| 248 |
+
foreach base $bases {
|
| 249 |
+
if {[LibcVersion $base -> v]} break
|
| 250 |
+
}
|
| 251 |
+
|
| 252 |
+
append plat -$v
|
| 253 |
+
return "${plat}-${cpu}"
|
| 254 |
+
}
|
| 255 |
+
}
|
| 256 |
+
|
| 257 |
+
return $id
|
| 258 |
+
}
|
| 259 |
+
|
| 260 |
+
proc ::platform::LibcVersion {base _->_ vv} {
|
| 261 |
+
upvar 1 $vv v
|
| 262 |
+
set libclist [lsort [glob -nocomplain -directory $base libc*]]
|
| 263 |
+
|
| 264 |
+
if {![llength $libclist]} { return 0 }
|
| 265 |
+
|
| 266 |
+
set libc [lindex $libclist 0]
|
| 267 |
+
|
| 268 |
+
# Try executing the library first. This should suceed
|
| 269 |
+
# for a glibc library, and return the version
|
| 270 |
+
# information.
|
| 271 |
+
|
| 272 |
+
if {![catch {
|
| 273 |
+
set vdata [lindex [split [exec $libc] \n] 0]
|
| 274 |
+
}]} {
|
| 275 |
+
regexp {version ([0-9]+(\.[0-9]+)*)} $vdata -> v
|
| 276 |
+
foreach {major minor} [split $v .] break
|
| 277 |
+
set v glibc${major}.${minor}
|
| 278 |
+
return 1
|
| 279 |
+
} else {
|
| 280 |
+
# We had trouble executing the library. We are now
|
| 281 |
+
# inspecting its name to determine the version
|
| 282 |
+
# number. This code by Larry McVoy.
|
| 283 |
+
|
| 284 |
+
if {[regexp -- {libc-([0-9]+)\.([0-9]+)} $libc -> major minor]} {
|
| 285 |
+
set v glibc${major}.${minor}
|
| 286 |
+
return 1
|
| 287 |
+
}
|
| 288 |
+
}
|
| 289 |
+
return 0
|
| 290 |
+
}
|
| 291 |
+
|
| 292 |
+
# -- platform::patterns
|
| 293 |
+
#
|
| 294 |
+
# Given an exact platform identifier, i.e. _not_ the generic
|
| 295 |
+
# identifier it assembles a list of exact platform identifier
|
| 296 |
+
# describing platform which should be compatible with the
|
| 297 |
+
# input.
|
| 298 |
+
#
|
| 299 |
+
# I.e. packages for all platforms in the result list should be
|
| 300 |
+
# loadable on the specified platform.
|
| 301 |
+
|
| 302 |
+
# << Should we add the generic identifier to the list as well ? In
|
| 303 |
+
# general it is not compatible I believe. So better not. In many
|
| 304 |
+
# cases the exact identifier is identical to the generic one
|
| 305 |
+
# anyway.
|
| 306 |
+
# >>
|
| 307 |
+
|
| 308 |
+
proc ::platform::patterns {id} {
|
| 309 |
+
set res [list $id]
|
| 310 |
+
if {$id eq "tcl"} {return $res}
|
| 311 |
+
|
| 312 |
+
switch -glob -- $id {
|
| 313 |
+
solaris*-* {
|
| 314 |
+
if {[regexp {solaris([^-]*)-(.*)} $id -> v cpu]} {
|
| 315 |
+
if {$v eq ""} {return $id}
|
| 316 |
+
foreach {major minor} [split $v .] break
|
| 317 |
+
incr minor -1
|
| 318 |
+
for {set j $minor} {$j >= 6} {incr j -1} {
|
| 319 |
+
lappend res solaris${major}.${j}-${cpu}
|
| 320 |
+
}
|
| 321 |
+
}
|
| 322 |
+
}
|
| 323 |
+
linux*-* {
|
| 324 |
+
if {[regexp {linux-glibc([^-]*)-(.*)} $id -> v cpu]} {
|
| 325 |
+
foreach {major minor} [split $v .] break
|
| 326 |
+
incr minor -1
|
| 327 |
+
for {set j $minor} {$j >= 0} {incr j -1} {
|
| 328 |
+
lappend res linux-glibc${major}.${j}-${cpu}
|
| 329 |
+
}
|
| 330 |
+
}
|
| 331 |
+
}
|
| 332 |
+
macosx-powerpc {
|
| 333 |
+
lappend res macosx-universal
|
| 334 |
+
}
|
| 335 |
+
macosx-x86_64 {
|
| 336 |
+
lappend res macosx-i386-x86_64
|
| 337 |
+
}
|
| 338 |
+
macosx-ix86 {
|
| 339 |
+
lappend res macosx-universal macosx-i386-x86_64
|
| 340 |
+
}
|
| 341 |
+
macosx*-* {
|
| 342 |
+
# 10.5+,11.0+
|
| 343 |
+
if {[regexp {macosx([^-]*)-(.*)} $id -> v cpu]} {
|
| 344 |
+
|
| 345 |
+
switch -exact -- $cpu {
|
| 346 |
+
ix86 {
|
| 347 |
+
lappend alt i386-x86_64
|
| 348 |
+
lappend alt universal
|
| 349 |
+
}
|
| 350 |
+
x86_64 {
|
| 351 |
+
if {[lindex [split $::tcl_platform(osVersion) .] 0] < 19} {
|
| 352 |
+
set alt i386-x86_64
|
| 353 |
+
} else {
|
| 354 |
+
set alt {}
|
| 355 |
+
}
|
| 356 |
+
}
|
| 357 |
+
arm {
|
| 358 |
+
lappend alt x86_64
|
| 359 |
+
}
|
| 360 |
+
default { set alt {} }
|
| 361 |
+
}
|
| 362 |
+
|
| 363 |
+
if {$v ne ""} {
|
| 364 |
+
foreach {major minor} [split $v .] break
|
| 365 |
+
|
| 366 |
+
set res {}
|
| 367 |
+
if {$major eq 12} {
|
| 368 |
+
# Add 12.0 to 12.minor to patterns.
|
| 369 |
+
for {set j $minor} {$j >= 0} {incr j -1} {
|
| 370 |
+
lappend res macosx${major}.${j}-${cpu}
|
| 371 |
+
foreach a $alt {
|
| 372 |
+
lappend res macosx${major}.${j}-$a
|
| 373 |
+
}
|
| 374 |
+
}
|
| 375 |
+
set major 11
|
| 376 |
+
set minor 5
|
| 377 |
+
}
|
| 378 |
+
if {$major eq 11} {
|
| 379 |
+
# Add 11.0 to 11.minor to patterns.
|
| 380 |
+
for {set j $minor} {$j >= 0} {incr j -1} {
|
| 381 |
+
lappend res macosx${major}.${j}-${cpu}
|
| 382 |
+
foreach a $alt {
|
| 383 |
+
lappend res macosx${major}.${j}-$a
|
| 384 |
+
}
|
| 385 |
+
}
|
| 386 |
+
set major 10
|
| 387 |
+
set minor 15
|
| 388 |
+
}
|
| 389 |
+
# Add 10.5 to 10.minor to patterns.
|
| 390 |
+
for {set j $minor} {$j >= 5} {incr j -1} {
|
| 391 |
+
if {$cpu ne "arm"} {
|
| 392 |
+
lappend res macosx${major}.${j}-${cpu}
|
| 393 |
+
}
|
| 394 |
+
foreach a $alt {
|
| 395 |
+
lappend res macosx${major}.${j}-$a
|
| 396 |
+
}
|
| 397 |
+
}
|
| 398 |
+
|
| 399 |
+
# Add unversioned patterns for 10.3/10.4 builds.
|
| 400 |
+
lappend res macosx-${cpu}
|
| 401 |
+
foreach a $alt {
|
| 402 |
+
lappend res macosx-$a
|
| 403 |
+
}
|
| 404 |
+
} else {
|
| 405 |
+
# No version, just do unversioned patterns.
|
| 406 |
+
foreach a $alt {
|
| 407 |
+
lappend res macosx-$a
|
| 408 |
+
}
|
| 409 |
+
}
|
| 410 |
+
} else {
|
| 411 |
+
# no v, no cpu ... nothing
|
| 412 |
+
}
|
| 413 |
+
}
|
| 414 |
+
}
|
| 415 |
+
lappend res tcl ; # Pure tcl packages are always compatible.
|
| 416 |
+
return $res
|
| 417 |
+
}
|
| 418 |
+
|
| 419 |
+
|
| 420 |
+
# ### ### ### ######### ######### #########
|
| 421 |
+
## Ready
|
| 422 |
+
|
| 423 |
+
package provide platform 1.0.18
|
| 424 |
+
|
| 425 |
+
# ### ### ### ######### ######### #########
|
| 426 |
+
## Demo application
|
| 427 |
+
|
| 428 |
+
if {[info exists argv0] && ($argv0 eq [info script])} {
|
| 429 |
+
puts ====================================
|
| 430 |
+
parray tcl_platform
|
| 431 |
+
puts ====================================
|
| 432 |
+
puts Generic\ identification:\ [::platform::generic]
|
| 433 |
+
puts Exact\ identification:\ \ \ [::platform::identify]
|
| 434 |
+
puts ====================================
|
| 435 |
+
puts Search\ patterns:
|
| 436 |
+
puts *\ [join [::platform::patterns [::platform::identify]] \n*\ ]
|
| 437 |
+
puts ====================================
|
| 438 |
+
exit 0
|
| 439 |
+
}
|
my_container_sandbox/workspace/anaconda3/lib/tcl8/8.4/platform/shell-1.1.4.tm
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
# -*- tcl -*-
|
| 3 |
+
# ### ### ### ######### ######### #########
|
| 4 |
+
## Overview
|
| 5 |
+
|
| 6 |
+
# Higher-level commands which invoke the functionality of this package
|
| 7 |
+
# for an arbitrary tcl shell (tclsh, wish, ...). This is required by a
|
| 8 |
+
# repository as while the tcl shell executing packages uses the same
|
| 9 |
+
# platform in general as a repository application there can be
|
| 10 |
+
# differences in detail (i.e. 32/64 bit builds).
|
| 11 |
+
|
| 12 |
+
# ### ### ### ######### ######### #########
|
| 13 |
+
## Requirements
|
| 14 |
+
|
| 15 |
+
package require platform
|
| 16 |
+
namespace eval ::platform::shell {}
|
| 17 |
+
|
| 18 |
+
# ### ### ### ######### ######### #########
|
| 19 |
+
## Implementation
|
| 20 |
+
|
| 21 |
+
# -- platform::shell::generic
|
| 22 |
+
|
| 23 |
+
proc ::platform::shell::generic {shell} {
|
| 24 |
+
# Argument is the path to a tcl shell.
|
| 25 |
+
|
| 26 |
+
CHECK $shell
|
| 27 |
+
LOCATE base out
|
| 28 |
+
|
| 29 |
+
set code {}
|
| 30 |
+
# Forget any pre-existing platform package, it might be in
|
| 31 |
+
# conflict with this one.
|
| 32 |
+
lappend code {package forget platform}
|
| 33 |
+
# Inject our platform package
|
| 34 |
+
lappend code [list source $base]
|
| 35 |
+
# Query and print the architecture
|
| 36 |
+
lappend code {puts [platform::generic]}
|
| 37 |
+
# And done
|
| 38 |
+
lappend code {exit 0}
|
| 39 |
+
|
| 40 |
+
set arch [RUN $shell [join $code \n]]
|
| 41 |
+
|
| 42 |
+
if {$out} {file delete -force $base}
|
| 43 |
+
return $arch
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
# -- platform::shell::identify
|
| 47 |
+
|
| 48 |
+
proc ::platform::shell::identify {shell} {
|
| 49 |
+
# Argument is the path to a tcl shell.
|
| 50 |
+
|
| 51 |
+
CHECK $shell
|
| 52 |
+
LOCATE base out
|
| 53 |
+
|
| 54 |
+
set code {}
|
| 55 |
+
# Forget any pre-existing platform package, it might be in
|
| 56 |
+
# conflict with this one.
|
| 57 |
+
lappend code {package forget platform}
|
| 58 |
+
# Inject our platform package
|
| 59 |
+
lappend code [list source $base]
|
| 60 |
+
# Query and print the architecture
|
| 61 |
+
lappend code {puts [platform::identify]}
|
| 62 |
+
# And done
|
| 63 |
+
lappend code {exit 0}
|
| 64 |
+
|
| 65 |
+
set arch [RUN $shell [join $code \n]]
|
| 66 |
+
|
| 67 |
+
if {$out} {file delete -force $base}
|
| 68 |
+
return $arch
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
# -- platform::shell::platform
|
| 72 |
+
|
| 73 |
+
proc ::platform::shell::platform {shell} {
|
| 74 |
+
# Argument is the path to a tcl shell.
|
| 75 |
+
|
| 76 |
+
CHECK $shell
|
| 77 |
+
|
| 78 |
+
set code {}
|
| 79 |
+
lappend code {puts $tcl_platform(platform)}
|
| 80 |
+
lappend code {exit 0}
|
| 81 |
+
|
| 82 |
+
return [RUN $shell [join $code \n]]
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
# ### ### ### ######### ######### #########
|
| 86 |
+
## Internal helper commands.
|
| 87 |
+
|
| 88 |
+
proc ::platform::shell::CHECK {shell} {
|
| 89 |
+
if {![file exists $shell]} {
|
| 90 |
+
return -code error "Shell \"$shell\" does not exist"
|
| 91 |
+
}
|
| 92 |
+
if {![file executable $shell]} {
|
| 93 |
+
return -code error "Shell \"$shell\" is not executable (permissions)"
|
| 94 |
+
}
|
| 95 |
+
return
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
proc ::platform::shell::LOCATE {bv ov} {
|
| 99 |
+
upvar 1 $bv base $ov out
|
| 100 |
+
|
| 101 |
+
# Locate the platform package for injection into the specified
|
| 102 |
+
# shell. We are using package management to find it, whereever it
|
| 103 |
+
# is, instead of using hardwired relative paths. This allows us to
|
| 104 |
+
# install the two packages as TMs without breaking the code
|
| 105 |
+
# here. If the found package is wrapped we copy the code somewhere
|
| 106 |
+
# where the spawned shell will be able to read it.
|
| 107 |
+
|
| 108 |
+
# This code is brittle, it needs has to adapt to whatever changes
|
| 109 |
+
# are made to the TM code, i.e. the provide statement generated by
|
| 110 |
+
# tm.tcl
|
| 111 |
+
|
| 112 |
+
set pl [package ifneeded platform [package require platform]]
|
| 113 |
+
set base [lindex $pl end]
|
| 114 |
+
|
| 115 |
+
set out 0
|
| 116 |
+
if {[lindex [file system $base]] ne "native"} {
|
| 117 |
+
set temp [TEMP]
|
| 118 |
+
file copy -force $base $temp
|
| 119 |
+
set base $temp
|
| 120 |
+
set out 1
|
| 121 |
+
}
|
| 122 |
+
return
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
proc ::platform::shell::RUN {shell code} {
|
| 126 |
+
set c [TEMP]
|
| 127 |
+
set cc [open $c w]
|
| 128 |
+
puts $cc $code
|
| 129 |
+
close $cc
|
| 130 |
+
|
| 131 |
+
set e [TEMP]
|
| 132 |
+
|
| 133 |
+
set code [catch {
|
| 134 |
+
exec $shell $c 2> $e
|
| 135 |
+
} res]
|
| 136 |
+
|
| 137 |
+
file delete $c
|
| 138 |
+
|
| 139 |
+
if {$code} {
|
| 140 |
+
append res \n[read [set chan [open $e r]]][close $chan]
|
| 141 |
+
file delete $e
|
| 142 |
+
return -code error "Shell \"$shell\" is not executable ($res)"
|
| 143 |
+
}
|
| 144 |
+
|
| 145 |
+
file delete $e
|
| 146 |
+
return $res
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
proc ::platform::shell::TEMP {} {
|
| 150 |
+
set prefix platform
|
| 151 |
+
|
| 152 |
+
# This code is copied out of Tcllib's fileutil package.
|
| 153 |
+
# (TempFile/tempfile)
|
| 154 |
+
|
| 155 |
+
set tmpdir [DIR]
|
| 156 |
+
|
| 157 |
+
set chars "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
| 158 |
+
set nrand_chars 10
|
| 159 |
+
set maxtries 10
|
| 160 |
+
set access [list RDWR CREAT EXCL TRUNC]
|
| 161 |
+
set permission 0600
|
| 162 |
+
set channel ""
|
| 163 |
+
set checked_dir_writable 0
|
| 164 |
+
set mypid [pid]
|
| 165 |
+
for {set i 0} {$i < $maxtries} {incr i} {
|
| 166 |
+
set newname $prefix
|
| 167 |
+
for {set j 0} {$j < $nrand_chars} {incr j} {
|
| 168 |
+
append newname [string index $chars \
|
| 169 |
+
[expr {int(rand()*62)}]]
|
| 170 |
+
}
|
| 171 |
+
set newname [file join $tmpdir $newname]
|
| 172 |
+
if {[file exists $newname]} {
|
| 173 |
+
after 1
|
| 174 |
+
} else {
|
| 175 |
+
if {[catch {open $newname $access $permission} channel]} {
|
| 176 |
+
if {!$checked_dir_writable} {
|
| 177 |
+
set dirname [file dirname $newname]
|
| 178 |
+
if {![file writable $dirname]} {
|
| 179 |
+
return -code error "Directory $dirname is not writable"
|
| 180 |
+
}
|
| 181 |
+
set checked_dir_writable 1
|
| 182 |
+
}
|
| 183 |
+
} else {
|
| 184 |
+
# Success
|
| 185 |
+
close $channel
|
| 186 |
+
return [file normalize $newname]
|
| 187 |
+
}
|
| 188 |
+
}
|
| 189 |
+
}
|
| 190 |
+
if {$channel ne ""} {
|
| 191 |
+
return -code error "Failed to open a temporary file: $channel"
|
| 192 |
+
} else {
|
| 193 |
+
return -code error "Failed to find an unused temporary file name"
|
| 194 |
+
}
|
| 195 |
+
}
|
| 196 |
+
|
| 197 |
+
proc ::platform::shell::DIR {} {
|
| 198 |
+
# This code is copied out of Tcllib's fileutil package.
|
| 199 |
+
# (TempDir/tempdir)
|
| 200 |
+
|
| 201 |
+
global tcl_platform env
|
| 202 |
+
|
| 203 |
+
set attempdirs [list]
|
| 204 |
+
|
| 205 |
+
foreach tmp {TMPDIR TEMP TMP} {
|
| 206 |
+
if { [info exists env($tmp)] } {
|
| 207 |
+
lappend attempdirs $env($tmp)
|
| 208 |
+
}
|
| 209 |
+
}
|
| 210 |
+
|
| 211 |
+
switch $tcl_platform(platform) {
|
| 212 |
+
windows {
|
| 213 |
+
lappend attempdirs "C:\\TEMP" "C:\\TMP" "\\TEMP" "\\TMP"
|
| 214 |
+
}
|
| 215 |
+
macintosh {
|
| 216 |
+
set tmpdir $env(TRASH_FOLDER) ;# a better place?
|
| 217 |
+
}
|
| 218 |
+
default {
|
| 219 |
+
lappend attempdirs \
|
| 220 |
+
[file join / tmp] \
|
| 221 |
+
[file join / var tmp] \
|
| 222 |
+
[file join / usr tmp]
|
| 223 |
+
}
|
| 224 |
+
}
|
| 225 |
+
|
| 226 |
+
lappend attempdirs [pwd]
|
| 227 |
+
|
| 228 |
+
foreach tmp $attempdirs {
|
| 229 |
+
if { [file isdirectory $tmp] && [file writable $tmp] } {
|
| 230 |
+
return [file normalize $tmp]
|
| 231 |
+
}
|
| 232 |
+
}
|
| 233 |
+
|
| 234 |
+
# Fail if nothing worked.
|
| 235 |
+
return -code error "Unable to determine a proper directory for temporary files"
|
| 236 |
+
}
|
| 237 |
+
|
| 238 |
+
# ### ### ### ######### ######### #########
|
| 239 |
+
## Ready
|
| 240 |
+
|
| 241 |
+
package provide platform::shell 1.1.4
|
my_container_sandbox/workspace/anaconda3/lib/tcl8/8.5/msgcat-1.6.1.tm
ADDED
|
@@ -0,0 +1,1210 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# msgcat.tcl --
|
| 2 |
+
#
|
| 3 |
+
# This file defines various procedures which implement a
|
| 4 |
+
# message catalog facility for Tcl programs. It should be
|
| 5 |
+
# loaded with the command "package require msgcat".
|
| 6 |
+
#
|
| 7 |
+
# Copyright (c) 2010-2015 Harald Oehlmann.
|
| 8 |
+
# Copyright (c) 1998-2000 Ajuba Solutions.
|
| 9 |
+
# Copyright (c) 1998 Mark Harrison.
|
| 10 |
+
#
|
| 11 |
+
# See the file "license.terms" for information on usage and redistribution
|
| 12 |
+
# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
|
| 13 |
+
|
| 14 |
+
package require Tcl 8.5-
|
| 15 |
+
# When the version number changes, be sure to update the pkgIndex.tcl file,
|
| 16 |
+
# and the installation directory in the Makefiles.
|
| 17 |
+
package provide msgcat 1.6.1
|
| 18 |
+
|
| 19 |
+
namespace eval msgcat {
|
| 20 |
+
namespace export mc mcexists mcload mclocale mcmax mcmset mcpreferences mcset\
|
| 21 |
+
mcunknown mcflset mcflmset mcloadedlocales mcforgetpackage\
|
| 22 |
+
mcpackageconfig mcpackagelocale
|
| 23 |
+
|
| 24 |
+
# Records the list of locales to search
|
| 25 |
+
variable Loclist {}
|
| 26 |
+
|
| 27 |
+
# List of currently loaded locales
|
| 28 |
+
variable LoadedLocales {}
|
| 29 |
+
|
| 30 |
+
# Records the locale of the currently sourced message catalogue file
|
| 31 |
+
variable FileLocale
|
| 32 |
+
|
| 33 |
+
# Configuration values per Package (e.g. client namespace).
|
| 34 |
+
# The dict key is of the form "<option> <namespace>" and the value is the
|
| 35 |
+
# configuration option. A nonexisting key is an unset option.
|
| 36 |
+
variable PackageConfig [dict create mcfolder {} loadcmd {} changecmd {}\
|
| 37 |
+
unknowncmd {} loadedlocales {} loclist {}]
|
| 38 |
+
|
| 39 |
+
# Records the mapping between source strings and translated strings. The
|
| 40 |
+
# dict key is of the form "<namespace> <locale> <src>", where locale and
|
| 41 |
+
# namespace should be themselves dict values and the value is
|
| 42 |
+
# the translated string.
|
| 43 |
+
variable Msgs [dict create]
|
| 44 |
+
|
| 45 |
+
# Map of language codes used in Windows registry to those of ISO-639
|
| 46 |
+
if {[info sharedlibextension] eq ".dll"} {
|
| 47 |
+
variable WinRegToISO639 [dict create {*}{
|
| 48 |
+
01 ar 0401 ar_SA 0801 ar_IQ 0c01 ar_EG 1001 ar_LY 1401 ar_DZ
|
| 49 |
+
1801 ar_MA 1c01 ar_TN 2001 ar_OM 2401 ar_YE 2801 ar_SY
|
| 50 |
+
2c01 ar_JO 3001 ar_LB 3401 ar_KW 3801 ar_AE 3c01 ar_BH
|
| 51 |
+
4001 ar_QA
|
| 52 |
+
02 bg 0402 bg_BG
|
| 53 |
+
03 ca 0403 ca_ES
|
| 54 |
+
04 zh 0404 zh_TW 0804 zh_CN 0c04 zh_HK 1004 zh_SG 1404 zh_MO
|
| 55 |
+
05 cs 0405 cs_CZ
|
| 56 |
+
06 da 0406 da_DK
|
| 57 |
+
07 de 0407 de_DE 0807 de_CH 0c07 de_AT 1007 de_LU 1407 de_LI
|
| 58 |
+
08 el 0408 el_GR
|
| 59 |
+
09 en 0409 en_US 0809 en_GB 0c09 en_AU 1009 en_CA 1409 en_NZ
|
| 60 |
+
1809 en_IE 1c09 en_ZA 2009 en_JM 2409 en_GD 2809 en_BZ
|
| 61 |
+
2c09 en_TT 3009 en_ZW 3409 en_PH
|
| 62 |
+
0a es 040a es_ES 080a es_MX 0c0a es_ES@modern 100a es_GT 140a es_CR
|
| 63 |
+
180a es_PA 1c0a es_DO 200a es_VE 240a es_CO 280a es_PE
|
| 64 |
+
2c0a es_AR 300a es_EC 340a es_CL 380a es_UY 3c0a es_PY
|
| 65 |
+
400a es_BO 440a es_SV 480a es_HN 4c0a es_NI 500a es_PR
|
| 66 |
+
0b fi 040b fi_FI
|
| 67 |
+
0c fr 040c fr_FR 080c fr_BE 0c0c fr_CA 100c fr_CH 140c fr_LU
|
| 68 |
+
180c fr_MC
|
| 69 |
+
0d he 040d he_IL
|
| 70 |
+
0e hu 040e hu_HU
|
| 71 |
+
0f is 040f is_IS
|
| 72 |
+
10 it 0410 it_IT 0810 it_CH
|
| 73 |
+
11 ja 0411 ja_JP
|
| 74 |
+
12 ko 0412 ko_KR
|
| 75 |
+
13 nl 0413 nl_NL 0813 nl_BE
|
| 76 |
+
14 no 0414 no_NO 0814 nn_NO
|
| 77 |
+
15 pl 0415 pl_PL
|
| 78 |
+
16 pt 0416 pt_BR 0816 pt_PT
|
| 79 |
+
17 rm 0417 rm_CH
|
| 80 |
+
18 ro 0418 ro_RO 0818 ro_MO
|
| 81 |
+
19 ru 0819 ru_MO
|
| 82 |
+
1a hr 041a hr_HR 081a sr_YU 0c1a sr_YU@cyrillic
|
| 83 |
+
1b sk 041b sk_SK
|
| 84 |
+
1c sq 041c sq_AL
|
| 85 |
+
1d sv 041d sv_SE 081d sv_FI
|
| 86 |
+
1e th 041e th_TH
|
| 87 |
+
1f tr 041f tr_TR
|
| 88 |
+
20 ur 0420 ur_PK 0820 ur_IN
|
| 89 |
+
21 id 0421 id_ID
|
| 90 |
+
22 uk 0422 uk_UA
|
| 91 |
+
23 be 0423 be_BY
|
| 92 |
+
24 sl 0424 sl_SI
|
| 93 |
+
25 et 0425 et_EE
|
| 94 |
+
26 lv 0426 lv_LV
|
| 95 |
+
27 lt 0427 lt_LT
|
| 96 |
+
28 tg 0428 tg_TJ
|
| 97 |
+
29 fa 0429 fa_IR
|
| 98 |
+
2a vi 042a vi_VN
|
| 99 |
+
2b hy 042b hy_AM
|
| 100 |
+
2c az 042c az_AZ@latin 082c az_AZ@cyrillic
|
| 101 |
+
2d eu
|
| 102 |
+
2e wen 042e wen_DE
|
| 103 |
+
2f mk 042f mk_MK
|
| 104 |
+
30 bnt 0430 bnt_TZ
|
| 105 |
+
31 ts 0431 ts_ZA
|
| 106 |
+
32 tn
|
| 107 |
+
33 ven 0433 ven_ZA
|
| 108 |
+
34 xh 0434 xh_ZA
|
| 109 |
+
35 zu 0435 zu_ZA
|
| 110 |
+
36 af 0436 af_ZA
|
| 111 |
+
37 ka 0437 ka_GE
|
| 112 |
+
38 fo 0438 fo_FO
|
| 113 |
+
39 hi 0439 hi_IN
|
| 114 |
+
3a mt 043a mt_MT
|
| 115 |
+
3b se 043b se_NO
|
| 116 |
+
043c gd_UK 083c ga_IE
|
| 117 |
+
3d yi 043d yi_IL
|
| 118 |
+
3e ms 043e ms_MY 083e ms_BN
|
| 119 |
+
3f kk 043f kk_KZ
|
| 120 |
+
40 ky 0440 ky_KG
|
| 121 |
+
41 sw 0441 sw_KE
|
| 122 |
+
42 tk 0442 tk_TM
|
| 123 |
+
43 uz 0443 uz_UZ@latin 0843 uz_UZ@cyrillic
|
| 124 |
+
44 tt 0444 tt_RU
|
| 125 |
+
45 bn 0445 bn_IN
|
| 126 |
+
46 pa 0446 pa_IN
|
| 127 |
+
47 gu 0447 gu_IN
|
| 128 |
+
48 or 0448 or_IN
|
| 129 |
+
49 ta
|
| 130 |
+
4a te 044a te_IN
|
| 131 |
+
4b kn 044b kn_IN
|
| 132 |
+
4c ml 044c ml_IN
|
| 133 |
+
4d as 044d as_IN
|
| 134 |
+
4e mr 044e mr_IN
|
| 135 |
+
4f sa 044f sa_IN
|
| 136 |
+
50 mn
|
| 137 |
+
51 bo 0451 bo_CN
|
| 138 |
+
52 cy 0452 cy_GB
|
| 139 |
+
53 km 0453 km_KH
|
| 140 |
+
54 lo 0454 lo_LA
|
| 141 |
+
55 my 0455 my_MM
|
| 142 |
+
56 gl 0456 gl_ES
|
| 143 |
+
57 kok 0457 kok_IN
|
| 144 |
+
58 mni 0458 mni_IN
|
| 145 |
+
59 sd
|
| 146 |
+
5a syr 045a syr_TR
|
| 147 |
+
5b si 045b si_LK
|
| 148 |
+
5c chr 045c chr_US
|
| 149 |
+
5d iu 045d iu_CA
|
| 150 |
+
5e am 045e am_ET
|
| 151 |
+
5f ber 045f ber_MA
|
| 152 |
+
60 ks 0460 ks_PK 0860 ks_IN
|
| 153 |
+
61 ne 0461 ne_NP 0861 ne_IN
|
| 154 |
+
62 fy 0462 fy_NL
|
| 155 |
+
63 ps
|
| 156 |
+
64 tl 0464 tl_PH
|
| 157 |
+
65 div 0465 div_MV
|
| 158 |
+
66 bin 0466 bin_NG
|
| 159 |
+
67 ful 0467 ful_NG
|
| 160 |
+
68 ha 0468 ha_NG
|
| 161 |
+
69 nic 0469 nic_NG
|
| 162 |
+
6a yo 046a yo_NG
|
| 163 |
+
70 ibo 0470 ibo_NG
|
| 164 |
+
71 kau 0471 kau_NG
|
| 165 |
+
72 om 0472 om_ET
|
| 166 |
+
73 ti 0473 ti_ET
|
| 167 |
+
74 gn 0474 gn_PY
|
| 168 |
+
75 cpe 0475 cpe_US
|
| 169 |
+
76 la 0476 la_VA
|
| 170 |
+
77 so 0477 so_SO
|
| 171 |
+
78 sit 0478 sit_CN
|
| 172 |
+
79 pap 0479 pap_AN
|
| 173 |
+
}]
|
| 174 |
+
}
|
| 175 |
+
}
|
| 176 |
+
|
| 177 |
+
# msgcat::mc --
|
| 178 |
+
#
|
| 179 |
+
# Find the translation for the given string based on the current
|
| 180 |
+
# locale setting. Check the local namespace first, then look in each
|
| 181 |
+
# parent namespace until the source is found. If additional args are
|
| 182 |
+
# specified, use the format command to work them into the traslated
|
| 183 |
+
# string.
|
| 184 |
+
# If no catalog item is found, mcunknown is called in the caller frame
|
| 185 |
+
# and its result is returned.
|
| 186 |
+
#
|
| 187 |
+
# Arguments:
|
| 188 |
+
# src The string to translate.
|
| 189 |
+
# args Args to pass to the format command
|
| 190 |
+
#
|
| 191 |
+
# Results:
|
| 192 |
+
# Returns the translated string. Propagates errors thrown by the
|
| 193 |
+
# format command.
|
| 194 |
+
|
| 195 |
+
proc msgcat::mc {src args} {
|
| 196 |
+
# this may be replaced by:
|
| 197 |
+
# return [mcget -namespace [uplevel 1 [list ::namespace current]] --\
|
| 198 |
+
# $src {*}$args]
|
| 199 |
+
|
| 200 |
+
# Check for the src in each namespace starting from the local and
|
| 201 |
+
# ending in the global.
|
| 202 |
+
|
| 203 |
+
variable Msgs
|
| 204 |
+
variable Loclist
|
| 205 |
+
|
| 206 |
+
set ns [uplevel 1 [list ::namespace current]]
|
| 207 |
+
set loclist [PackagePreferences $ns]
|
| 208 |
+
|
| 209 |
+
set nscur $ns
|
| 210 |
+
while {$nscur != ""} {
|
| 211 |
+
foreach loc $loclist {
|
| 212 |
+
if {[dict exists $Msgs $nscur $loc $src]} {
|
| 213 |
+
return [DefaultUnknown "" [dict get $Msgs $nscur $loc $src]\
|
| 214 |
+
{*}$args]
|
| 215 |
+
}
|
| 216 |
+
}
|
| 217 |
+
set nscur [namespace parent $nscur]
|
| 218 |
+
}
|
| 219 |
+
# call package local or default unknown command
|
| 220 |
+
set args [linsert $args 0 [lindex $loclist 0] $src]
|
| 221 |
+
switch -exact -- [Invoke unknowncmd $args $ns result 1] {
|
| 222 |
+
0 { return [uplevel 1 [linsert $args 0 [namespace origin mcunknown]]] }
|
| 223 |
+
1 { return [DefaultUnknown {*}$args] }
|
| 224 |
+
default { return $result }
|
| 225 |
+
}
|
| 226 |
+
}
|
| 227 |
+
|
| 228 |
+
# msgcat::mcexists --
|
| 229 |
+
#
|
| 230 |
+
# Check if a catalog item is set or if mc would invoke mcunknown.
|
| 231 |
+
#
|
| 232 |
+
# Arguments:
|
| 233 |
+
# -exactnamespace Only check the exact namespace and no
|
| 234 |
+
# parent namespaces
|
| 235 |
+
# -exactlocale Only check the exact locale and not all members
|
| 236 |
+
# of the preferences list
|
| 237 |
+
# src Message catalog key
|
| 238 |
+
#
|
| 239 |
+
# Results:
|
| 240 |
+
# true if an adequate catalog key was found
|
| 241 |
+
|
| 242 |
+
proc msgcat::mcexists {args} {
|
| 243 |
+
|
| 244 |
+
variable Msgs
|
| 245 |
+
variable Loclist
|
| 246 |
+
variable PackageConfig
|
| 247 |
+
|
| 248 |
+
set ns [uplevel 1 [list ::namespace current]]
|
| 249 |
+
set loclist [PackagePreferences $ns]
|
| 250 |
+
|
| 251 |
+
while {[llength $args] != 1} {
|
| 252 |
+
set args [lassign $args option]
|
| 253 |
+
switch -glob -- $option {
|
| 254 |
+
-exactnamespace { set exactnamespace 1 }
|
| 255 |
+
-exactlocale { set loclist [lrange $loclist 0 0] }
|
| 256 |
+
-* { return -code error "unknown option \"$option\"" }
|
| 257 |
+
default {
|
| 258 |
+
return -code error "wrong # args: should be\
|
| 259 |
+
\"[lindex [info level 0] 0] ?-exactnamespace?\
|
| 260 |
+
?-exactlocale? src\""
|
| 261 |
+
}
|
| 262 |
+
}
|
| 263 |
+
}
|
| 264 |
+
set src [lindex $args 0]
|
| 265 |
+
|
| 266 |
+
while {$ns ne ""} {
|
| 267 |
+
foreach loc $loclist {
|
| 268 |
+
if {[dict exists $Msgs $ns $loc $src]} {
|
| 269 |
+
return 1
|
| 270 |
+
}
|
| 271 |
+
}
|
| 272 |
+
if {[info exists exactnamespace]} {return 0}
|
| 273 |
+
set ns [namespace parent $ns]
|
| 274 |
+
}
|
| 275 |
+
return 0
|
| 276 |
+
}
|
| 277 |
+
|
| 278 |
+
# msgcat::mclocale --
|
| 279 |
+
#
|
| 280 |
+
# Query or set the current locale.
|
| 281 |
+
#
|
| 282 |
+
# Arguments:
|
| 283 |
+
# newLocale (Optional) The new locale string. Locale strings
|
| 284 |
+
# should be composed of one or more sublocale parts
|
| 285 |
+
# separated by underscores (e.g. en_US).
|
| 286 |
+
#
|
| 287 |
+
# Results:
|
| 288 |
+
# Returns the normalized set locale.
|
| 289 |
+
|
| 290 |
+
proc msgcat::mclocale {args} {
|
| 291 |
+
variable Loclist
|
| 292 |
+
variable LoadedLocales
|
| 293 |
+
set len [llength $args]
|
| 294 |
+
|
| 295 |
+
if {$len > 1} {
|
| 296 |
+
return -code error "wrong # args: should be\
|
| 297 |
+
\"[lindex [info level 0] 0] ?newLocale?\""
|
| 298 |
+
}
|
| 299 |
+
|
| 300 |
+
if {$len == 1} {
|
| 301 |
+
set newLocale [string tolower [lindex $args 0]]
|
| 302 |
+
if {$newLocale ne [file tail $newLocale]} {
|
| 303 |
+
return -code error "invalid newLocale value \"$newLocale\":\
|
| 304 |
+
could be path to unsafe code."
|
| 305 |
+
}
|
| 306 |
+
if {[lindex $Loclist 0] ne $newLocale} {
|
| 307 |
+
set Loclist [GetPreferences $newLocale]
|
| 308 |
+
|
| 309 |
+
# locale not loaded jet
|
| 310 |
+
LoadAll $Loclist
|
| 311 |
+
# Invoke callback
|
| 312 |
+
Invoke changecmd $Loclist
|
| 313 |
+
}
|
| 314 |
+
}
|
| 315 |
+
return [lindex $Loclist 0]
|
| 316 |
+
}
|
| 317 |
+
|
| 318 |
+
# msgcat::GetPreferences --
|
| 319 |
+
#
|
| 320 |
+
# Get list of locales from a locale.
|
| 321 |
+
# The first element is always the lowercase locale.
|
| 322 |
+
# Other elements have one component separated by "_" less.
|
| 323 |
+
# Multiple "_" are seen as one separator: de__ch_spec de__ch de {}
|
| 324 |
+
#
|
| 325 |
+
# Arguments:
|
| 326 |
+
# Locale.
|
| 327 |
+
#
|
| 328 |
+
# Results:
|
| 329 |
+
# Locale list
|
| 330 |
+
|
| 331 |
+
proc msgcat::GetPreferences {locale} {
|
| 332 |
+
set locale [string tolower $locale]
|
| 333 |
+
set loclist [list $locale]
|
| 334 |
+
while {-1 !=[set pos [string last "_" $locale]]} {
|
| 335 |
+
set locale [string range $locale 0 $pos-1]
|
| 336 |
+
if { "_" ne [string index $locale end] } {
|
| 337 |
+
lappend loclist $locale
|
| 338 |
+
}
|
| 339 |
+
}
|
| 340 |
+
if {"" ne [lindex $loclist end]} {
|
| 341 |
+
lappend loclist {}
|
| 342 |
+
}
|
| 343 |
+
return $loclist
|
| 344 |
+
}
|
| 345 |
+
|
| 346 |
+
# msgcat::mcpreferences --
|
| 347 |
+
#
|
| 348 |
+
# Fetch the list of locales used to look up strings, ordered from
|
| 349 |
+
# most preferred to least preferred.
|
| 350 |
+
#
|
| 351 |
+
# Arguments:
|
| 352 |
+
# None.
|
| 353 |
+
#
|
| 354 |
+
# Results:
|
| 355 |
+
# Returns an ordered list of the locales preferred by the user.
|
| 356 |
+
|
| 357 |
+
proc msgcat::mcpreferences {} {
|
| 358 |
+
variable Loclist
|
| 359 |
+
return $Loclist
|
| 360 |
+
}
|
| 361 |
+
|
| 362 |
+
# msgcat::mcloadedlocales --
|
| 363 |
+
#
|
| 364 |
+
# Get or change the list of currently loaded default locales
|
| 365 |
+
#
|
| 366 |
+
# The following subcommands are available:
|
| 367 |
+
# loaded
|
| 368 |
+
# Get the current list of loaded locales
|
| 369 |
+
# clear
|
| 370 |
+
# Remove all loaded locales not present in mcpreferences.
|
| 371 |
+
#
|
| 372 |
+
# Arguments:
|
| 373 |
+
# subcommand One of loaded or clear
|
| 374 |
+
#
|
| 375 |
+
# Results:
|
| 376 |
+
# Empty string, if not stated differently for the subcommand
|
| 377 |
+
|
| 378 |
+
proc msgcat::mcloadedlocales {subcommand} {
|
| 379 |
+
variable Loclist
|
| 380 |
+
variable LoadedLocales
|
| 381 |
+
variable Msgs
|
| 382 |
+
variable PackageConfig
|
| 383 |
+
switch -exact -- $subcommand {
|
| 384 |
+
clear {
|
| 385 |
+
# Remove all locales not contained in Loclist
|
| 386 |
+
# skip any packages with package locale
|
| 387 |
+
set LoadedLocales $Loclist
|
| 388 |
+
foreach ns [dict keys $Msgs] {
|
| 389 |
+
if {![dict exists $PackageConfig loclist $ns]} {
|
| 390 |
+
foreach locale [dict keys [dict get $Msgs $ns]] {
|
| 391 |
+
if {$locale ni $Loclist} {
|
| 392 |
+
dict unset Msgs $ns $locale
|
| 393 |
+
}
|
| 394 |
+
}
|
| 395 |
+
}
|
| 396 |
+
}
|
| 397 |
+
}
|
| 398 |
+
loaded { return $LoadedLocales }
|
| 399 |
+
default {
|
| 400 |
+
return -code error "unknown subcommand \"$subcommand\": must be\
|
| 401 |
+
clear, or loaded"
|
| 402 |
+
}
|
| 403 |
+
}
|
| 404 |
+
return
|
| 405 |
+
}
|
| 406 |
+
|
| 407 |
+
# msgcat::mcpackagelocale --
|
| 408 |
+
#
|
| 409 |
+
# Get or change the package locale of the calling package.
|
| 410 |
+
#
|
| 411 |
+
# The following subcommands are available:
|
| 412 |
+
# set
|
| 413 |
+
# Set a package locale.
|
| 414 |
+
# This may load message catalog files and may clear message catalog
|
| 415 |
+
# items, if the former locale was the default locale.
|
| 416 |
+
# Returns the normalized set locale.
|
| 417 |
+
# The default locale is taken, if locale is not given.
|
| 418 |
+
# get
|
| 419 |
+
# Get the locale valid for this package.
|
| 420 |
+
# isset
|
| 421 |
+
# Returns true, if a package locale is set
|
| 422 |
+
# unset
|
| 423 |
+
# Unset the package locale and activate the default locale.
|
| 424 |
+
# This loads message catalog file which where missing in the package
|
| 425 |
+
# locale.
|
| 426 |
+
# preferences
|
| 427 |
+
# Return locale preference list valid for the package.
|
| 428 |
+
# loaded
|
| 429 |
+
# Return loaded locale list valid for the current package.
|
| 430 |
+
# clear
|
| 431 |
+
# If the current package has a package locale, remove all package
|
| 432 |
+
# locales not containes in package mcpreferences.
|
| 433 |
+
# It is an error to call this without a package locale set.
|
| 434 |
+
#
|
| 435 |
+
# The subcommands get, preferences and loaded return the corresponding
|
| 436 |
+
# default data, if no package locale is set.
|
| 437 |
+
#
|
| 438 |
+
# Arguments:
|
| 439 |
+
# subcommand see list above
|
| 440 |
+
# locale package locale (only set subcommand)
|
| 441 |
+
#
|
| 442 |
+
# Results:
|
| 443 |
+
# Empty string, if not stated differently for the subcommand
|
| 444 |
+
|
| 445 |
+
proc msgcat::mcpackagelocale {subcommand {locale ""}} {
|
| 446 |
+
# todo: implement using an ensemble
|
| 447 |
+
variable Loclist
|
| 448 |
+
variable LoadedLocales
|
| 449 |
+
variable Msgs
|
| 450 |
+
variable PackageConfig
|
| 451 |
+
# Check option
|
| 452 |
+
# check if required item is exactly provided
|
| 453 |
+
if {[llength [info level 0]] == 2} {
|
| 454 |
+
# locale not given
|
| 455 |
+
unset locale
|
| 456 |
+
} else {
|
| 457 |
+
# locale given
|
| 458 |
+
if {$subcommand in
|
| 459 |
+
{"get" "isset" "unset" "preferences" "loaded" "clear"} } {
|
| 460 |
+
return -code error "wrong # args: should be\
|
| 461 |
+
\"[lrange [info level 0] 0 1]\""
|
| 462 |
+
}
|
| 463 |
+
set locale [string tolower $locale]
|
| 464 |
+
}
|
| 465 |
+
set ns [uplevel 1 {::namespace current}]
|
| 466 |
+
|
| 467 |
+
switch -exact -- $subcommand {
|
| 468 |
+
get { return [lindex [PackagePreferences $ns] 0] }
|
| 469 |
+
preferences { return [PackagePreferences $ns] }
|
| 470 |
+
loaded { return [PackageLocales $ns] }
|
| 471 |
+
present { return [expr {$locale in [PackageLocales $ns]} ]}
|
| 472 |
+
isset { return [dict exists $PackageConfig loclist $ns] }
|
| 473 |
+
set { # set a package locale or add a package locale
|
| 474 |
+
|
| 475 |
+
# Copy the default locale if no package locale set so far
|
| 476 |
+
if {![dict exists $PackageConfig loclist $ns]} {
|
| 477 |
+
dict set PackageConfig loclist $ns $Loclist
|
| 478 |
+
dict set PackageConfig loadedlocales $ns $LoadedLocales
|
| 479 |
+
}
|
| 480 |
+
|
| 481 |
+
# Check if changed
|
| 482 |
+
set loclist [dict get $PackageConfig loclist $ns]
|
| 483 |
+
if {! [info exists locale] || $locale eq [lindex $loclist 0] } {
|
| 484 |
+
return [lindex $loclist 0]
|
| 485 |
+
}
|
| 486 |
+
|
| 487 |
+
# Change loclist
|
| 488 |
+
set loclist [GetPreferences $locale]
|
| 489 |
+
set locale [lindex $loclist 0]
|
| 490 |
+
dict set PackageConfig loclist $ns $loclist
|
| 491 |
+
|
| 492 |
+
# load eventual missing locales
|
| 493 |
+
set loadedLocales [dict get $PackageConfig loadedlocales $ns]
|
| 494 |
+
if {$locale in $loadedLocales} { return $locale }
|
| 495 |
+
set loadLocales [ListComplement $loadedLocales $loclist]
|
| 496 |
+
dict set PackageConfig loadedlocales $ns\
|
| 497 |
+
[concat $loadedLocales $loadLocales]
|
| 498 |
+
Load $ns $loadLocales
|
| 499 |
+
return $locale
|
| 500 |
+
}
|
| 501 |
+
clear { # Remove all locales not contained in Loclist
|
| 502 |
+
if {![dict exists $PackageConfig loclist $ns]} {
|
| 503 |
+
return -code error "clear only when package locale set"
|
| 504 |
+
}
|
| 505 |
+
set loclist [dict get $PackageConfig loclist $ns]
|
| 506 |
+
dict set PackageConfig loadedlocales $ns $loclist
|
| 507 |
+
if {[dict exists $Msgs $ns]} {
|
| 508 |
+
foreach locale [dict keys [dict get $Msgs $ns]] {
|
| 509 |
+
if {$locale ni $loclist} {
|
| 510 |
+
dict unset Msgs $ns $locale
|
| 511 |
+
}
|
| 512 |
+
}
|
| 513 |
+
}
|
| 514 |
+
}
|
| 515 |
+
unset { # unset package locale and restore default locales
|
| 516 |
+
|
| 517 |
+
if { ![dict exists $PackageConfig loclist $ns] } { return }
|
| 518 |
+
|
| 519 |
+
# unset package locale
|
| 520 |
+
set loadLocales [ListComplement\
|
| 521 |
+
[dict get $PackageConfig loadedlocales $ns] $LoadedLocales]
|
| 522 |
+
dict unset PackageConfig loadedlocales $ns
|
| 523 |
+
dict unset PackageConfig loclist $ns
|
| 524 |
+
|
| 525 |
+
# unset keys not in global loaded locales
|
| 526 |
+
if {[dict exists $Msgs $ns]} {
|
| 527 |
+
foreach locale [dict keys [dict get $Msgs $ns]] {
|
| 528 |
+
if {$locale ni $LoadedLocales} {
|
| 529 |
+
dict unset Msgs $ns $locale
|
| 530 |
+
}
|
| 531 |
+
}
|
| 532 |
+
}
|
| 533 |
+
|
| 534 |
+
# Add missing locales
|
| 535 |
+
Load $ns $loadLocales
|
| 536 |
+
}
|
| 537 |
+
default {
|
| 538 |
+
return -code error "unknown subcommand \"$subcommand\": must be\
|
| 539 |
+
clear, get, isset, loaded, present, set, or unset"
|
| 540 |
+
}
|
| 541 |
+
}
|
| 542 |
+
return
|
| 543 |
+
}
|
| 544 |
+
|
| 545 |
+
# msgcat::mcforgetpackage --
|
| 546 |
+
#
|
| 547 |
+
# Remove any data of the calling package from msgcat
|
| 548 |
+
#
|
| 549 |
+
|
| 550 |
+
proc msgcat::mcforgetpackage {} {
|
| 551 |
+
# todo: this may be implemented using an ensemble
|
| 552 |
+
variable PackageConfig
|
| 553 |
+
variable Msgs
|
| 554 |
+
set ns [uplevel 1 {::namespace current}]
|
| 555 |
+
# Remove MC items
|
| 556 |
+
dict unset Msgs $ns
|
| 557 |
+
# Remove config items
|
| 558 |
+
foreach key [dict keys $PackageConfig] {
|
| 559 |
+
dict unset PackageConfig $key $ns
|
| 560 |
+
}
|
| 561 |
+
return
|
| 562 |
+
}
|
| 563 |
+
|
| 564 |
+
# msgcat::mcpackageconfig --
|
| 565 |
+
#
|
| 566 |
+
# Get or modify the per caller namespace (e.g. packages) config options.
|
| 567 |
+
#
|
| 568 |
+
# Available subcommands are:
|
| 569 |
+
#
|
| 570 |
+
# get get the current value or an error if not set.
|
| 571 |
+
# isset return true, if the option is set
|
| 572 |
+
# set set the value (see also distinct option).
|
| 573 |
+
# Returns the number of loaded message files.
|
| 574 |
+
# unset Clear option. return "".
|
| 575 |
+
#
|
| 576 |
+
# Available options are:
|
| 577 |
+
#
|
| 578 |
+
# mcfolder
|
| 579 |
+
# The message catalog folder of the package.
|
| 580 |
+
# This is automatically set by mcload.
|
| 581 |
+
# If the value is changed using the set subcommand, an evntual
|
| 582 |
+
# loadcmd is invoked and all message files of the package locale are
|
| 583 |
+
# loaded.
|
| 584 |
+
#
|
| 585 |
+
# loadcmd
|
| 586 |
+
# The command gets executed before a message file would be
|
| 587 |
+
# sourced for this module.
|
| 588 |
+
# The command is invoked with the expanded locale list to load.
|
| 589 |
+
# The command is not invoked if the registering package namespace
|
| 590 |
+
# is not present.
|
| 591 |
+
# This callback might also be used as an alternative to message
|
| 592 |
+
# files.
|
| 593 |
+
# If the value is changed using the set subcommand, the callback is
|
| 594 |
+
# directly invoked with the current file locale list. No file load is
|
| 595 |
+
# executed.
|
| 596 |
+
#
|
| 597 |
+
# changecmd
|
| 598 |
+
# The command is invoked, after an executed locale change.
|
| 599 |
+
# Appended argument is expanded mcpreferences.
|
| 600 |
+
#
|
| 601 |
+
# unknowncmd
|
| 602 |
+
# Use a package locale mcunknown procedure instead the global one.
|
| 603 |
+
# The appended arguments are identical to mcunknown.
|
| 604 |
+
# A default unknown handler is used if set to the empty string.
|
| 605 |
+
# This consists in returning the key if no arguments are given.
|
| 606 |
+
# With given arguments, format is used to process the arguments.
|
| 607 |
+
#
|
| 608 |
+
# Arguments:
|
| 609 |
+
# subcommand Operation on the package
|
| 610 |
+
# option The package option to get or set.
|
| 611 |
+
# ?value? Eventual value for the subcommand
|
| 612 |
+
#
|
| 613 |
+
# Results:
|
| 614 |
+
# Depends on the subcommand and option and is described there
|
| 615 |
+
|
| 616 |
+
proc msgcat::mcpackageconfig {subcommand option {value ""}} {
|
| 617 |
+
variable PackageConfig
|
| 618 |
+
# get namespace
|
| 619 |
+
set ns [uplevel 1 {::namespace current}]
|
| 620 |
+
|
| 621 |
+
if {$option ni {"mcfolder" "loadcmd" "changecmd" "unknowncmd"}} {
|
| 622 |
+
return -code error "bad option \"$option\": must be mcfolder, loadcmd,\
|
| 623 |
+
changecmd, or unknowncmd"
|
| 624 |
+
}
|
| 625 |
+
|
| 626 |
+
# check if value argument is exactly provided
|
| 627 |
+
if {[llength [info level 0]] == 4 } {
|
| 628 |
+
# value provided
|
| 629 |
+
if {$subcommand in {"get" "isset" "unset"}} {
|
| 630 |
+
return -code error "wrong # args: should be\
|
| 631 |
+
\"[lrange [info level 0] 0 2] value\""
|
| 632 |
+
}
|
| 633 |
+
} elseif {$subcommand eq "set"} {
|
| 634 |
+
return -code error\
|
| 635 |
+
"wrong # args: should be \"[lrange [info level 0] 0 2]\""
|
| 636 |
+
}
|
| 637 |
+
|
| 638 |
+
# Execute subcommands
|
| 639 |
+
switch -exact -- $subcommand {
|
| 640 |
+
get { # Operation get return current value
|
| 641 |
+
if {![dict exists $PackageConfig $option $ns]} {
|
| 642 |
+
return -code error "package option \"$option\" not set"
|
| 643 |
+
}
|
| 644 |
+
return [dict get $PackageConfig $option $ns]
|
| 645 |
+
}
|
| 646 |
+
isset { return [dict exists $PackageConfig $option $ns] }
|
| 647 |
+
unset { dict unset PackageConfig $option $ns }
|
| 648 |
+
set { # Set option
|
| 649 |
+
|
| 650 |
+
if {$option eq "mcfolder"} {
|
| 651 |
+
set value [file normalize $value]
|
| 652 |
+
}
|
| 653 |
+
# Check if changed
|
| 654 |
+
if { [dict exists $PackageConfig $option $ns]
|
| 655 |
+
&& $value eq [dict get $PackageConfig $option $ns] } {
|
| 656 |
+
return 0
|
| 657 |
+
}
|
| 658 |
+
|
| 659 |
+
# set new value
|
| 660 |
+
dict set PackageConfig $option $ns $value
|
| 661 |
+
|
| 662 |
+
# Reload pending message catalogs
|
| 663 |
+
switch -exact -- $option {
|
| 664 |
+
mcfolder { return [Load $ns [PackageLocales $ns]] }
|
| 665 |
+
loadcmd { return [Load $ns [PackageLocales $ns] 1] }
|
| 666 |
+
}
|
| 667 |
+
return 0
|
| 668 |
+
}
|
| 669 |
+
default {
|
| 670 |
+
return -code error "unknown subcommand \"$subcommand\":\
|
| 671 |
+
must be get, isset, set, or unset"
|
| 672 |
+
}
|
| 673 |
+
}
|
| 674 |
+
return
|
| 675 |
+
}
|
| 676 |
+
|
| 677 |
+
# msgcat::PackagePreferences --
|
| 678 |
+
#
|
| 679 |
+
# Return eventual present package preferences or the default list if not
|
| 680 |
+
# present.
|
| 681 |
+
#
|
| 682 |
+
# Arguments:
|
| 683 |
+
# ns Package namespace
|
| 684 |
+
#
|
| 685 |
+
# Results:
|
| 686 |
+
# locale list
|
| 687 |
+
|
| 688 |
+
proc msgcat::PackagePreferences {ns} {
|
| 689 |
+
variable PackageConfig
|
| 690 |
+
if {[dict exists $PackageConfig loclist $ns]} {
|
| 691 |
+
return [dict get $PackageConfig loclist $ns]
|
| 692 |
+
}
|
| 693 |
+
variable Loclist
|
| 694 |
+
return $Loclist
|
| 695 |
+
}
|
| 696 |
+
|
| 697 |
+
# msgcat::PackageLocales --
|
| 698 |
+
#
|
| 699 |
+
# Return eventual present package locales or the default list if not
|
| 700 |
+
# present.
|
| 701 |
+
#
|
| 702 |
+
# Arguments:
|
| 703 |
+
# ns Package namespace
|
| 704 |
+
#
|
| 705 |
+
# Results:
|
| 706 |
+
# locale list
|
| 707 |
+
|
| 708 |
+
proc msgcat::PackageLocales {ns} {
|
| 709 |
+
variable PackageConfig
|
| 710 |
+
if {[dict exists $PackageConfig loadedlocales $ns]} {
|
| 711 |
+
return [dict get $PackageConfig loadedlocales $ns]
|
| 712 |
+
}
|
| 713 |
+
variable LoadedLocales
|
| 714 |
+
return $LoadedLocales
|
| 715 |
+
}
|
| 716 |
+
|
| 717 |
+
# msgcat::ListComplement --
|
| 718 |
+
#
|
| 719 |
+
# Build the complement of two lists.
|
| 720 |
+
# Return a list with all elements in list2 but not in list1.
|
| 721 |
+
# Optionally return the intersection.
|
| 722 |
+
#
|
| 723 |
+
# Arguments:
|
| 724 |
+
# list1 excluded list
|
| 725 |
+
# list2 included list
|
| 726 |
+
# inlistname If not "", write in this variable the intersection list
|
| 727 |
+
#
|
| 728 |
+
# Results:
|
| 729 |
+
# list with all elements in list2 but not in list1
|
| 730 |
+
|
| 731 |
+
proc msgcat::ListComplement {list1 list2 {inlistname ""}} {
|
| 732 |
+
if {"" ne $inlistname} {
|
| 733 |
+
upvar 1 $inlistname inlist
|
| 734 |
+
}
|
| 735 |
+
set inlist {}
|
| 736 |
+
set outlist {}
|
| 737 |
+
foreach item $list2 {
|
| 738 |
+
if {$item in $list1} {
|
| 739 |
+
lappend inlist $item
|
| 740 |
+
} else {
|
| 741 |
+
lappend outlist $item
|
| 742 |
+
}
|
| 743 |
+
}
|
| 744 |
+
return $outlist
|
| 745 |
+
}
|
| 746 |
+
|
| 747 |
+
# msgcat::mcload --
|
| 748 |
+
#
|
| 749 |
+
# Attempt to load message catalogs for each locale in the
|
| 750 |
+
# preference list from the specified directory.
|
| 751 |
+
#
|
| 752 |
+
# Arguments:
|
| 753 |
+
# langdir The directory to search.
|
| 754 |
+
#
|
| 755 |
+
# Results:
|
| 756 |
+
# Returns the number of message catalogs that were loaded.
|
| 757 |
+
|
| 758 |
+
proc msgcat::mcload {langdir} {
|
| 759 |
+
return [uplevel 1 [list\
|
| 760 |
+
[namespace origin mcpackageconfig] set mcfolder $langdir]]
|
| 761 |
+
}
|
| 762 |
+
|
| 763 |
+
# msgcat::LoadAll --
|
| 764 |
+
#
|
| 765 |
+
# Load a list of locales for all packages not having a package locale
|
| 766 |
+
# list.
|
| 767 |
+
#
|
| 768 |
+
# Arguments:
|
| 769 |
+
# langdir The directory to search.
|
| 770 |
+
#
|
| 771 |
+
# Results:
|
| 772 |
+
# Returns the number of message catalogs that were loaded.
|
| 773 |
+
|
| 774 |
+
proc msgcat::LoadAll {locales} {
|
| 775 |
+
variable PackageConfig
|
| 776 |
+
variable LoadedLocales
|
| 777 |
+
if {0 == [llength $locales]} { return {} }
|
| 778 |
+
# filter jet unloaded locales
|
| 779 |
+
set locales [ListComplement $LoadedLocales $locales]
|
| 780 |
+
if {0 == [llength $locales]} { return {} }
|
| 781 |
+
lappend LoadedLocales {*}$locales
|
| 782 |
+
|
| 783 |
+
set packages [lsort -unique [concat\
|
| 784 |
+
[dict keys [dict get $PackageConfig loadcmd]]\
|
| 785 |
+
[dict keys [dict get $PackageConfig mcfolder]]]]
|
| 786 |
+
foreach ns $packages {
|
| 787 |
+
if {! [dict exists $PackageConfig loclist $ns] } {
|
| 788 |
+
Load $ns $locales
|
| 789 |
+
}
|
| 790 |
+
}
|
| 791 |
+
return $locales
|
| 792 |
+
}
|
| 793 |
+
|
| 794 |
+
# msgcat::Load --
|
| 795 |
+
#
|
| 796 |
+
# Invoke message load callback and load message catalog files.
|
| 797 |
+
#
|
| 798 |
+
# Arguments:
|
| 799 |
+
# ns Namespace (equal package) to load the message catalog.
|
| 800 |
+
# locales List of locales to load.
|
| 801 |
+
# callbackonly true if only callback should be invoked
|
| 802 |
+
#
|
| 803 |
+
# Results:
|
| 804 |
+
# Returns the number of message catalogs that were loaded.
|
| 805 |
+
|
| 806 |
+
proc msgcat::Load {ns locales {callbackonly 0}} {
|
| 807 |
+
variable FileLocale
|
| 808 |
+
variable PackageConfig
|
| 809 |
+
variable LoadedLocals
|
| 810 |
+
|
| 811 |
+
if {0 == [llength $locales]} { return 0 }
|
| 812 |
+
|
| 813 |
+
# Invoke callback
|
| 814 |
+
Invoke loadcmd $locales $ns
|
| 815 |
+
|
| 816 |
+
if {$callbackonly || ![dict exists $PackageConfig mcfolder $ns]} {
|
| 817 |
+
return 0
|
| 818 |
+
}
|
| 819 |
+
|
| 820 |
+
# Invoke file load
|
| 821 |
+
set langdir [dict get $PackageConfig mcfolder $ns]
|
| 822 |
+
|
| 823 |
+
# Save the file locale if we are recursively called
|
| 824 |
+
if {[info exists FileLocale]} {
|
| 825 |
+
set nestedFileLocale $FileLocale
|
| 826 |
+
}
|
| 827 |
+
set x 0
|
| 828 |
+
foreach p $locales {
|
| 829 |
+
if {$p eq {}} {
|
| 830 |
+
set p ROOT
|
| 831 |
+
}
|
| 832 |
+
set langfile [file join $langdir $p.msg]
|
| 833 |
+
if {[file exists $langfile]} {
|
| 834 |
+
incr x
|
| 835 |
+
set FileLocale [string tolower\
|
| 836 |
+
[file tail [file rootname $langfile]]]
|
| 837 |
+
if {"root" eq $FileLocale} {
|
| 838 |
+
set FileLocale ""
|
| 839 |
+
}
|
| 840 |
+
namespace inscope $ns [list ::source -encoding utf-8 $langfile]
|
| 841 |
+
unset FileLocale
|
| 842 |
+
}
|
| 843 |
+
}
|
| 844 |
+
if {[info exists nestedFileLocale]} {
|
| 845 |
+
set FileLocale $nestedFileLocale
|
| 846 |
+
}
|
| 847 |
+
return $x
|
| 848 |
+
}
|
| 849 |
+
|
| 850 |
+
# msgcat::Invoke --
|
| 851 |
+
#
|
| 852 |
+
# Invoke a set of registered callbacks.
|
| 853 |
+
# The callback is only invoked, if its registered namespace exists.
|
| 854 |
+
#
|
| 855 |
+
# Arguments:
|
| 856 |
+
# index Index into PackageConfig to get callback command
|
| 857 |
+
# arglist parameters to the callback invocation
|
| 858 |
+
# ns (Optional) package to call.
|
| 859 |
+
# If not given or empty, check all registered packages.
|
| 860 |
+
# resultname Variable to save the callback result of the last called
|
| 861 |
+
# callback to. May be set to "" to discard the result.
|
| 862 |
+
# failerror (0) Fail on error if true. Otherwise call bgerror.
|
| 863 |
+
#
|
| 864 |
+
# Results:
|
| 865 |
+
# Possible values:
|
| 866 |
+
# - 0: no valid command registered
|
| 867 |
+
# - 1: registered command was the empty string
|
| 868 |
+
# - 2: registered command called, resultname is set
|
| 869 |
+
# - 3: registered command failed
|
| 870 |
+
# If multiple commands are called, the maximum of all results is returned.
|
| 871 |
+
|
| 872 |
+
proc msgcat::Invoke {index arglist {ns ""} {resultname ""} {failerror 0}} {
|
| 873 |
+
variable PackageConfig
|
| 874 |
+
variable Config
|
| 875 |
+
if {"" ne $resultname} {
|
| 876 |
+
upvar 1 $resultname result
|
| 877 |
+
}
|
| 878 |
+
if {"" eq $ns} {
|
| 879 |
+
set packageList [dict keys [dict get $PackageConfig $index]]
|
| 880 |
+
} else {
|
| 881 |
+
set packageList [list $ns]
|
| 882 |
+
}
|
| 883 |
+
set ret 0
|
| 884 |
+
foreach ns $packageList {
|
| 885 |
+
if {[dict exists $PackageConfig $index $ns] && [namespace exists $ns]} {
|
| 886 |
+
set cmd [dict get $PackageConfig $index $ns]
|
| 887 |
+
if {"" eq $cmd} {
|
| 888 |
+
if {$ret == 0} {set ret 1}
|
| 889 |
+
} else {
|
| 890 |
+
if {$failerror} {
|
| 891 |
+
set result [namespace inscope $ns $cmd {*}$arglist]
|
| 892 |
+
set ret 2
|
| 893 |
+
} elseif {1 == [catch {
|
| 894 |
+
set result [namespace inscope $ns $cmd {*}$arglist]
|
| 895 |
+
if {$ret < 2} {set ret 2}
|
| 896 |
+
} err derr]} {
|
| 897 |
+
after idle [concat [::interp bgerror ""]\
|
| 898 |
+
[list $err $derr]]
|
| 899 |
+
set ret 3
|
| 900 |
+
}
|
| 901 |
+
}
|
| 902 |
+
}
|
| 903 |
+
}
|
| 904 |
+
return $ret
|
| 905 |
+
}
|
| 906 |
+
|
| 907 |
+
# msgcat::mcset --
|
| 908 |
+
#
|
| 909 |
+
# Set the translation for a given string in a specified locale.
|
| 910 |
+
#
|
| 911 |
+
# Arguments:
|
| 912 |
+
# locale The locale to use.
|
| 913 |
+
# src The source string.
|
| 914 |
+
# dest (Optional) The translated string. If omitted,
|
| 915 |
+
# the source string is used.
|
| 916 |
+
#
|
| 917 |
+
# Results:
|
| 918 |
+
# Returns the new locale.
|
| 919 |
+
|
| 920 |
+
proc msgcat::mcset {locale src {dest ""}} {
|
| 921 |
+
variable Msgs
|
| 922 |
+
if {[llength [info level 0]] == 3} { ;# dest not specified
|
| 923 |
+
set dest $src
|
| 924 |
+
}
|
| 925 |
+
|
| 926 |
+
set ns [uplevel 1 [list ::namespace current]]
|
| 927 |
+
|
| 928 |
+
set locale [string tolower $locale]
|
| 929 |
+
|
| 930 |
+
dict set Msgs $ns $locale $src $dest
|
| 931 |
+
return $dest
|
| 932 |
+
}
|
| 933 |
+
|
| 934 |
+
# msgcat::mcflset --
|
| 935 |
+
#
|
| 936 |
+
# Set the translation for a given string in the current file locale.
|
| 937 |
+
#
|
| 938 |
+
# Arguments:
|
| 939 |
+
# src The source string.
|
| 940 |
+
# dest (Optional) The translated string. If omitted,
|
| 941 |
+
# the source string is used.
|
| 942 |
+
#
|
| 943 |
+
# Results:
|
| 944 |
+
# Returns the new locale.
|
| 945 |
+
|
| 946 |
+
proc msgcat::mcflset {src {dest ""}} {
|
| 947 |
+
variable FileLocale
|
| 948 |
+
variable Msgs
|
| 949 |
+
|
| 950 |
+
if {![info exists FileLocale]} {
|
| 951 |
+
return -code error "must only be used inside a message catalog loaded\
|
| 952 |
+
with ::msgcat::mcload"
|
| 953 |
+
}
|
| 954 |
+
return [uplevel 1 [list [namespace origin mcset] $FileLocale $src $dest]]
|
| 955 |
+
}
|
| 956 |
+
|
| 957 |
+
# msgcat::mcmset --
|
| 958 |
+
#
|
| 959 |
+
# Set the translation for multiple strings in a specified locale.
|
| 960 |
+
#
|
| 961 |
+
# Arguments:
|
| 962 |
+
# locale The locale to use.
|
| 963 |
+
# pairs One or more src/dest pairs (must be even length)
|
| 964 |
+
#
|
| 965 |
+
# Results:
|
| 966 |
+
# Returns the number of pairs processed
|
| 967 |
+
|
| 968 |
+
proc msgcat::mcmset {locale pairs} {
|
| 969 |
+
variable Msgs
|
| 970 |
+
|
| 971 |
+
set length [llength $pairs]
|
| 972 |
+
if {$length % 2} {
|
| 973 |
+
return -code error "bad translation list:\
|
| 974 |
+
should be \"[lindex [info level 0] 0] locale {src dest ...}\""
|
| 975 |
+
}
|
| 976 |
+
|
| 977 |
+
set locale [string tolower $locale]
|
| 978 |
+
set ns [uplevel 1 [list ::namespace current]]
|
| 979 |
+
|
| 980 |
+
foreach {src dest} $pairs {
|
| 981 |
+
dict set Msgs $ns $locale $src $dest
|
| 982 |
+
}
|
| 983 |
+
|
| 984 |
+
return [expr {$length / 2}]
|
| 985 |
+
}
|
| 986 |
+
|
| 987 |
+
# msgcat::mcflmset --
|
| 988 |
+
#
|
| 989 |
+
# Set the translation for multiple strings in the mc file locale.
|
| 990 |
+
#
|
| 991 |
+
# Arguments:
|
| 992 |
+
# pairs One or more src/dest pairs (must be even length)
|
| 993 |
+
#
|
| 994 |
+
# Results:
|
| 995 |
+
# Returns the number of pairs processed
|
| 996 |
+
|
| 997 |
+
proc msgcat::mcflmset {pairs} {
|
| 998 |
+
variable FileLocale
|
| 999 |
+
variable Msgs
|
| 1000 |
+
|
| 1001 |
+
if {![info exists FileLocale]} {
|
| 1002 |
+
return -code error "must only be used inside a message catalog loaded\
|
| 1003 |
+
with ::msgcat::mcload"
|
| 1004 |
+
}
|
| 1005 |
+
return [uplevel 1 [list [namespace origin mcmset] $FileLocale $pairs]]
|
| 1006 |
+
}
|
| 1007 |
+
|
| 1008 |
+
# msgcat::mcunknown --
|
| 1009 |
+
#
|
| 1010 |
+
# This routine is called by msgcat::mc if a translation cannot
|
| 1011 |
+
# be found for a string and no unknowncmd is set for the current
|
| 1012 |
+
# package. This routine is intended to be replaced
|
| 1013 |
+
# by an application specific routine for error reporting
|
| 1014 |
+
# purposes. The default behavior is to return the source string.
|
| 1015 |
+
# If additional args are specified, the format command will be used
|
| 1016 |
+
# to work them into the traslated string.
|
| 1017 |
+
#
|
| 1018 |
+
# Arguments:
|
| 1019 |
+
# locale The current locale.
|
| 1020 |
+
# src The string to be translated.
|
| 1021 |
+
# args Args to pass to the format command
|
| 1022 |
+
#
|
| 1023 |
+
# Results:
|
| 1024 |
+
# Returns the translated value.
|
| 1025 |
+
|
| 1026 |
+
proc msgcat::mcunknown {args} {
|
| 1027 |
+
return [uplevel 1 [list [namespace origin DefaultUnknown] {*}$args]]
|
| 1028 |
+
}
|
| 1029 |
+
|
| 1030 |
+
# msgcat::DefaultUnknown --
|
| 1031 |
+
#
|
| 1032 |
+
# This routine is called by msgcat::mc if a translation cannot
|
| 1033 |
+
# be found for a string in the following circumstances:
|
| 1034 |
+
# - Default global handler, if mcunknown is not redefined.
|
| 1035 |
+
# - Per package handler, if the package sets unknowncmd to the empty
|
| 1036 |
+
# string.
|
| 1037 |
+
# It returna the source string if the argument list is empty.
|
| 1038 |
+
# If additional args are specified, the format command will be used
|
| 1039 |
+
# to work them into the traslated string.
|
| 1040 |
+
#
|
| 1041 |
+
# Arguments:
|
| 1042 |
+
# locale (unused) The current locale.
|
| 1043 |
+
# src The string to be translated.
|
| 1044 |
+
# args Args to pass to the format command
|
| 1045 |
+
#
|
| 1046 |
+
# Results:
|
| 1047 |
+
# Returns the translated value.
|
| 1048 |
+
|
| 1049 |
+
proc msgcat::DefaultUnknown {locale src args} {
|
| 1050 |
+
if {[llength $args]} {
|
| 1051 |
+
return [format $src {*}$args]
|
| 1052 |
+
} else {
|
| 1053 |
+
return $src
|
| 1054 |
+
}
|
| 1055 |
+
}
|
| 1056 |
+
|
| 1057 |
+
# msgcat::mcmax --
|
| 1058 |
+
#
|
| 1059 |
+
# Calculates the maximum length of the translated strings of the given
|
| 1060 |
+
# list.
|
| 1061 |
+
#
|
| 1062 |
+
# Arguments:
|
| 1063 |
+
# args strings to translate.
|
| 1064 |
+
#
|
| 1065 |
+
# Results:
|
| 1066 |
+
# Returns the length of the longest translated string.
|
| 1067 |
+
|
| 1068 |
+
proc msgcat::mcmax {args} {
|
| 1069 |
+
set max 0
|
| 1070 |
+
foreach string $args {
|
| 1071 |
+
set translated [uplevel 1 [list [namespace origin mc] $string]]
|
| 1072 |
+
set len [string length $translated]
|
| 1073 |
+
if {$len>$max} {
|
| 1074 |
+
set max $len
|
| 1075 |
+
}
|
| 1076 |
+
}
|
| 1077 |
+
return $max
|
| 1078 |
+
}
|
| 1079 |
+
|
| 1080 |
+
# Convert the locale values stored in environment variables to a form
|
| 1081 |
+
# suitable for passing to [mclocale]
|
| 1082 |
+
proc msgcat::ConvertLocale {value} {
|
| 1083 |
+
# Assume $value is of form: $language[_$territory][.$codeset][@modifier]
|
| 1084 |
+
# Convert to form: $language[_$territory][_$modifier]
|
| 1085 |
+
#
|
| 1086 |
+
# Comment out expanded RE version -- bugs alleged
|
| 1087 |
+
# regexp -expanded {
|
| 1088 |
+
# ^ # Match all the way to the beginning
|
| 1089 |
+
# ([^_.@]*) # Match "lanugage"; ends with _, ., or @
|
| 1090 |
+
# (_([^.@]*))? # Match (optional) "territory"; starts with _
|
| 1091 |
+
# ([.]([^@]*))? # Match (optional) "codeset"; starts with .
|
| 1092 |
+
# (@(.*))? # Match (optional) "modifier"; starts with @
|
| 1093 |
+
# $ # Match all the way to the end
|
| 1094 |
+
# } $value -> language _ territory _ codeset _ modifier
|
| 1095 |
+
if {![regexp {^([^_.@]+)(_([^.@]*))?([.]([^@]*))?(@(.*))?$} $value \
|
| 1096 |
+
-> language _ territory _ codeset _ modifier]} {
|
| 1097 |
+
return -code error "invalid locale '$value': empty language part"
|
| 1098 |
+
}
|
| 1099 |
+
set ret $language
|
| 1100 |
+
if {[string length $territory]} {
|
| 1101 |
+
append ret _$territory
|
| 1102 |
+
}
|
| 1103 |
+
if {[string length $modifier]} {
|
| 1104 |
+
append ret _$modifier
|
| 1105 |
+
}
|
| 1106 |
+
return $ret
|
| 1107 |
+
}
|
| 1108 |
+
|
| 1109 |
+
# Initialize the default locale
|
| 1110 |
+
proc msgcat::Init {} {
|
| 1111 |
+
global env
|
| 1112 |
+
|
| 1113 |
+
#
|
| 1114 |
+
# set default locale, try to get from environment
|
| 1115 |
+
#
|
| 1116 |
+
foreach varName {LC_ALL LC_MESSAGES LANG} {
|
| 1117 |
+
if {[info exists env($varName)] && ("" ne $env($varName))} {
|
| 1118 |
+
if {![catch {
|
| 1119 |
+
mclocale [ConvertLocale $env($varName)]
|
| 1120 |
+
}]} {
|
| 1121 |
+
return
|
| 1122 |
+
}
|
| 1123 |
+
}
|
| 1124 |
+
}
|
| 1125 |
+
#
|
| 1126 |
+
# On Darwin, fallback to current CFLocale identifier if available.
|
| 1127 |
+
#
|
| 1128 |
+
if {[info exists ::tcl::mac::locale] && $::tcl::mac::locale ne ""} {
|
| 1129 |
+
if {![catch {
|
| 1130 |
+
mclocale [ConvertLocale $::tcl::mac::locale]
|
| 1131 |
+
}]} {
|
| 1132 |
+
return
|
| 1133 |
+
}
|
| 1134 |
+
}
|
| 1135 |
+
#
|
| 1136 |
+
# The rest of this routine is special processing for Windows or
|
| 1137 |
+
# Cygwin. All other platforms, get out now.
|
| 1138 |
+
#
|
| 1139 |
+
if {([info sharedlibextension] ne ".dll")
|
| 1140 |
+
|| [catch {package require registry}]} {
|
| 1141 |
+
mclocale C
|
| 1142 |
+
return
|
| 1143 |
+
}
|
| 1144 |
+
#
|
| 1145 |
+
# On Windows or Cygwin, try to set locale depending on registry
|
| 1146 |
+
# settings, or fall back on locale of "C".
|
| 1147 |
+
#
|
| 1148 |
+
|
| 1149 |
+
# On Vista and later:
|
| 1150 |
+
# HCU/Control Panel/Desktop : PreferredUILanguages is for language packs,
|
| 1151 |
+
# HCU/Control Pannel/International : localName is the default locale.
|
| 1152 |
+
#
|
| 1153 |
+
# They contain the local string as RFC5646, composed of:
|
| 1154 |
+
# [a-z]{2,3} : language
|
| 1155 |
+
# -[a-z]{4} : script (optional, translated by table Latn->latin)
|
| 1156 |
+
# -[a-z]{2}|[0-9]{3} : territory (optional, numerical region codes not used)
|
| 1157 |
+
# (-.*)* : variant, extension, private use (optional, not used)
|
| 1158 |
+
# Those are translated to local strings.
|
| 1159 |
+
# Examples: de-CH -> de_ch, sr-Latn-CS -> sr_cs@latin, es-419 -> es
|
| 1160 |
+
#
|
| 1161 |
+
foreach key {{HKEY_CURRENT_USER\Control Panel\Desktop} {HKEY_CURRENT_USER\Control Panel\International}}\
|
| 1162 |
+
value {PreferredUILanguages localeName} {
|
| 1163 |
+
if {![catch {registry get $key $value} localeName]
|
| 1164 |
+
&& [regexp {^([a-z]{2,3})(?:-([a-z]{4}))?(?:-([a-z]{2}))?(?:-.+)?$}\
|
| 1165 |
+
[string tolower $localeName] match locale script territory]} {
|
| 1166 |
+
if {"" ne $territory} {
|
| 1167 |
+
append locale _ $territory
|
| 1168 |
+
}
|
| 1169 |
+
set modifierDict [dict create latn latin cyrl cyrillic]
|
| 1170 |
+
if {[dict exists $modifierDict $script]} {
|
| 1171 |
+
append locale @ [dict get $modifierDict $script]
|
| 1172 |
+
}
|
| 1173 |
+
if {![catch {mclocale [ConvertLocale $locale]}]} {
|
| 1174 |
+
return
|
| 1175 |
+
}
|
| 1176 |
+
}
|
| 1177 |
+
}
|
| 1178 |
+
|
| 1179 |
+
# then check value locale which contains a numerical language ID
|
| 1180 |
+
if {[catch {
|
| 1181 |
+
set locale [registry get $key "locale"]
|
| 1182 |
+
}]} {
|
| 1183 |
+
mclocale C
|
| 1184 |
+
return
|
| 1185 |
+
}
|
| 1186 |
+
#
|
| 1187 |
+
# Keep trying to match against smaller and smaller suffixes
|
| 1188 |
+
# of the registry value, since the latter hexadigits appear
|
| 1189 |
+
# to determine general language and earlier hexadigits determine
|
| 1190 |
+
# more precise information, such as territory. For example,
|
| 1191 |
+
# 0409 - English - United States
|
| 1192 |
+
# 0809 - English - United Kingdom
|
| 1193 |
+
# Add more translations to the WinRegToISO639 array above.
|
| 1194 |
+
#
|
| 1195 |
+
variable WinRegToISO639
|
| 1196 |
+
set locale [string tolower $locale]
|
| 1197 |
+
while {[string length $locale]} {
|
| 1198 |
+
if {![catch {
|
| 1199 |
+
mclocale [ConvertLocale [dict get $WinRegToISO639 $locale]]
|
| 1200 |
+
}]} {
|
| 1201 |
+
return
|
| 1202 |
+
}
|
| 1203 |
+
set locale [string range $locale 1 end]
|
| 1204 |
+
}
|
| 1205 |
+
#
|
| 1206 |
+
# No translation known. Fall back on "C" locale
|
| 1207 |
+
#
|
| 1208 |
+
mclocale C
|
| 1209 |
+
}
|
| 1210 |
+
msgcat::Init
|
my_container_sandbox/workspace/anaconda3/lib/tcl8/8.5/tcltest-2.5.3.tm
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
my_container_sandbox/workspace/anaconda3/lib/tcl8/8.6/http-2.9.5.tm
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
my_container_sandbox/workspace/anaconda3/lib/tcl8/8.6/tdbc/sqlite3-1.1.3.tm
ADDED
|
@@ -0,0 +1,715 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# tdbcsqlite3.tcl --
|
| 2 |
+
#
|
| 3 |
+
# SQLite3 database driver for TDBC
|
| 4 |
+
#
|
| 5 |
+
# Copyright (c) 2008 by Kevin B. Kenny.
|
| 6 |
+
# See the file "license.terms" for information on usage and redistribution
|
| 7 |
+
# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
|
| 8 |
+
#
|
| 9 |
+
# RCS: @(#) $Id: tdbcodbc.tcl,v 1.47 2008/02/27 02:08:27 kennykb Exp $
|
| 10 |
+
#
|
| 11 |
+
#------------------------------------------------------------------------------
|
| 12 |
+
|
| 13 |
+
package require tdbc
|
| 14 |
+
package require sqlite3
|
| 15 |
+
|
| 16 |
+
package provide tdbc::sqlite3 1.1.3
|
| 17 |
+
|
| 18 |
+
namespace eval tdbc::sqlite3 {
|
| 19 |
+
namespace export connection
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
#------------------------------------------------------------------------------
|
| 23 |
+
#
|
| 24 |
+
# tdbc::sqlite3::connection --
|
| 25 |
+
#
|
| 26 |
+
# Class representing a SQLite3 database connection
|
| 27 |
+
#
|
| 28 |
+
#------------------------------------------------------------------------------
|
| 29 |
+
|
| 30 |
+
::oo::class create ::tdbc::sqlite3::connection {
|
| 31 |
+
|
| 32 |
+
superclass ::tdbc::connection
|
| 33 |
+
|
| 34 |
+
variable timeout
|
| 35 |
+
|
| 36 |
+
# The constructor accepts a database name and opens the database.
|
| 37 |
+
|
| 38 |
+
constructor {databaseName args} {
|
| 39 |
+
set timeout 0
|
| 40 |
+
if {[llength $args] % 2 != 0} {
|
| 41 |
+
set cmd [lrange [info level 0] 0 end-[llength $args]]
|
| 42 |
+
return -code error \
|
| 43 |
+
-errorcode {TDBC GENERAL_ERROR HY000 SQLITE3 WRONGNUMARGS} \
|
| 44 |
+
"wrong # args, should be \"$cmd ?-option value?...\""
|
| 45 |
+
}
|
| 46 |
+
next
|
| 47 |
+
sqlite3 [namespace current]::db $databaseName
|
| 48 |
+
if {[llength $args] > 0} {
|
| 49 |
+
my configure {*}$args
|
| 50 |
+
}
|
| 51 |
+
db nullvalue \ufffd
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
# The 'statementCreate' method forwards to the constructor of the
|
| 55 |
+
# statement class
|
| 56 |
+
|
| 57 |
+
forward statementCreate ::tdbc::sqlite3::statement create
|
| 58 |
+
|
| 59 |
+
# The 'configure' method queries and sets options to the database
|
| 60 |
+
|
| 61 |
+
method configure args {
|
| 62 |
+
if {[llength $args] == 0} {
|
| 63 |
+
|
| 64 |
+
# Query all configuration options
|
| 65 |
+
|
| 66 |
+
set result {-encoding utf-8}
|
| 67 |
+
lappend result -isolation
|
| 68 |
+
if {[db onecolumn {PRAGMA read_uncommitted}]} {
|
| 69 |
+
lappend result readuncommitted
|
| 70 |
+
} else {
|
| 71 |
+
lappend result serializable
|
| 72 |
+
}
|
| 73 |
+
lappend result -readonly 0
|
| 74 |
+
lappend result -timeout $timeout
|
| 75 |
+
return $result
|
| 76 |
+
|
| 77 |
+
} elseif {[llength $args] == 1} {
|
| 78 |
+
|
| 79 |
+
# Query a single option
|
| 80 |
+
|
| 81 |
+
set option [lindex $args 0]
|
| 82 |
+
switch -exact -- $option {
|
| 83 |
+
-e - -en - -enc - -enco - -encod - -encodi - -encodin -
|
| 84 |
+
-encoding {
|
| 85 |
+
return utf-8
|
| 86 |
+
}
|
| 87 |
+
-i - -is - -iso - -isol - -isola - -isolat - -isolati -
|
| 88 |
+
-isolatio - -isolation {
|
| 89 |
+
if {[db onecolumn {PRAGMA read_uncommitted}]} {
|
| 90 |
+
return readuncommitted
|
| 91 |
+
} else {
|
| 92 |
+
return serializable
|
| 93 |
+
}
|
| 94 |
+
}
|
| 95 |
+
-r - -re - -rea - -read - -reado - -readon - -readonl -
|
| 96 |
+
-readonly {
|
| 97 |
+
return 0
|
| 98 |
+
}
|
| 99 |
+
-t - -ti - -tim - -time - -timeo - -timeou - -timeout {
|
| 100 |
+
return $timeout
|
| 101 |
+
}
|
| 102 |
+
default {
|
| 103 |
+
return -code error \
|
| 104 |
+
-errorcode [list TDBC GENERAL_ERROR HY000 SQLITE3 \
|
| 105 |
+
BADOPTION $option] \
|
| 106 |
+
"bad option \"$option\": must be\
|
| 107 |
+
-encoding, -isolation, -readonly or -timeout"
|
| 108 |
+
|
| 109 |
+
}
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
} elseif {[llength $args] % 2 != 0} {
|
| 113 |
+
|
| 114 |
+
# Syntax error
|
| 115 |
+
|
| 116 |
+
set cmd [lrange [info level 0] 0 end-[llength $args]]
|
| 117 |
+
return -code error \
|
| 118 |
+
-errorcode [list TDBC GENERAL_ERROR HY000 \
|
| 119 |
+
SQLITE3 WRONGNUMARGS] \
|
| 120 |
+
"wrong # args, should be \" $cmd ?-option value?...\""
|
| 121 |
+
}
|
| 122 |
+
|
| 123 |
+
# Set one or more options
|
| 124 |
+
|
| 125 |
+
foreach {option value} $args {
|
| 126 |
+
switch -exact -- $option {
|
| 127 |
+
-e - -en - -enc - -enco - -encod - -encodi - -encodin -
|
| 128 |
+
-encoding {
|
| 129 |
+
if {$value ne {utf-8}} {
|
| 130 |
+
return -code error \
|
| 131 |
+
-errorcode [list TDBC FEATURE_NOT_SUPPORTED 0A000 \
|
| 132 |
+
SQLITE3 ENCODING] \
|
| 133 |
+
"-encoding not supported. SQLite3 is always \
|
| 134 |
+
Unicode."
|
| 135 |
+
}
|
| 136 |
+
}
|
| 137 |
+
-i - -is - -iso - -isol - -isola - -isolat - -isolati -
|
| 138 |
+
-isolatio - -isolation {
|
| 139 |
+
switch -exact -- $value {
|
| 140 |
+
readu - readun - readunc - readunco - readuncom -
|
| 141 |
+
readuncomm - readuncommi - readuncommit -
|
| 142 |
+
readuncommitt - readuncommitte - readuncommitted {
|
| 143 |
+
db eval {PRAGMA read_uncommitted = 1}
|
| 144 |
+
}
|
| 145 |
+
readc - readco - readcom - readcomm - readcommi -
|
| 146 |
+
readcommit - readcommitt - readcommitte -
|
| 147 |
+
readcommitted -
|
| 148 |
+
rep - repe - repea - repeat - repeata - repeatab -
|
| 149 |
+
repeatabl - repeatable - repeatabler - repeatablere -
|
| 150 |
+
repeatablerea - repeatablread -
|
| 151 |
+
s - se - ser - seri - seria - serial - seriali -
|
| 152 |
+
serializ - serializa - serializab - serializabl -
|
| 153 |
+
serializable -
|
| 154 |
+
reado - readon - readonl - readonly {
|
| 155 |
+
db eval {PRAGMA read_uncommitted = 0}
|
| 156 |
+
}
|
| 157 |
+
default {
|
| 158 |
+
return -code error \
|
| 159 |
+
-errorcode [list TDBC GENERAL_ERROR HY000 \
|
| 160 |
+
SQLITE3 BADISOLATION $value] \
|
| 161 |
+
"bad isolation level \"$value\":\
|
| 162 |
+
should be readuncommitted, readcommitted,\
|
| 163 |
+
repeatableread, serializable, or readonly"
|
| 164 |
+
}
|
| 165 |
+
}
|
| 166 |
+
}
|
| 167 |
+
-r - -re - -rea - -read - -reado - -readon - -readonl -
|
| 168 |
+
-readonly {
|
| 169 |
+
if {$value} {
|
| 170 |
+
return -code error \
|
| 171 |
+
-errorcode [list TDBC FEATURE_NOT_SUPPORTED 0A000 \
|
| 172 |
+
SQLITE3 READONLY] \
|
| 173 |
+
"SQLite3's Tcl API does not support read-only\
|
| 174 |
+
access"
|
| 175 |
+
}
|
| 176 |
+
}
|
| 177 |
+
-t - -ti - -tim - -time - -timeo - -timeou - -timeout {
|
| 178 |
+
if {![string is integer $value]} {
|
| 179 |
+
return -code error \
|
| 180 |
+
-errorcode [list TDBC DATA_EXCEPTION 22018 \
|
| 181 |
+
SQLITE3 $value] \
|
| 182 |
+
"expected integer but got \"$value\""
|
| 183 |
+
}
|
| 184 |
+
db timeout $value
|
| 185 |
+
set timeout $value
|
| 186 |
+
}
|
| 187 |
+
default {
|
| 188 |
+
return -code error \
|
| 189 |
+
-errorcode [list TDBC GENERAL_ERROR HY000 \
|
| 190 |
+
SQLITE3 BADOPTION $value] \
|
| 191 |
+
"bad option \"$option\": must be\
|
| 192 |
+
-encoding, -isolation, -readonly or -timeout"
|
| 193 |
+
|
| 194 |
+
}
|
| 195 |
+
}
|
| 196 |
+
}
|
| 197 |
+
return
|
| 198 |
+
}
|
| 199 |
+
|
| 200 |
+
# The 'tables' method introspects on the tables in the database.
|
| 201 |
+
|
| 202 |
+
method tables {{pattern %}} {
|
| 203 |
+
set retval {}
|
| 204 |
+
my foreach row {
|
| 205 |
+
SELECT * from sqlite_master
|
| 206 |
+
WHERE type IN ('table', 'view')
|
| 207 |
+
AND name LIKE :pattern
|
| 208 |
+
} {
|
| 209 |
+
dict set row name [string tolower [dict get $row name]]
|
| 210 |
+
dict set retval [dict get $row name] $row
|
| 211 |
+
}
|
| 212 |
+
return $retval
|
| 213 |
+
}
|
| 214 |
+
|
| 215 |
+
# The 'columns' method introspects on columns of a table.
|
| 216 |
+
|
| 217 |
+
method columns {table {pattern %}} {
|
| 218 |
+
regsub -all ' $table '' table
|
| 219 |
+
set retval {}
|
| 220 |
+
set pattern [string map [list \
|
| 221 |
+
* {[*]} \
|
| 222 |
+
? {[?]} \
|
| 223 |
+
\[ \\\[ \
|
| 224 |
+
\] \\\[ \
|
| 225 |
+
_ ? \
|
| 226 |
+
% *] [string tolower $pattern]]
|
| 227 |
+
my foreach origrow "PRAGMA table_info('$table')" {
|
| 228 |
+
set row {}
|
| 229 |
+
dict for {key value} $origrow {
|
| 230 |
+
dict set row [string tolower $key] $value
|
| 231 |
+
}
|
| 232 |
+
dict set row name [string tolower [dict get $row name]]
|
| 233 |
+
if {![string match $pattern [dict get $row name]]} {
|
| 234 |
+
continue
|
| 235 |
+
}
|
| 236 |
+
switch -regexp -matchvar info [dict get $row type] {
|
| 237 |
+
{^(.+)\(\s*([[:digit:]]+)\s*,\s*([[:digit:]]+)\s*\)\s*$} {
|
| 238 |
+
dict set row type [string tolower [lindex $info 1]]
|
| 239 |
+
dict set row precision [lindex $info 2]
|
| 240 |
+
dict set row scale [lindex $info 3]
|
| 241 |
+
}
|
| 242 |
+
{^(.+)\(\s*([[:digit:]]+)\s*\)\s*$} {
|
| 243 |
+
dict set row type [string tolower [lindex $info 1]]
|
| 244 |
+
dict set row precision [lindex $info 2]
|
| 245 |
+
dict set row scale 0
|
| 246 |
+
}
|
| 247 |
+
default {
|
| 248 |
+
dict set row type [string tolower [dict get $row type]]
|
| 249 |
+
dict set row precision 0
|
| 250 |
+
dict set row scale 0
|
| 251 |
+
}
|
| 252 |
+
}
|
| 253 |
+
dict set row nullable [expr {![dict get $row notnull]}]
|
| 254 |
+
dict set retval [dict get $row name] $row
|
| 255 |
+
}
|
| 256 |
+
return $retval
|
| 257 |
+
}
|
| 258 |
+
|
| 259 |
+
# The 'primarykeys' method enumerates the primary keys on a table.
|
| 260 |
+
|
| 261 |
+
method primarykeys {table} {
|
| 262 |
+
set result {}
|
| 263 |
+
my foreach row "PRAGMA table_info($table)" {
|
| 264 |
+
if {[dict get $row pk]} {
|
| 265 |
+
lappend result [dict create ordinalPosition \
|
| 266 |
+
[expr {[dict get $row cid]+1}] \
|
| 267 |
+
columnName \
|
| 268 |
+
[dict get $row name]]
|
| 269 |
+
}
|
| 270 |
+
}
|
| 271 |
+
return $result
|
| 272 |
+
}
|
| 273 |
+
|
| 274 |
+
# The 'foreignkeys' method enumerates the foreign keys that are
|
| 275 |
+
# declared in a table or that refer to a given table.
|
| 276 |
+
|
| 277 |
+
method foreignkeys {args} {
|
| 278 |
+
|
| 279 |
+
variable ::tdbc::generalError
|
| 280 |
+
|
| 281 |
+
# Check arguments
|
| 282 |
+
|
| 283 |
+
set argdict {}
|
| 284 |
+
if {[llength $args] % 2 != 0} {
|
| 285 |
+
set errorcode $generalError
|
| 286 |
+
lappend errorcode wrongNumArgs
|
| 287 |
+
return -code error -errorcode $errorcode \
|
| 288 |
+
"wrong # args: should be [lrange [info level 0] 0 1]\
|
| 289 |
+
?-option value?..."
|
| 290 |
+
}
|
| 291 |
+
foreach {key value} $args {
|
| 292 |
+
if {$key ni {-primary -foreign}} {
|
| 293 |
+
set errorcode $generalError
|
| 294 |
+
lappend errorcode badOption
|
| 295 |
+
return -code error -errorcode $errorcode \
|
| 296 |
+
"bad option \"$key\", must be -primary or -foreign"
|
| 297 |
+
}
|
| 298 |
+
set key [string range $key 1 end]
|
| 299 |
+
if {[dict exists $argdict $key]} {
|
| 300 |
+
set errorcode $generalError
|
| 301 |
+
lappend errorcode dupOption
|
| 302 |
+
return -code error -errorcode $errorcode \
|
| 303 |
+
"duplicate option \"$key\" supplied"
|
| 304 |
+
}
|
| 305 |
+
dict set argdict $key $value
|
| 306 |
+
}
|
| 307 |
+
|
| 308 |
+
# If we know the table with the foreign key, search just its
|
| 309 |
+
# foreign keys. Otherwise, iterate over all the tables in the
|
| 310 |
+
# database.
|
| 311 |
+
|
| 312 |
+
if {[dict exists $argdict foreign]} {
|
| 313 |
+
return [my ForeignKeysForTable [dict get $argdict foreign] \
|
| 314 |
+
$argdict]
|
| 315 |
+
} else {
|
| 316 |
+
set result {}
|
| 317 |
+
foreach foreignTable [dict keys [my tables]] {
|
| 318 |
+
lappend result {*}[my ForeignKeysForTable \
|
| 319 |
+
$foreignTable $argdict]
|
| 320 |
+
}
|
| 321 |
+
return $result
|
| 322 |
+
}
|
| 323 |
+
|
| 324 |
+
}
|
| 325 |
+
|
| 326 |
+
# The private ForeignKeysForTable method enumerates the foreign keys
|
| 327 |
+
# in a specific table.
|
| 328 |
+
#
|
| 329 |
+
# Parameters:
|
| 330 |
+
#
|
| 331 |
+
# foreignTable - Name of the table containing foreign keys.
|
| 332 |
+
# argdict - Dictionary that may or may not contain a key,
|
| 333 |
+
# 'primary', whose value is the name of a table that
|
| 334 |
+
# must hold the primary key corresponding to the foreign
|
| 335 |
+
# key. If the 'primary' key is absent, all tables are
|
| 336 |
+
# candidates.
|
| 337 |
+
# Results:
|
| 338 |
+
#
|
| 339 |
+
# Returns the list of foreign keys that meed the specified
|
| 340 |
+
# conditions, as a list of dictionaries, each containing the
|
| 341 |
+
# keys, foreignConstraintName, foreignTable, foreignColumn,
|
| 342 |
+
# primaryTable, primaryColumn, and ordinalPosition. Note that the
|
| 343 |
+
# foreign constraint name is constructed arbitrarily, since SQLite3
|
| 344 |
+
# does not report this information.
|
| 345 |
+
|
| 346 |
+
method ForeignKeysForTable {foreignTable argdict} {
|
| 347 |
+
|
| 348 |
+
set result {}
|
| 349 |
+
set n 0
|
| 350 |
+
|
| 351 |
+
# Go through the foreign keys in the given table, looking for
|
| 352 |
+
# ones that refer to the primary table (if one is given), or
|
| 353 |
+
# for any primary keys if none is given.
|
| 354 |
+
my foreach row "PRAGMA foreign_key_list($foreignTable)" {
|
| 355 |
+
if {(![dict exists $argdict primary])
|
| 356 |
+
|| ([string tolower [dict get $row table]]
|
| 357 |
+
eq [dict get $argdict primary])} {
|
| 358 |
+
|
| 359 |
+
# Construct a dictionary for each key, translating
|
| 360 |
+
# SQLite names to TDBC ones and converting sequence
|
| 361 |
+
# numbers to 1-based indexing.
|
| 362 |
+
|
| 363 |
+
set rrow [dict create foreignTable $foreignTable \
|
| 364 |
+
foreignConstraintName \
|
| 365 |
+
?$foreignTable?[dict get $row id]]
|
| 366 |
+
if {[dict exists $row seq]} {
|
| 367 |
+
dict set rrow ordinalPosition \
|
| 368 |
+
[expr {1 + [dict get $row seq]}]
|
| 369 |
+
}
|
| 370 |
+
foreach {to from} {
|
| 371 |
+
foreignColumn from
|
| 372 |
+
primaryTable table
|
| 373 |
+
primaryColumn to
|
| 374 |
+
deleteAction on_delete
|
| 375 |
+
updateAction on_update
|
| 376 |
+
} {
|
| 377 |
+
if {[dict exists $row $from]} {
|
| 378 |
+
dict set rrow $to [dict get $row $from]
|
| 379 |
+
}
|
| 380 |
+
}
|
| 381 |
+
|
| 382 |
+
# Add the newly-constucted dictionary to the result list
|
| 383 |
+
|
| 384 |
+
lappend result $rrow
|
| 385 |
+
}
|
| 386 |
+
}
|
| 387 |
+
|
| 388 |
+
return $result
|
| 389 |
+
}
|
| 390 |
+
|
| 391 |
+
# The 'preparecall' method prepares a call to a stored procedure.
|
| 392 |
+
# SQLite3 does not have stored procedures, since it's an in-process
|
| 393 |
+
# server.
|
| 394 |
+
|
| 395 |
+
method preparecall {call} {
|
| 396 |
+
return -code error \
|
| 397 |
+
-errorcode [list TDBC FEATURE_NOT_SUPPORTED 0A000 \
|
| 398 |
+
SQLITE3 PREPARECALL] \
|
| 399 |
+
{SQLite3 does not support stored procedures}
|
| 400 |
+
}
|
| 401 |
+
|
| 402 |
+
# The 'begintransaction' method launches a database transaction
|
| 403 |
+
|
| 404 |
+
method begintransaction {} {
|
| 405 |
+
db eval {BEGIN TRANSACTION}
|
| 406 |
+
}
|
| 407 |
+
|
| 408 |
+
# The 'commit' method commits a database transaction
|
| 409 |
+
|
| 410 |
+
method commit {} {
|
| 411 |
+
db eval {COMMIT}
|
| 412 |
+
}
|
| 413 |
+
|
| 414 |
+
# The 'rollback' method abandons a database transaction
|
| 415 |
+
|
| 416 |
+
method rollback {} {
|
| 417 |
+
db eval {ROLLBACK}
|
| 418 |
+
}
|
| 419 |
+
|
| 420 |
+
# The 'transaction' method executes a script as a single transaction.
|
| 421 |
+
# We override the 'transaction' method of the base class, since SQLite3
|
| 422 |
+
# has a faster implementation of the same thing. (The base class's generic
|
| 423 |
+
# method should also work.)
|
| 424 |
+
# (Don't overload the base class method, because 'break', 'continue'
|
| 425 |
+
# and 'return' in the transaction body don't work!)
|
| 426 |
+
|
| 427 |
+
#method transaction {script} {
|
| 428 |
+
# uplevel 1 [list {*}[namespace code db] transaction $script]
|
| 429 |
+
#}
|
| 430 |
+
|
| 431 |
+
method prepare {sqlCode} {
|
| 432 |
+
set result [next $sqlCode]
|
| 433 |
+
return $result
|
| 434 |
+
}
|
| 435 |
+
|
| 436 |
+
method getDBhandle {} {
|
| 437 |
+
return [namespace which db]
|
| 438 |
+
}
|
| 439 |
+
}
|
| 440 |
+
|
| 441 |
+
#------------------------------------------------------------------------------
|
| 442 |
+
#
|
| 443 |
+
# tdbc::sqlite3::statement --
|
| 444 |
+
#
|
| 445 |
+
# Class representing a statement to execute against a SQLite3 database
|
| 446 |
+
#
|
| 447 |
+
#------------------------------------------------------------------------------
|
| 448 |
+
|
| 449 |
+
::oo::class create ::tdbc::sqlite3::statement {
|
| 450 |
+
|
| 451 |
+
superclass ::tdbc::statement
|
| 452 |
+
|
| 453 |
+
variable Params db sql
|
| 454 |
+
|
| 455 |
+
# The constructor accepts the handle to the connection and the SQL
|
| 456 |
+
# code for the statement to prepare. All that it does is to parse the
|
| 457 |
+
# statement and store it. The parse is used to support the
|
| 458 |
+
# 'params' and 'paramtype' methods.
|
| 459 |
+
|
| 460 |
+
constructor {connection sqlcode} {
|
| 461 |
+
next
|
| 462 |
+
set Params {}
|
| 463 |
+
set db [$connection getDBhandle]
|
| 464 |
+
set sql $sqlcode
|
| 465 |
+
foreach token [::tdbc::tokenize $sqlcode] {
|
| 466 |
+
if {[string index $token 0] in {$ : @}} {
|
| 467 |
+
dict set Params [string range $token 1 end] \
|
| 468 |
+
{type Tcl_Obj precision 0 scale 0 nullable 1 direction in}
|
| 469 |
+
}
|
| 470 |
+
}
|
| 471 |
+
}
|
| 472 |
+
|
| 473 |
+
# The 'resultSetCreate' method relays to the result set constructor
|
| 474 |
+
|
| 475 |
+
forward resultSetCreate ::tdbc::sqlite3::resultset create
|
| 476 |
+
|
| 477 |
+
# The 'params' method returns descriptions of the parameters accepted
|
| 478 |
+
# by the statement
|
| 479 |
+
|
| 480 |
+
method params {} {
|
| 481 |
+
return $Params
|
| 482 |
+
}
|
| 483 |
+
|
| 484 |
+
# The 'paramtype' method need do nothing; Sqlite3 uses manifest typing.
|
| 485 |
+
|
| 486 |
+
method paramtype args {;}
|
| 487 |
+
|
| 488 |
+
method getDBhandle {} {
|
| 489 |
+
return $db
|
| 490 |
+
}
|
| 491 |
+
|
| 492 |
+
method getSql {} {
|
| 493 |
+
return $sql
|
| 494 |
+
}
|
| 495 |
+
|
| 496 |
+
}
|
| 497 |
+
|
| 498 |
+
#-------------------------------------------------------------------------------
|
| 499 |
+
#
|
| 500 |
+
# tdbc::sqlite3::resultset --
|
| 501 |
+
#
|
| 502 |
+
# Class that represents a SQLlite result set in Tcl
|
| 503 |
+
#
|
| 504 |
+
#-------------------------------------------------------------------------------
|
| 505 |
+
|
| 506 |
+
::oo::class create ::tdbc::sqlite3::resultset {
|
| 507 |
+
|
| 508 |
+
superclass ::tdbc::resultset
|
| 509 |
+
|
| 510 |
+
# The variables of this class all have peculiar names. The reason is
|
| 511 |
+
# that the RunQuery method needs to execute with an activation record
|
| 512 |
+
# that has no local variables whose names could conflict with names
|
| 513 |
+
# in the SQL query. We start the variable names with hyphens because
|
| 514 |
+
# they can't be bind variables.
|
| 515 |
+
|
| 516 |
+
variable -set {*}{
|
| 517 |
+
-columns -db -needcolumns -resultArray
|
| 518 |
+
-results -sql -Cursor -RowCount -END
|
| 519 |
+
}
|
| 520 |
+
|
| 521 |
+
constructor {statement args} {
|
| 522 |
+
next
|
| 523 |
+
set -db [$statement getDBhandle]
|
| 524 |
+
set -sql [$statement getSql]
|
| 525 |
+
set -columns {}
|
| 526 |
+
set -results {}
|
| 527 |
+
${-db} trace [namespace code {my RecordStatement}]
|
| 528 |
+
if {[llength $args] == 0} {
|
| 529 |
+
|
| 530 |
+
# Variable substitutions are evaluated in caller's context
|
| 531 |
+
|
| 532 |
+
uplevel 1 [list ${-db} eval ${-sql} \
|
| 533 |
+
[namespace which -variable -resultArray] \
|
| 534 |
+
[namespace code {my RecordResult}]]
|
| 535 |
+
|
| 536 |
+
} elseif {[llength $args] == 1} {
|
| 537 |
+
|
| 538 |
+
# Variable substitutions are in the dictionary at [lindex $args 0].
|
| 539 |
+
|
| 540 |
+
set -paramDict [lindex $args 0]
|
| 541 |
+
|
| 542 |
+
# At this point, the activation record must contain no variables
|
| 543 |
+
# that might be bound within the query. All variables at this point
|
| 544 |
+
# begin with hyphens so that they are syntactically incorrect
|
| 545 |
+
# as bound variables in SQL.
|
| 546 |
+
|
| 547 |
+
unset args
|
| 548 |
+
unset statement
|
| 549 |
+
|
| 550 |
+
dict with -paramDict {
|
| 551 |
+
${-db} eval ${-sql} -resultArray {
|
| 552 |
+
my RecordResult
|
| 553 |
+
}
|
| 554 |
+
}
|
| 555 |
+
|
| 556 |
+
} else {
|
| 557 |
+
|
| 558 |
+
${-db} trace {}
|
| 559 |
+
|
| 560 |
+
# Too many args
|
| 561 |
+
|
| 562 |
+
return -code error \
|
| 563 |
+
-errorcode [list TDBC GENERAL_ERROR HY000 \
|
| 564 |
+
SQLITE3 WRONGNUMARGS] \
|
| 565 |
+
"wrong # args: should be\
|
| 566 |
+
[lrange [info level 0] 0 1] statement ?dictionary?"
|
| 567 |
+
|
| 568 |
+
}
|
| 569 |
+
${-db} trace {}
|
| 570 |
+
set -Cursor 0
|
| 571 |
+
if {${-Cursor} < [llength ${-results}]
|
| 572 |
+
&& [lindex ${-results} ${-Cursor}] eq {statement}} {
|
| 573 |
+
incr -Cursor 2
|
| 574 |
+
}
|
| 575 |
+
if {${-Cursor} < [llength ${-results}]
|
| 576 |
+
&& [lindex ${-results} ${-Cursor}] eq {columns}} {
|
| 577 |
+
incr -Cursor
|
| 578 |
+
set -columns [lindex ${-results} ${-Cursor}]
|
| 579 |
+
incr -Cursor
|
| 580 |
+
}
|
| 581 |
+
set -RowCount [${-db} changes]
|
| 582 |
+
}
|
| 583 |
+
|
| 584 |
+
# Record the start of a SQL statement
|
| 585 |
+
|
| 586 |
+
method RecordStatement {stmt} {
|
| 587 |
+
set -needcolumns 1
|
| 588 |
+
lappend -results statement {}
|
| 589 |
+
}
|
| 590 |
+
|
| 591 |
+
# Record one row of results from a query by appending it as a dictionary
|
| 592 |
+
# to the 'results' list. As a side effect, set 'columns' to a list
|
| 593 |
+
# comprising the names of the columns of the result.
|
| 594 |
+
|
| 595 |
+
method RecordResult {} {
|
| 596 |
+
set columns ${-resultArray(*)}
|
| 597 |
+
if {[info exists -needcolumns]} {
|
| 598 |
+
lappend -results columns $columns
|
| 599 |
+
unset -needcolumns
|
| 600 |
+
}
|
| 601 |
+
set dict {}
|
| 602 |
+
foreach key $columns {
|
| 603 |
+
if {[set -resultArray($key)] ne "\ufffd"} {
|
| 604 |
+
dict set dict $key [set -resultArray($key)]
|
| 605 |
+
}
|
| 606 |
+
}
|
| 607 |
+
lappend -results row $dict
|
| 608 |
+
}
|
| 609 |
+
|
| 610 |
+
# Advance to the next result set
|
| 611 |
+
|
| 612 |
+
method nextresults {} {
|
| 613 |
+
set have 0
|
| 614 |
+
while {${-Cursor} < [llength ${-results}]} {
|
| 615 |
+
if {[lindex ${-results} ${-Cursor}] eq {statement}} {
|
| 616 |
+
set have 1
|
| 617 |
+
incr -Cursor 2
|
| 618 |
+
break
|
| 619 |
+
}
|
| 620 |
+
incr -Cursor 2
|
| 621 |
+
}
|
| 622 |
+
if {!$have} {
|
| 623 |
+
set -END {}
|
| 624 |
+
}
|
| 625 |
+
if {${-Cursor} >= [llength ${-results}]} {
|
| 626 |
+
set -columns {}
|
| 627 |
+
} elseif {[lindex ${-results} ${-Cursor}] eq {columns}} {
|
| 628 |
+
incr -Cursor
|
| 629 |
+
set -columns [lindex ${-results} ${-Cursor}]
|
| 630 |
+
incr -Cursor
|
| 631 |
+
} else {
|
| 632 |
+
set -columns {}
|
| 633 |
+
}
|
| 634 |
+
return $have
|
| 635 |
+
}
|
| 636 |
+
|
| 637 |
+
method getDBhandle {} {
|
| 638 |
+
return ${-db}
|
| 639 |
+
}
|
| 640 |
+
|
| 641 |
+
# Return a list of the columns
|
| 642 |
+
|
| 643 |
+
method columns {} {
|
| 644 |
+
if {[info exists -END]} {
|
| 645 |
+
return -code error \
|
| 646 |
+
-errorcode {TDBC GENERAL_ERROR HY010 SQLITE3 FUNCTIONSEQ} \
|
| 647 |
+
"Function sequence error: result set is exhausted."
|
| 648 |
+
}
|
| 649 |
+
return ${-columns}
|
| 650 |
+
}
|
| 651 |
+
|
| 652 |
+
# Return the next row of the result set as a list
|
| 653 |
+
|
| 654 |
+
method nextlist var {
|
| 655 |
+
|
| 656 |
+
upvar 1 $var row
|
| 657 |
+
|
| 658 |
+
if {[info exists -END]} {
|
| 659 |
+
return -code error \
|
| 660 |
+
-errorcode {TDBC GENERAL_ERROR HY010 SQLITE3 FUNCTIONSEQ} \
|
| 661 |
+
"Function sequence error: result set is exhausted."
|
| 662 |
+
}
|
| 663 |
+
if {${-Cursor} >= [llength ${-results}]
|
| 664 |
+
|| [lindex ${-results} ${-Cursor}] ne {row}} {
|
| 665 |
+
return 0
|
| 666 |
+
} else {
|
| 667 |
+
set row {}
|
| 668 |
+
incr -Cursor
|
| 669 |
+
set d [lindex ${-results} ${-Cursor}]
|
| 670 |
+
incr -Cursor
|
| 671 |
+
foreach key ${-columns} {
|
| 672 |
+
if {[dict exists $d $key]} {
|
| 673 |
+
lappend row [dict get $d $key]
|
| 674 |
+
} else {
|
| 675 |
+
lappend row {}
|
| 676 |
+
}
|
| 677 |
+
}
|
| 678 |
+
}
|
| 679 |
+
return 1
|
| 680 |
+
}
|
| 681 |
+
|
| 682 |
+
# Return the next row of the result set as a dict
|
| 683 |
+
|
| 684 |
+
method nextdict var {
|
| 685 |
+
|
| 686 |
+
upvar 1 $var row
|
| 687 |
+
|
| 688 |
+
if {[info exists -END]} {
|
| 689 |
+
return -code error \
|
| 690 |
+
-errorcode {TDBC GENERAL_ERROR HY010 SQLITE3 FUNCTIONSEQ} \
|
| 691 |
+
"Function sequence error: result set is exhausted."
|
| 692 |
+
}
|
| 693 |
+
if {${-Cursor} >= [llength ${-results}]
|
| 694 |
+
|| [lindex ${-results} ${-Cursor}] ne {row}} {
|
| 695 |
+
return 0
|
| 696 |
+
} else {
|
| 697 |
+
incr -Cursor
|
| 698 |
+
set row [lindex ${-results} ${-Cursor}]
|
| 699 |
+
incr -Cursor
|
| 700 |
+
}
|
| 701 |
+
return 1
|
| 702 |
+
}
|
| 703 |
+
|
| 704 |
+
# Return the number of rows affected by a statement
|
| 705 |
+
|
| 706 |
+
method rowcount {} {
|
| 707 |
+
if {[info exists -END]} {
|
| 708 |
+
return -code error \
|
| 709 |
+
-errorcode {TDBC GENERAL_ERROR HY010 SQLITE3 FUNCTIONSEQ} \
|
| 710 |
+
"Function sequence error: result set is exhausted."
|
| 711 |
+
}
|
| 712 |
+
return ${-RowCount}
|
| 713 |
+
}
|
| 714 |
+
|
| 715 |
+
}
|
my_container_sandbox/workspace/anaconda3/lib/tk8.6/demos/images/earth.gif
ADDED
|
my_container_sandbox/workspace/anaconda3/lib/tk8.6/demos/images/earthmenu.png
ADDED
|
my_container_sandbox/workspace/anaconda3/lib/tk8.6/demos/images/earthris.gif
ADDED
|
my_container_sandbox/workspace/anaconda3/lib/tk8.6/demos/images/flagdown.xbm
ADDED
|
|
my_container_sandbox/workspace/anaconda3/lib/tk8.6/demos/images/flagup.xbm
ADDED
|
|
my_container_sandbox/workspace/anaconda3/lib/tk8.6/demos/images/gray25.xbm
ADDED
|
|
my_container_sandbox/workspace/anaconda3/lib/tk8.6/demos/images/letters.xbm
ADDED
|
|
my_container_sandbox/workspace/anaconda3/lib/tk8.6/demos/images/noletter.xbm
ADDED
|
|
my_container_sandbox/workspace/anaconda3/lib/tk8.6/demos/images/ouster.png
ADDED
|
my_container_sandbox/workspace/anaconda3/lib/tk8.6/demos/images/pattern.xbm
ADDED
|
|
my_container_sandbox/workspace/anaconda3/lib/tk8.6/demos/images/tcllogo.gif
ADDED
|
my_container_sandbox/workspace/anaconda3/lib/tk8.6/images/README
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
README - images directory
|
| 2 |
+
|
| 3 |
+
This directory includes images for the Tcl Logo and the Tcl Powered
|
| 4 |
+
Logo. Please feel free to use the Tcl Powered Logo on any of your
|
| 5 |
+
products that employ the use of Tcl or Tk. The Tcl logo may also be
|
| 6 |
+
used to promote Tcl in your product documentation, web site or other
|
| 7 |
+
places you so desire.
|
my_container_sandbox/workspace/anaconda3/lib/tk8.6/images/logo.eps
ADDED
|
@@ -0,0 +1,2091 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
%!PS-Adobe-3.0 EPSF-3.0
|
| 2 |
+
%%Creator: Adobe Illustrator(TM) 5.5
|
| 3 |
+
%%For: (Bud Northern) (Mark Anderson Design)
|
| 4 |
+
%%Title: (TCL/TK LOGO.ILLUS)
|
| 5 |
+
%%CreationDate: (8/1/96) (4:58 PM)
|
| 6 |
+
%%BoundingBox: 251 331 371 512
|
| 7 |
+
%%HiResBoundingBox: 251.3386 331.5616 370.5213 511.775
|
| 8 |
+
%%DocumentProcessColors: Cyan Magenta Yellow
|
| 9 |
+
%%DocumentSuppliedResources: procset Adobe_level2_AI5 1.0 0
|
| 10 |
+
%%+ procset Adobe_IllustratorA_AI5 1.0 0
|
| 11 |
+
%AI5_FileFormat 1.2
|
| 12 |
+
%AI3_ColorUsage: Color
|
| 13 |
+
%%DocumentCustomColors: (TCL RED)
|
| 14 |
+
%%CMYKCustomColor: 0 0.45 1 0 (Orange)
|
| 15 |
+
%%+ 0 0.25 1 0 (Orange Yellow)
|
| 16 |
+
%%+ 0 0.79 0.91 0 (TCL RED)
|
| 17 |
+
%AI3_TemplateBox: 306 396 306 396
|
| 18 |
+
%AI3_TileBox: 12 12 600 780
|
| 19 |
+
%AI3_DocumentPreview: Macintosh_ColorPic
|
| 20 |
+
%AI5_ArtSize: 612 792
|
| 21 |
+
%AI5_RulerUnits: 0
|
| 22 |
+
%AI5_ArtFlags: 1 0 0 1 0 0 1 1 0
|
| 23 |
+
%AI5_TargetResolution: 800
|
| 24 |
+
%AI5_NumLayers: 1
|
| 25 |
+
%AI5_OpenToView: 90 576 2 938 673 18 1 1 2 40
|
| 26 |
+
%AI5_OpenViewLayers: 7
|
| 27 |
+
%%EndComments
|
| 28 |
+
%%BeginProlog
|
| 29 |
+
%%BeginResource: procset Adobe_level2_AI5 1.0 0
|
| 30 |
+
%%Title: (Adobe Illustrator (R) Version 5.0 Level 2 Emulation)
|
| 31 |
+
%%Version: 1.0
|
| 32 |
+
%%CreationDate: (04/10/93) ()
|
| 33 |
+
%%Copyright: ((C) 1987-1993 Adobe Systems Incorporated All Rights Reserved)
|
| 34 |
+
userdict /Adobe_level2_AI5 21 dict dup begin
|
| 35 |
+
put
|
| 36 |
+
/packedarray where not
|
| 37 |
+
{
|
| 38 |
+
userdict begin
|
| 39 |
+
/packedarray
|
| 40 |
+
{
|
| 41 |
+
array astore readonly
|
| 42 |
+
} bind def
|
| 43 |
+
/setpacking /pop load def
|
| 44 |
+
/currentpacking false def
|
| 45 |
+
end
|
| 46 |
+
0
|
| 47 |
+
} if
|
| 48 |
+
pop
|
| 49 |
+
userdict /defaultpacking currentpacking put true setpacking
|
| 50 |
+
/initialize
|
| 51 |
+
{
|
| 52 |
+
Adobe_level2_AI5 begin
|
| 53 |
+
} bind def
|
| 54 |
+
/terminate
|
| 55 |
+
{
|
| 56 |
+
currentdict Adobe_level2_AI5 eq
|
| 57 |
+
{
|
| 58 |
+
end
|
| 59 |
+
} if
|
| 60 |
+
} bind def
|
| 61 |
+
mark
|
| 62 |
+
/setcustomcolor where not
|
| 63 |
+
{
|
| 64 |
+
/findcmykcustomcolor
|
| 65 |
+
{
|
| 66 |
+
5 packedarray
|
| 67 |
+
} bind def
|
| 68 |
+
/setcustomcolor
|
| 69 |
+
{
|
| 70 |
+
exch aload pop pop
|
| 71 |
+
4
|
| 72 |
+
{
|
| 73 |
+
4 index mul 4 1 roll
|
| 74 |
+
} repeat
|
| 75 |
+
5 -1 roll pop
|
| 76 |
+
setcmykcolor
|
| 77 |
+
}
|
| 78 |
+
def
|
| 79 |
+
} if
|
| 80 |
+
|
| 81 |
+
/gt38? mark {version cvx exec} stopped {cleartomark true} {38 gt exch pop} ifelse def
|
| 82 |
+
userdict /deviceDPI 72 0 matrix defaultmatrix dtransform dup mul exch dup mul add sqrt put
|
| 83 |
+
userdict /level2?
|
| 84 |
+
systemdict /languagelevel known dup
|
| 85 |
+
{
|
| 86 |
+
pop systemdict /languagelevel get 2 ge
|
| 87 |
+
} if
|
| 88 |
+
put
|
| 89 |
+
level2? not
|
| 90 |
+
{
|
| 91 |
+
/setcmykcolor where not
|
| 92 |
+
{
|
| 93 |
+
/setcmykcolor
|
| 94 |
+
{
|
| 95 |
+
exch .11 mul add exch .59 mul add exch .3 mul add
|
| 96 |
+
1 exch sub setgray
|
| 97 |
+
} def
|
| 98 |
+
} if
|
| 99 |
+
/currentcmykcolor where not
|
| 100 |
+
{
|
| 101 |
+
/currentcmykcolor
|
| 102 |
+
{
|
| 103 |
+
0 0 0 1 currentgray sub
|
| 104 |
+
} def
|
| 105 |
+
} if
|
| 106 |
+
/setoverprint where not
|
| 107 |
+
{
|
| 108 |
+
/setoverprint /pop load def
|
| 109 |
+
} if
|
| 110 |
+
/selectfont where not
|
| 111 |
+
{
|
| 112 |
+
/selectfont
|
| 113 |
+
{
|
| 114 |
+
exch findfont exch
|
| 115 |
+
dup type /arraytype eq
|
| 116 |
+
{
|
| 117 |
+
makefont
|
| 118 |
+
}
|
| 119 |
+
{
|
| 120 |
+
scalefont
|
| 121 |
+
} ifelse
|
| 122 |
+
setfont
|
| 123 |
+
} bind def
|
| 124 |
+
} if
|
| 125 |
+
/cshow where not
|
| 126 |
+
{
|
| 127 |
+
/cshow
|
| 128 |
+
{
|
| 129 |
+
[
|
| 130 |
+
0 0 5 -1 roll aload pop
|
| 131 |
+
] cvx bind forall
|
| 132 |
+
} bind def
|
| 133 |
+
} if
|
| 134 |
+
} if
|
| 135 |
+
cleartomark
|
| 136 |
+
/anyColor?
|
| 137 |
+
{
|
| 138 |
+
add add add 0 ne
|
| 139 |
+
} bind def
|
| 140 |
+
/testColor
|
| 141 |
+
{
|
| 142 |
+
gsave
|
| 143 |
+
setcmykcolor currentcmykcolor
|
| 144 |
+
grestore
|
| 145 |
+
} bind def
|
| 146 |
+
/testCMYKColorThrough
|
| 147 |
+
{
|
| 148 |
+
testColor anyColor?
|
| 149 |
+
} bind def
|
| 150 |
+
userdict /composite?
|
| 151 |
+
level2?
|
| 152 |
+
{
|
| 153 |
+
gsave 1 1 1 1 setcmykcolor currentcmykcolor grestore
|
| 154 |
+
add add add 4 eq
|
| 155 |
+
}
|
| 156 |
+
{
|
| 157 |
+
1 0 0 0 testCMYKColorThrough
|
| 158 |
+
0 1 0 0 testCMYKColorThrough
|
| 159 |
+
0 0 1 0 testCMYKColorThrough
|
| 160 |
+
0 0 0 1 testCMYKColorThrough
|
| 161 |
+
and and and
|
| 162 |
+
} ifelse
|
| 163 |
+
put
|
| 164 |
+
composite? not
|
| 165 |
+
{
|
| 166 |
+
userdict begin
|
| 167 |
+
gsave
|
| 168 |
+
/cyan? 1 0 0 0 testCMYKColorThrough def
|
| 169 |
+
/magenta? 0 1 0 0 testCMYKColorThrough def
|
| 170 |
+
/yellow? 0 0 1 0 testCMYKColorThrough def
|
| 171 |
+
/black? 0 0 0 1 testCMYKColorThrough def
|
| 172 |
+
grestore
|
| 173 |
+
/isCMYKSep? cyan? magenta? yellow? black? or or or def
|
| 174 |
+
/customColor? isCMYKSep? not def
|
| 175 |
+
end
|
| 176 |
+
} if
|
| 177 |
+
end defaultpacking setpacking
|
| 178 |
+
%%EndResource
|
| 179 |
+
%%BeginResource: procset Adobe_IllustratorA_AI5 1.1 0
|
| 180 |
+
%%Title: (Adobe Illustrator (R) Version 5.0 Abbreviated Prolog)
|
| 181 |
+
%%Version: 1.1
|
| 182 |
+
%%CreationDate: (3/7/1994) ()
|
| 183 |
+
%%Copyright: ((C) 1987-1994 Adobe Systems Incorporated All Rights Reserved)
|
| 184 |
+
currentpacking true setpacking
|
| 185 |
+
userdict /Adobe_IllustratorA_AI5_vars 70 dict dup begin
|
| 186 |
+
put
|
| 187 |
+
/_lp /none def
|
| 188 |
+
/_pf
|
| 189 |
+
{
|
| 190 |
+
} def
|
| 191 |
+
/_ps
|
| 192 |
+
{
|
| 193 |
+
} def
|
| 194 |
+
/_psf
|
| 195 |
+
{
|
| 196 |
+
} def
|
| 197 |
+
/_pss
|
| 198 |
+
{
|
| 199 |
+
} def
|
| 200 |
+
/_pjsf
|
| 201 |
+
{
|
| 202 |
+
} def
|
| 203 |
+
/_pjss
|
| 204 |
+
{
|
| 205 |
+
} def
|
| 206 |
+
/_pola 0 def
|
| 207 |
+
/_doClip 0 def
|
| 208 |
+
/cf currentflat def
|
| 209 |
+
/_tm matrix def
|
| 210 |
+
/_renderStart
|
| 211 |
+
[
|
| 212 |
+
/e0 /r0 /a0 /o0 /e1 /r1 /a1 /i0
|
| 213 |
+
] def
|
| 214 |
+
/_renderEnd
|
| 215 |
+
[
|
| 216 |
+
null null null null /i1 /i1 /i1 /i1
|
| 217 |
+
] def
|
| 218 |
+
/_render -1 def
|
| 219 |
+
/_rise 0 def
|
| 220 |
+
/_ax 0 def
|
| 221 |
+
/_ay 0 def
|
| 222 |
+
/_cx 0 def
|
| 223 |
+
/_cy 0 def
|
| 224 |
+
/_leading
|
| 225 |
+
[
|
| 226 |
+
0 0
|
| 227 |
+
] def
|
| 228 |
+
/_ctm matrix def
|
| 229 |
+
/_mtx matrix def
|
| 230 |
+
/_sp 16#020 def
|
| 231 |
+
/_hyphen (-) def
|
| 232 |
+
/_fScl 0 def
|
| 233 |
+
/_cnt 0 def
|
| 234 |
+
/_hs 1 def
|
| 235 |
+
/_nativeEncoding 0 def
|
| 236 |
+
/_useNativeEncoding 0 def
|
| 237 |
+
/_tempEncode 0 def
|
| 238 |
+
/_pntr 0 def
|
| 239 |
+
/_tDict 2 dict def
|
| 240 |
+
/_wv 0 def
|
| 241 |
+
/Tx
|
| 242 |
+
{
|
| 243 |
+
} def
|
| 244 |
+
/Tj
|
| 245 |
+
{
|
| 246 |
+
} def
|
| 247 |
+
/CRender
|
| 248 |
+
{
|
| 249 |
+
} def
|
| 250 |
+
/_AI3_savepage
|
| 251 |
+
{
|
| 252 |
+
} def
|
| 253 |
+
/_gf null def
|
| 254 |
+
/_cf 4 array def
|
| 255 |
+
/_if null def
|
| 256 |
+
/_of false def
|
| 257 |
+
/_fc
|
| 258 |
+
{
|
| 259 |
+
} def
|
| 260 |
+
/_gs null def
|
| 261 |
+
/_cs 4 array def
|
| 262 |
+
/_is null def
|
| 263 |
+
/_os false def
|
| 264 |
+
/_sc
|
| 265 |
+
{
|
| 266 |
+
} def
|
| 267 |
+
/discardSave null def
|
| 268 |
+
/buffer 256 string def
|
| 269 |
+
/beginString null def
|
| 270 |
+
/endString null def
|
| 271 |
+
/endStringLength null def
|
| 272 |
+
/layerCnt 1 def
|
| 273 |
+
/layerCount 1 def
|
| 274 |
+
/perCent (%) 0 get def
|
| 275 |
+
/perCentSeen? false def
|
| 276 |
+
/newBuff null def
|
| 277 |
+
/newBuffButFirst null def
|
| 278 |
+
/newBuffLast null def
|
| 279 |
+
/clipForward? false def
|
| 280 |
+
end
|
| 281 |
+
userdict /Adobe_IllustratorA_AI5 74 dict dup begin
|
| 282 |
+
put
|
| 283 |
+
/initialize
|
| 284 |
+
{
|
| 285 |
+
Adobe_IllustratorA_AI5 dup begin
|
| 286 |
+
Adobe_IllustratorA_AI5_vars begin
|
| 287 |
+
discardDict
|
| 288 |
+
{
|
| 289 |
+
bind pop pop
|
| 290 |
+
} forall
|
| 291 |
+
dup /nc get begin
|
| 292 |
+
{
|
| 293 |
+
dup xcheck 1 index type /operatortype ne and
|
| 294 |
+
{
|
| 295 |
+
bind
|
| 296 |
+
} if
|
| 297 |
+
pop pop
|
| 298 |
+
} forall
|
| 299 |
+
end
|
| 300 |
+
newpath
|
| 301 |
+
} def
|
| 302 |
+
/terminate
|
| 303 |
+
{
|
| 304 |
+
end
|
| 305 |
+
end
|
| 306 |
+
} def
|
| 307 |
+
/_
|
| 308 |
+
null def
|
| 309 |
+
/ddef
|
| 310 |
+
{
|
| 311 |
+
Adobe_IllustratorA_AI5_vars 3 1 roll put
|
| 312 |
+
} def
|
| 313 |
+
/xput
|
| 314 |
+
{
|
| 315 |
+
dup load dup length exch maxlength eq
|
| 316 |
+
{
|
| 317 |
+
dup dup load dup
|
| 318 |
+
length 2 mul dict copy def
|
| 319 |
+
} if
|
| 320 |
+
load begin
|
| 321 |
+
def
|
| 322 |
+
end
|
| 323 |
+
} def
|
| 324 |
+
/npop
|
| 325 |
+
{
|
| 326 |
+
{
|
| 327 |
+
pop
|
| 328 |
+
} repeat
|
| 329 |
+
} def
|
| 330 |
+
/sw
|
| 331 |
+
{
|
| 332 |
+
dup length exch stringwidth
|
| 333 |
+
exch 5 -1 roll 3 index mul add
|
| 334 |
+
4 1 roll 3 1 roll mul add
|
| 335 |
+
} def
|
| 336 |
+
/swj
|
| 337 |
+
{
|
| 338 |
+
dup 4 1 roll
|
| 339 |
+
dup length exch stringwidth
|
| 340 |
+
exch 5 -1 roll 3 index mul add
|
| 341 |
+
4 1 roll 3 1 roll mul add
|
| 342 |
+
6 2 roll /_cnt 0 ddef
|
| 343 |
+
{
|
| 344 |
+
1 index eq
|
| 345 |
+
{
|
| 346 |
+
/_cnt _cnt 1 add ddef
|
| 347 |
+
} if
|
| 348 |
+
} forall
|
| 349 |
+
pop
|
| 350 |
+
exch _cnt mul exch _cnt mul 2 index add 4 1 roll 2 index add 4 1 roll pop pop
|
| 351 |
+
} def
|
| 352 |
+
/ss
|
| 353 |
+
{
|
| 354 |
+
4 1 roll
|
| 355 |
+
{
|
| 356 |
+
2 npop
|
| 357 |
+
(0) exch 2 copy 0 exch put pop
|
| 358 |
+
gsave
|
| 359 |
+
false charpath currentpoint
|
| 360 |
+
4 index setmatrix
|
| 361 |
+
stroke
|
| 362 |
+
grestore
|
| 363 |
+
moveto
|
| 364 |
+
2 copy rmoveto
|
| 365 |
+
} exch cshow
|
| 366 |
+
3 npop
|
| 367 |
+
} def
|
| 368 |
+
/jss
|
| 369 |
+
{
|
| 370 |
+
4 1 roll
|
| 371 |
+
{
|
| 372 |
+
2 npop
|
| 373 |
+
(0) exch 2 copy 0 exch put
|
| 374 |
+
gsave
|
| 375 |
+
_sp eq
|
| 376 |
+
{
|
| 377 |
+
exch 6 index 6 index 6 index 5 -1 roll widthshow
|
| 378 |
+
currentpoint
|
| 379 |
+
}
|
| 380 |
+
{
|
| 381 |
+
false charpath currentpoint
|
| 382 |
+
4 index setmatrix stroke
|
| 383 |
+
} ifelse
|
| 384 |
+
grestore
|
| 385 |
+
moveto
|
| 386 |
+
2 copy rmoveto
|
| 387 |
+
} exch cshow
|
| 388 |
+
6 npop
|
| 389 |
+
} def
|
| 390 |
+
/sp
|
| 391 |
+
{
|
| 392 |
+
{
|
| 393 |
+
2 npop (0) exch
|
| 394 |
+
2 copy 0 exch put pop
|
| 395 |
+
false charpath
|
| 396 |
+
2 copy rmoveto
|
| 397 |
+
} exch cshow
|
| 398 |
+
2 npop
|
| 399 |
+
} def
|
| 400 |
+
/jsp
|
| 401 |
+
{
|
| 402 |
+
{
|
| 403 |
+
2 npop
|
| 404 |
+
(0) exch 2 copy 0 exch put
|
| 405 |
+
_sp eq
|
| 406 |
+
{
|
| 407 |
+
exch 5 index 5 index 5 index 5 -1 roll widthshow
|
| 408 |
+
}
|
| 409 |
+
{
|
| 410 |
+
false charpath
|
| 411 |
+
} ifelse
|
| 412 |
+
2 copy rmoveto
|
| 413 |
+
} exch cshow
|
| 414 |
+
5 npop
|
| 415 |
+
} def
|
| 416 |
+
/pl
|
| 417 |
+
{
|
| 418 |
+
transform
|
| 419 |
+
0.25 sub round 0.25 add exch
|
| 420 |
+
0.25 sub round 0.25 add exch
|
| 421 |
+
itransform
|
| 422 |
+
} def
|
| 423 |
+
/setstrokeadjust where
|
| 424 |
+
{
|
| 425 |
+
pop true setstrokeadjust
|
| 426 |
+
/c
|
| 427 |
+
{
|
| 428 |
+
curveto
|
| 429 |
+
} def
|
| 430 |
+
/C
|
| 431 |
+
/c load def
|
| 432 |
+
/v
|
| 433 |
+
{
|
| 434 |
+
currentpoint 6 2 roll curveto
|
| 435 |
+
} def
|
| 436 |
+
/V
|
| 437 |
+
/v load def
|
| 438 |
+
/y
|
| 439 |
+
{
|
| 440 |
+
2 copy curveto
|
| 441 |
+
} def
|
| 442 |
+
/Y
|
| 443 |
+
/y load def
|
| 444 |
+
/l
|
| 445 |
+
{
|
| 446 |
+
lineto
|
| 447 |
+
} def
|
| 448 |
+
/L
|
| 449 |
+
/l load def
|
| 450 |
+
/m
|
| 451 |
+
{
|
| 452 |
+
moveto
|
| 453 |
+
} def
|
| 454 |
+
}
|
| 455 |
+
{
|
| 456 |
+
/c
|
| 457 |
+
{
|
| 458 |
+
pl curveto
|
| 459 |
+
} def
|
| 460 |
+
/C
|
| 461 |
+
/c load def
|
| 462 |
+
/v
|
| 463 |
+
{
|
| 464 |
+
currentpoint 6 2 roll pl curveto
|
| 465 |
+
} def
|
| 466 |
+
/V
|
| 467 |
+
/v load def
|
| 468 |
+
/y
|
| 469 |
+
{
|
| 470 |
+
pl 2 copy curveto
|
| 471 |
+
} def
|
| 472 |
+
/Y
|
| 473 |
+
/y load def
|
| 474 |
+
/l
|
| 475 |
+
{
|
| 476 |
+
pl lineto
|
| 477 |
+
} def
|
| 478 |
+
/L
|
| 479 |
+
/l load def
|
| 480 |
+
/m
|
| 481 |
+
{
|
| 482 |
+
pl moveto
|
| 483 |
+
} def
|
| 484 |
+
} ifelse
|
| 485 |
+
/d
|
| 486 |
+
{
|
| 487 |
+
setdash
|
| 488 |
+
} def
|
| 489 |
+
/cf
|
| 490 |
+
{
|
| 491 |
+
} def
|
| 492 |
+
/i
|
| 493 |
+
{
|
| 494 |
+
dup 0 eq
|
| 495 |
+
{
|
| 496 |
+
pop cf
|
| 497 |
+
} if
|
| 498 |
+
setflat
|
| 499 |
+
} def
|
| 500 |
+
/j
|
| 501 |
+
{
|
| 502 |
+
setlinejoin
|
| 503 |
+
} def
|
| 504 |
+
/J
|
| 505 |
+
{
|
| 506 |
+
setlinecap
|
| 507 |
+
} def
|
| 508 |
+
/M
|
| 509 |
+
{
|
| 510 |
+
setmiterlimit
|
| 511 |
+
} def
|
| 512 |
+
/w
|
| 513 |
+
{
|
| 514 |
+
setlinewidth
|
| 515 |
+
} def
|
| 516 |
+
/H
|
| 517 |
+
{
|
| 518 |
+
} def
|
| 519 |
+
/h
|
| 520 |
+
{
|
| 521 |
+
closepath
|
| 522 |
+
} def
|
| 523 |
+
/N
|
| 524 |
+
{
|
| 525 |
+
_pola 0 eq
|
| 526 |
+
{
|
| 527 |
+
_doClip 1 eq
|
| 528 |
+
{
|
| 529 |
+
clip /_doClip 0 ddef
|
| 530 |
+
} if
|
| 531 |
+
newpath
|
| 532 |
+
}
|
| 533 |
+
{
|
| 534 |
+
/CRender
|
| 535 |
+
{
|
| 536 |
+
N
|
| 537 |
+
} ddef
|
| 538 |
+
} ifelse
|
| 539 |
+
} def
|
| 540 |
+
/n
|
| 541 |
+
{
|
| 542 |
+
N
|
| 543 |
+
} def
|
| 544 |
+
/F
|
| 545 |
+
{
|
| 546 |
+
_pola 0 eq
|
| 547 |
+
{
|
| 548 |
+
_doClip 1 eq
|
| 549 |
+
{
|
| 550 |
+
gsave _pf grestore clip newpath /_lp /none ddef _fc
|
| 551 |
+
/_doClip 0 ddef
|
| 552 |
+
}
|
| 553 |
+
{
|
| 554 |
+
_pf
|
| 555 |
+
} ifelse
|
| 556 |
+
}
|
| 557 |
+
{
|
| 558 |
+
/CRender
|
| 559 |
+
{
|
| 560 |
+
F
|
| 561 |
+
} ddef
|
| 562 |
+
} ifelse
|
| 563 |
+
} def
|
| 564 |
+
/f
|
| 565 |
+
{
|
| 566 |
+
closepath
|
| 567 |
+
F
|
| 568 |
+
} def
|
| 569 |
+
/S
|
| 570 |
+
{
|
| 571 |
+
_pola 0 eq
|
| 572 |
+
{
|
| 573 |
+
_doClip 1 eq
|
| 574 |
+
{
|
| 575 |
+
gsave _ps grestore clip newpath /_lp /none ddef _sc
|
| 576 |
+
/_doClip 0 ddef
|
| 577 |
+
}
|
| 578 |
+
{
|
| 579 |
+
_ps
|
| 580 |
+
} ifelse
|
| 581 |
+
}
|
| 582 |
+
{
|
| 583 |
+
/CRender
|
| 584 |
+
{
|
| 585 |
+
S
|
| 586 |
+
} ddef
|
| 587 |
+
} ifelse
|
| 588 |
+
} def
|
| 589 |
+
/s
|
| 590 |
+
{
|
| 591 |
+
closepath
|
| 592 |
+
S
|
| 593 |
+
} def
|
| 594 |
+
/B
|
| 595 |
+
{
|
| 596 |
+
_pola 0 eq
|
| 597 |
+
{
|
| 598 |
+
_doClip 1 eq
|
| 599 |
+
gsave F grestore
|
| 600 |
+
{
|
| 601 |
+
gsave S grestore clip newpath /_lp /none ddef _sc
|
| 602 |
+
/_doClip 0 ddef
|
| 603 |
+
}
|
| 604 |
+
{
|
| 605 |
+
S
|
| 606 |
+
} ifelse
|
| 607 |
+
}
|
| 608 |
+
{
|
| 609 |
+
/CRender
|
| 610 |
+
{
|
| 611 |
+
B
|
| 612 |
+
} ddef
|
| 613 |
+
} ifelse
|
| 614 |
+
} def
|
| 615 |
+
/b
|
| 616 |
+
{
|
| 617 |
+
closepath
|
| 618 |
+
B
|
| 619 |
+
} def
|
| 620 |
+
/W
|
| 621 |
+
{
|
| 622 |
+
/_doClip 1 ddef
|
| 623 |
+
} def
|
| 624 |
+
/*
|
| 625 |
+
{
|
| 626 |
+
count 0 ne
|
| 627 |
+
{
|
| 628 |
+
dup type /stringtype eq
|
| 629 |
+
{
|
| 630 |
+
pop
|
| 631 |
+
} if
|
| 632 |
+
} if
|
| 633 |
+
newpath
|
| 634 |
+
} def
|
| 635 |
+
/u
|
| 636 |
+
{
|
| 637 |
+
} def
|
| 638 |
+
/U
|
| 639 |
+
{
|
| 640 |
+
} def
|
| 641 |
+
/q
|
| 642 |
+
{
|
| 643 |
+
_pola 0 eq
|
| 644 |
+
{
|
| 645 |
+
gsave
|
| 646 |
+
} if
|
| 647 |
+
} def
|
| 648 |
+
/Q
|
| 649 |
+
{
|
| 650 |
+
_pola 0 eq
|
| 651 |
+
{
|
| 652 |
+
grestore
|
| 653 |
+
} if
|
| 654 |
+
} def
|
| 655 |
+
/*u
|
| 656 |
+
{
|
| 657 |
+
_pola 1 add /_pola exch ddef
|
| 658 |
+
} def
|
| 659 |
+
/*U
|
| 660 |
+
{
|
| 661 |
+
_pola 1 sub /_pola exch ddef
|
| 662 |
+
_pola 0 eq
|
| 663 |
+
{
|
| 664 |
+
CRender
|
| 665 |
+
} if
|
| 666 |
+
} def
|
| 667 |
+
/D
|
| 668 |
+
{
|
| 669 |
+
pop
|
| 670 |
+
} def
|
| 671 |
+
/*w
|
| 672 |
+
{
|
| 673 |
+
} def
|
| 674 |
+
/*W
|
| 675 |
+
{
|
| 676 |
+
} def
|
| 677 |
+
/`
|
| 678 |
+
{
|
| 679 |
+
/_i save ddef
|
| 680 |
+
clipForward?
|
| 681 |
+
{
|
| 682 |
+
nulldevice
|
| 683 |
+
} if
|
| 684 |
+
6 1 roll 4 npop
|
| 685 |
+
concat pop
|
| 686 |
+
userdict begin
|
| 687 |
+
/showpage
|
| 688 |
+
{
|
| 689 |
+
} def
|
| 690 |
+
0 setgray
|
| 691 |
+
0 setlinecap
|
| 692 |
+
1 setlinewidth
|
| 693 |
+
0 setlinejoin
|
| 694 |
+
10 setmiterlimit
|
| 695 |
+
[] 0 setdash
|
| 696 |
+
/setstrokeadjust where {pop false setstrokeadjust} if
|
| 697 |
+
newpath
|
| 698 |
+
0 setgray
|
| 699 |
+
false setoverprint
|
| 700 |
+
} def
|
| 701 |
+
/~
|
| 702 |
+
{
|
| 703 |
+
end
|
| 704 |
+
_i restore
|
| 705 |
+
} def
|
| 706 |
+
/O
|
| 707 |
+
{
|
| 708 |
+
0 ne
|
| 709 |
+
/_of exch ddef
|
| 710 |
+
/_lp /none ddef
|
| 711 |
+
} def
|
| 712 |
+
/R
|
| 713 |
+
{
|
| 714 |
+
0 ne
|
| 715 |
+
/_os exch ddef
|
| 716 |
+
/_lp /none ddef
|
| 717 |
+
} def
|
| 718 |
+
/g
|
| 719 |
+
{
|
| 720 |
+
/_gf exch ddef
|
| 721 |
+
/_fc
|
| 722 |
+
{
|
| 723 |
+
_lp /fill ne
|
| 724 |
+
{
|
| 725 |
+
_of setoverprint
|
| 726 |
+
_gf setgray
|
| 727 |
+
/_lp /fill ddef
|
| 728 |
+
} if
|
| 729 |
+
} ddef
|
| 730 |
+
/_pf
|
| 731 |
+
{
|
| 732 |
+
_fc
|
| 733 |
+
fill
|
| 734 |
+
} ddef
|
| 735 |
+
/_psf
|
| 736 |
+
{
|
| 737 |
+
_fc
|
| 738 |
+
ashow
|
| 739 |
+
} ddef
|
| 740 |
+
/_pjsf
|
| 741 |
+
{
|
| 742 |
+
_fc
|
| 743 |
+
awidthshow
|
| 744 |
+
} ddef
|
| 745 |
+
/_lp /none ddef
|
| 746 |
+
} def
|
| 747 |
+
/G
|
| 748 |
+
{
|
| 749 |
+
/_gs exch ddef
|
| 750 |
+
/_sc
|
| 751 |
+
{
|
| 752 |
+
_lp /stroke ne
|
| 753 |
+
{
|
| 754 |
+
_os setoverprint
|
| 755 |
+
_gs setgray
|
| 756 |
+
/_lp /stroke ddef
|
| 757 |
+
} if
|
| 758 |
+
} ddef
|
| 759 |
+
/_ps
|
| 760 |
+
{
|
| 761 |
+
_sc
|
| 762 |
+
stroke
|
| 763 |
+
} ddef
|
| 764 |
+
/_pss
|
| 765 |
+
{
|
| 766 |
+
_sc
|
| 767 |
+
ss
|
| 768 |
+
} ddef
|
| 769 |
+
/_pjss
|
| 770 |
+
{
|
| 771 |
+
_sc
|
| 772 |
+
jss
|
| 773 |
+
} ddef
|
| 774 |
+
/_lp /none ddef
|
| 775 |
+
} def
|
| 776 |
+
/k
|
| 777 |
+
{
|
| 778 |
+
_cf astore pop
|
| 779 |
+
/_fc
|
| 780 |
+
{
|
| 781 |
+
_lp /fill ne
|
| 782 |
+
{
|
| 783 |
+
_of setoverprint
|
| 784 |
+
_cf aload pop setcmykcolor
|
| 785 |
+
/_lp /fill ddef
|
| 786 |
+
} if
|
| 787 |
+
} ddef
|
| 788 |
+
/_pf
|
| 789 |
+
{
|
| 790 |
+
_fc
|
| 791 |
+
fill
|
| 792 |
+
} ddef
|
| 793 |
+
/_psf
|
| 794 |
+
{
|
| 795 |
+
_fc
|
| 796 |
+
ashow
|
| 797 |
+
} ddef
|
| 798 |
+
/_pjsf
|
| 799 |
+
{
|
| 800 |
+
_fc
|
| 801 |
+
awidthshow
|
| 802 |
+
} ddef
|
| 803 |
+
/_lp /none ddef
|
| 804 |
+
} def
|
| 805 |
+
/K
|
| 806 |
+
{
|
| 807 |
+
_cs astore pop
|
| 808 |
+
/_sc
|
| 809 |
+
{
|
| 810 |
+
_lp /stroke ne
|
| 811 |
+
{
|
| 812 |
+
_os setoverprint
|
| 813 |
+
_cs aload pop setcmykcolor
|
| 814 |
+
/_lp /stroke ddef
|
| 815 |
+
} if
|
| 816 |
+
} ddef
|
| 817 |
+
/_ps
|
| 818 |
+
{
|
| 819 |
+
_sc
|
| 820 |
+
stroke
|
| 821 |
+
} ddef
|
| 822 |
+
/_pss
|
| 823 |
+
{
|
| 824 |
+
_sc
|
| 825 |
+
ss
|
| 826 |
+
} ddef
|
| 827 |
+
/_pjss
|
| 828 |
+
{
|
| 829 |
+
_sc
|
| 830 |
+
jss
|
| 831 |
+
} ddef
|
| 832 |
+
/_lp /none ddef
|
| 833 |
+
} def
|
| 834 |
+
/x
|
| 835 |
+
{
|
| 836 |
+
/_gf exch ddef
|
| 837 |
+
findcmykcustomcolor
|
| 838 |
+
/_if exch ddef
|
| 839 |
+
/_fc
|
| 840 |
+
{
|
| 841 |
+
_lp /fill ne
|
| 842 |
+
{
|
| 843 |
+
_of setoverprint
|
| 844 |
+
_if _gf 1 exch sub setcustomcolor
|
| 845 |
+
/_lp /fill ddef
|
| 846 |
+
} if
|
| 847 |
+
} ddef
|
| 848 |
+
/_pf
|
| 849 |
+
{
|
| 850 |
+
_fc
|
| 851 |
+
fill
|
| 852 |
+
} ddef
|
| 853 |
+
/_psf
|
| 854 |
+
{
|
| 855 |
+
_fc
|
| 856 |
+
ashow
|
| 857 |
+
} ddef
|
| 858 |
+
/_pjsf
|
| 859 |
+
{
|
| 860 |
+
_fc
|
| 861 |
+
awidthshow
|
| 862 |
+
} ddef
|
| 863 |
+
/_lp /none ddef
|
| 864 |
+
} def
|
| 865 |
+
/X
|
| 866 |
+
{
|
| 867 |
+
/_gs exch ddef
|
| 868 |
+
findcmykcustomcolor
|
| 869 |
+
/_is exch ddef
|
| 870 |
+
/_sc
|
| 871 |
+
{
|
| 872 |
+
_lp /stroke ne
|
| 873 |
+
{
|
| 874 |
+
_os setoverprint
|
| 875 |
+
_is _gs 1 exch sub setcustomcolor
|
| 876 |
+
/_lp /stroke ddef
|
| 877 |
+
} if
|
| 878 |
+
} ddef
|
| 879 |
+
/_ps
|
| 880 |
+
{
|
| 881 |
+
_sc
|
| 882 |
+
stroke
|
| 883 |
+
} ddef
|
| 884 |
+
/_pss
|
| 885 |
+
{
|
| 886 |
+
_sc
|
| 887 |
+
ss
|
| 888 |
+
} ddef
|
| 889 |
+
/_pjss
|
| 890 |
+
{
|
| 891 |
+
_sc
|
| 892 |
+
jss
|
| 893 |
+
} ddef
|
| 894 |
+
/_lp /none ddef
|
| 895 |
+
} def
|
| 896 |
+
/A
|
| 897 |
+
{
|
| 898 |
+
pop
|
| 899 |
+
} def
|
| 900 |
+
/annotatepage
|
| 901 |
+
{
|
| 902 |
+
userdict /annotatepage 2 copy known {get exec} {pop pop} ifelse
|
| 903 |
+
} def
|
| 904 |
+
/discard
|
| 905 |
+
{
|
| 906 |
+
save /discardSave exch store
|
| 907 |
+
discardDict begin
|
| 908 |
+
/endString exch store
|
| 909 |
+
gt38?
|
| 910 |
+
{
|
| 911 |
+
2 add
|
| 912 |
+
} if
|
| 913 |
+
load
|
| 914 |
+
stopped
|
| 915 |
+
pop
|
| 916 |
+
end
|
| 917 |
+
discardSave restore
|
| 918 |
+
} bind def
|
| 919 |
+
userdict /discardDict 7 dict dup begin
|
| 920 |
+
put
|
| 921 |
+
/pre38Initialize
|
| 922 |
+
{
|
| 923 |
+
/endStringLength endString length store
|
| 924 |
+
/newBuff buffer 0 endStringLength getinterval store
|
| 925 |
+
/newBuffButFirst newBuff 1 endStringLength 1 sub getinterval store
|
| 926 |
+
/newBuffLast newBuff endStringLength 1 sub 1 getinterval store
|
| 927 |
+
} def
|
| 928 |
+
/shiftBuffer
|
| 929 |
+
{
|
| 930 |
+
newBuff 0 newBuffButFirst putinterval
|
| 931 |
+
newBuffLast 0
|
| 932 |
+
currentfile read not
|
| 933 |
+
{
|
| 934 |
+
stop
|
| 935 |
+
} if
|
| 936 |
+
put
|
| 937 |
+
} def
|
| 938 |
+
0
|
| 939 |
+
{
|
| 940 |
+
pre38Initialize
|
| 941 |
+
mark
|
| 942 |
+
currentfile newBuff readstring exch pop
|
| 943 |
+
{
|
| 944 |
+
{
|
| 945 |
+
newBuff endString eq
|
| 946 |
+
{
|
| 947 |
+
cleartomark stop
|
| 948 |
+
} if
|
| 949 |
+
shiftBuffer
|
| 950 |
+
} loop
|
| 951 |
+
}
|
| 952 |
+
{
|
| 953 |
+
stop
|
| 954 |
+
} ifelse
|
| 955 |
+
} def
|
| 956 |
+
1
|
| 957 |
+
{
|
| 958 |
+
pre38Initialize
|
| 959 |
+
/beginString exch store
|
| 960 |
+
mark
|
| 961 |
+
currentfile newBuff readstring exch pop
|
| 962 |
+
{
|
| 963 |
+
{
|
| 964 |
+
newBuff beginString eq
|
| 965 |
+
{
|
| 966 |
+
/layerCount dup load 1 add store
|
| 967 |
+
}
|
| 968 |
+
{
|
| 969 |
+
newBuff endString eq
|
| 970 |
+
{
|
| 971 |
+
/layerCount dup load 1 sub store
|
| 972 |
+
layerCount 0 eq
|
| 973 |
+
{
|
| 974 |
+
cleartomark stop
|
| 975 |
+
} if
|
| 976 |
+
} if
|
| 977 |
+
} ifelse
|
| 978 |
+
shiftBuffer
|
| 979 |
+
} loop
|
| 980 |
+
}
|
| 981 |
+
{
|
| 982 |
+
stop
|
| 983 |
+
} ifelse
|
| 984 |
+
} def
|
| 985 |
+
2
|
| 986 |
+
{
|
| 987 |
+
mark
|
| 988 |
+
{
|
| 989 |
+
currentfile buffer readline not
|
| 990 |
+
{
|
| 991 |
+
stop
|
| 992 |
+
} if
|
| 993 |
+
endString eq
|
| 994 |
+
{
|
| 995 |
+
cleartomark stop
|
| 996 |
+
} if
|
| 997 |
+
} loop
|
| 998 |
+
} def
|
| 999 |
+
3
|
| 1000 |
+
{
|
| 1001 |
+
/beginString exch store
|
| 1002 |
+
/layerCnt 1 store
|
| 1003 |
+
mark
|
| 1004 |
+
{
|
| 1005 |
+
currentfile buffer readline not
|
| 1006 |
+
{
|
| 1007 |
+
stop
|
| 1008 |
+
} if
|
| 1009 |
+
dup beginString eq
|
| 1010 |
+
{
|
| 1011 |
+
pop /layerCnt dup load 1 add store
|
| 1012 |
+
}
|
| 1013 |
+
{
|
| 1014 |
+
endString eq
|
| 1015 |
+
{
|
| 1016 |
+
layerCnt 1 eq
|
| 1017 |
+
{
|
| 1018 |
+
cleartomark stop
|
| 1019 |
+
}
|
| 1020 |
+
{
|
| 1021 |
+
/layerCnt dup load 1 sub store
|
| 1022 |
+
} ifelse
|
| 1023 |
+
} if
|
| 1024 |
+
} ifelse
|
| 1025 |
+
} loop
|
| 1026 |
+
} def
|
| 1027 |
+
end
|
| 1028 |
+
userdict /clipRenderOff 15 dict dup begin
|
| 1029 |
+
put
|
| 1030 |
+
{
|
| 1031 |
+
/n /N /s /S /f /F /b /B
|
| 1032 |
+
}
|
| 1033 |
+
{
|
| 1034 |
+
{
|
| 1035 |
+
_doClip 1 eq
|
| 1036 |
+
{
|
| 1037 |
+
/_doClip 0 ddef clip
|
| 1038 |
+
} if
|
| 1039 |
+
newpath
|
| 1040 |
+
} def
|
| 1041 |
+
} forall
|
| 1042 |
+
/Tr /pop load def
|
| 1043 |
+
/Bb {} def
|
| 1044 |
+
/BB /pop load def
|
| 1045 |
+
/Bg {12 npop} def
|
| 1046 |
+
/Bm {6 npop} def
|
| 1047 |
+
/Bc /Bm load def
|
| 1048 |
+
/Bh {4 npop} def
|
| 1049 |
+
end
|
| 1050 |
+
/Lb
|
| 1051 |
+
{
|
| 1052 |
+
4 npop
|
| 1053 |
+
6 1 roll
|
| 1054 |
+
pop
|
| 1055 |
+
4 1 roll
|
| 1056 |
+
pop pop pop
|
| 1057 |
+
0 eq
|
| 1058 |
+
{
|
| 1059 |
+
0 eq
|
| 1060 |
+
{
|
| 1061 |
+
(%AI5_BeginLayer) 1 (%AI5_EndLayer--) discard
|
| 1062 |
+
}
|
| 1063 |
+
{
|
| 1064 |
+
/clipForward? true def
|
| 1065 |
+
|
| 1066 |
+
/Tx /pop load def
|
| 1067 |
+
/Tj /pop load def
|
| 1068 |
+
currentdict end clipRenderOff begin begin
|
| 1069 |
+
} ifelse
|
| 1070 |
+
}
|
| 1071 |
+
{
|
| 1072 |
+
0 eq
|
| 1073 |
+
{
|
| 1074 |
+
save /discardSave exch store
|
| 1075 |
+
} if
|
| 1076 |
+
} ifelse
|
| 1077 |
+
} bind def
|
| 1078 |
+
/LB
|
| 1079 |
+
{
|
| 1080 |
+
discardSave dup null ne
|
| 1081 |
+
{
|
| 1082 |
+
restore
|
| 1083 |
+
}
|
| 1084 |
+
{
|
| 1085 |
+
pop
|
| 1086 |
+
clipForward?
|
| 1087 |
+
{
|
| 1088 |
+
currentdict
|
| 1089 |
+
end
|
| 1090 |
+
end
|
| 1091 |
+
begin
|
| 1092 |
+
|
| 1093 |
+
/clipForward? false ddef
|
| 1094 |
+
} if
|
| 1095 |
+
} ifelse
|
| 1096 |
+
} bind def
|
| 1097 |
+
/Pb
|
| 1098 |
+
{
|
| 1099 |
+
pop pop
|
| 1100 |
+
0 (%AI5_EndPalette) discard
|
| 1101 |
+
} bind def
|
| 1102 |
+
/Np
|
| 1103 |
+
{
|
| 1104 |
+
0 (%AI5_End_NonPrinting--) discard
|
| 1105 |
+
} bind def
|
| 1106 |
+
/Ln /pop load def
|
| 1107 |
+
/Ap
|
| 1108 |
+
/pop load def
|
| 1109 |
+
/Ar
|
| 1110 |
+
{
|
| 1111 |
+
72 exch div
|
| 1112 |
+
0 dtransform dup mul exch dup mul add sqrt
|
| 1113 |
+
dup 1 lt
|
| 1114 |
+
{
|
| 1115 |
+
pop 1
|
| 1116 |
+
} if
|
| 1117 |
+
setflat
|
| 1118 |
+
} def
|
| 1119 |
+
/Mb
|
| 1120 |
+
{
|
| 1121 |
+
q
|
| 1122 |
+
} def
|
| 1123 |
+
/Md
|
| 1124 |
+
{
|
| 1125 |
+
} def
|
| 1126 |
+
/MB
|
| 1127 |
+
{
|
| 1128 |
+
Q
|
| 1129 |
+
} def
|
| 1130 |
+
/nc 3 dict def
|
| 1131 |
+
nc begin
|
| 1132 |
+
/setgray
|
| 1133 |
+
{
|
| 1134 |
+
pop
|
| 1135 |
+
} bind def
|
| 1136 |
+
/setcmykcolor
|
| 1137 |
+
{
|
| 1138 |
+
4 npop
|
| 1139 |
+
} bind def
|
| 1140 |
+
/setcustomcolor
|
| 1141 |
+
{
|
| 1142 |
+
2 npop
|
| 1143 |
+
} bind def
|
| 1144 |
+
currentdict readonly pop
|
| 1145 |
+
end
|
| 1146 |
+
currentdict readonly pop
|
| 1147 |
+
end
|
| 1148 |
+
setpacking
|
| 1149 |
+
%%EndResource
|
| 1150 |
+
%%EndProlog
|
| 1151 |
+
%%BeginSetup
|
| 1152 |
+
Adobe_level2_AI5 /initialize get exec
|
| 1153 |
+
Adobe_IllustratorA_AI5 /initialize get exec
|
| 1154 |
+
%AI5_Begin_NonPrinting
|
| 1155 |
+
Np
|
| 1156 |
+
%AI3_BeginPattern: (Yellow Stripe)
|
| 1157 |
+
(Yellow Stripe) 8.4499 4.6 80.4499 76.6 [
|
| 1158 |
+
%AI3_Tile
|
| 1159 |
+
(0 O 0 R 0 0.4 1 0 k 0 0.4 1 0 K) @
|
| 1160 |
+
(
|
| 1161 |
+
800 Ar
|
| 1162 |
+
0 J 0 j 3.6 w 4 M []0 d
|
| 1163 |
+
%AI3_Note:
|
| 1164 |
+
0 D
|
| 1165 |
+
8.1999 8.1999 m
|
| 1166 |
+
80.6999 8.1999 L
|
| 1167 |
+
S
|
| 1168 |
+
8.1999 22.6 m
|
| 1169 |
+
80.6999 22.6 L
|
| 1170 |
+
S
|
| 1171 |
+
8.1999 37.0001 m
|
| 1172 |
+
80.6999 37.0001 L
|
| 1173 |
+
S
|
| 1174 |
+
8.1999 51.3999 m
|
| 1175 |
+
80.6999 51.3999 L
|
| 1176 |
+
S
|
| 1177 |
+
8.1999 65.8 m
|
| 1178 |
+
80.6999 65.8 L
|
| 1179 |
+
S
|
| 1180 |
+
8.1999 15.3999 m
|
| 1181 |
+
80.6999 15.3999 L
|
| 1182 |
+
S
|
| 1183 |
+
8.1999 29.8 m
|
| 1184 |
+
80.6999 29.8 L
|
| 1185 |
+
S
|
| 1186 |
+
8.1999 44.1999 m
|
| 1187 |
+
80.6999 44.1999 L
|
| 1188 |
+
S
|
| 1189 |
+
8.1999 58.6 m
|
| 1190 |
+
80.6999 58.6 L
|
| 1191 |
+
S
|
| 1192 |
+
8.1999 73.0001 m
|
| 1193 |
+
80.6999 73.0001 L
|
| 1194 |
+
S
|
| 1195 |
+
) &
|
| 1196 |
+
] E
|
| 1197 |
+
%AI3_EndPattern
|
| 1198 |
+
%AI5_End_NonPrinting--
|
| 1199 |
+
%AI5_Begin_NonPrinting
|
| 1200 |
+
Np
|
| 1201 |
+
3 Bn
|
| 1202 |
+
%AI5_BeginGradient: (Black & White)
|
| 1203 |
+
(Black & White) 0 2 Bd
|
| 1204 |
+
[
|
| 1205 |
+
<
|
| 1206 |
+
FFFEFDFCFBFAF9F8F7F6F5F4F3F2F1F0EFEEEDECEBEAE9E8E7E6E5E4E3E2E1E0DFDEDDDCDBDAD9D8
|
| 1207 |
+
D7D6D5D4D3D2D1D0CFCECDCCCBCAC9C8C7C6C5C4C3C2C1C0BFBEBDBCBBBAB9B8B7B6B5B4B3B2B1B0
|
| 1208 |
+
AFAEADACABAAA9A8A7A6A5A4A3A2A1A09F9E9D9C9B9A999897969594939291908F8E8D8C8B8A8988
|
| 1209 |
+
87868584838281807F7E7D7C7B7A797877767574737271706F6E6D6C6B6A69686766656463626160
|
| 1210 |
+
5F5E5D5C5B5A595857565554535251504F4E4D4C4B4A494847464544434241403F3E3D3C3B3A3938
|
| 1211 |
+
37363534333231302F2E2D2C2B2A292827262524232221201F1E1D1C1B1A19181716151413121110
|
| 1212 |
+
0F0E0D0C0B0A09080706050403020100
|
| 1213 |
+
>
|
| 1214 |
+
0 %_Br
|
| 1215 |
+
[
|
| 1216 |
+
0 0 50 100 %_Bs
|
| 1217 |
+
1 0 50 0 %_Bs
|
| 1218 |
+
BD
|
| 1219 |
+
%AI5_EndGradient
|
| 1220 |
+
%AI5_BeginGradient: (Red & Yellow)
|
| 1221 |
+
(Red & Yellow) 0 2 Bd
|
| 1222 |
+
[
|
| 1223 |
+
0
|
| 1224 |
+
<
|
| 1225 |
+
000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627
|
| 1226 |
+
28292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F
|
| 1227 |
+
505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F7071727374757677
|
| 1228 |
+
78797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9F
|
| 1229 |
+
A0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7
|
| 1230 |
+
C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEF
|
| 1231 |
+
F0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF
|
| 1232 |
+
>
|
| 1233 |
+
<
|
| 1234 |
+
FFFFFEFEFDFDFDFCFCFBFBFBFAFAF9F9F9F8F8F7F7F7F6F6F5F5F5F4F4F3F3F3F2F2F1F1F1F0F0EF
|
| 1235 |
+
EFEFEEEEEDEDEDECECEBEBEBEAEAE9E9E9E8E8E7E7E7E6E6E5E5E5E4E4E3E3E3E2E2E1E1E1E0E0DF
|
| 1236 |
+
DFDFDEDEDDDDDDDCDCDBDBDBDADAD9D9D9D8D8D7D7D7D6D6D5D5D5D4D4D3D3D3D2D2D1D1D1D0D0CF
|
| 1237 |
+
CFCFCECECDCDCDCCCCCBCBCBCACAC9C9C9C8C8C7C7C7C6C6C5C5C5C4C4C3C3C3C2C2C1C1C1C0C0BF
|
| 1238 |
+
BFBFBEBEBDBDBDBCBCBBBBBBBABAB9B9B9B8B8B7B7B7B6B6B5B5B5B4B4B3B3B3B2B2B1B1B1B0B0AF
|
| 1239 |
+
AFAFAEAEADADADACACABABABAAAAA9A9A9A8A8A7A7A7A6A6A5A5A5A4A4A3A3A3A2A2A1A1A1A0A09F
|
| 1240 |
+
9F9F9E9E9D9D9D9C9C9B9B9B9A9A9999
|
| 1241 |
+
>
|
| 1242 |
+
0
|
| 1243 |
+
1 %_Br
|
| 1244 |
+
[
|
| 1245 |
+
0 1 0.6 0 1 50 100 %_Bs
|
| 1246 |
+
0 0 1 0 1 50 0 %_Bs
|
| 1247 |
+
BD
|
| 1248 |
+
%AI5_EndGradient
|
| 1249 |
+
%AI5_BeginGradient: (Yellow & Blue Radial)
|
| 1250 |
+
(Yellow & Blue Radial) 1 2 Bd
|
| 1251 |
+
[
|
| 1252 |
+
<
|
| 1253 |
+
000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627
|
| 1254 |
+
28292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F
|
| 1255 |
+
505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F7071727374757677
|
| 1256 |
+
78797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9F
|
| 1257 |
+
A0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7
|
| 1258 |
+
C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEF
|
| 1259 |
+
F0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF
|
| 1260 |
+
>
|
| 1261 |
+
<
|
| 1262 |
+
1415161718191A1B1C1D1E1F1F202122232425262728292A2A2B2C2D2E2F30313233343536363738
|
| 1263 |
+
393A3B3C3D3E3F40414142434445464748494A4B4C4D4D4E4F50515253545556575858595A5B5C5D
|
| 1264 |
+
5E5F60616263646465666768696A6B6C6D6E6F6F707172737475767778797A7B7B7C7D7E7F808182
|
| 1265 |
+
83848586868788898A8B8C8D8E8F90919292939495969798999A9B9C9D9D9E9FA0A1A2A3A4A5A6A7
|
| 1266 |
+
A8A9A9AAABACADAEAFB0B1B2B3B4B4B5B6B7B8B9BABBBCBDBEBFC0C0C1C2C3C4C5C6C7C8C9CACBCB
|
| 1267 |
+
CCCDCECFD0D1D2D3D4D5D6D7D7D8D9DADBDCDDDEDFE0E1E2E2E3E4E5E6E7E8E9EAEBECEDEEEEEFF0
|
| 1268 |
+
F1F2F3F4F5F6F7F8F9F9FAFBFCFDFEFF
|
| 1269 |
+
>
|
| 1270 |
+
<
|
| 1271 |
+
ABAAAAA9A8A7A7A6A5A5A4A3A3A2A1A1A09F9F9E9D9D9C9B9B9A9999989797969595949393929191
|
| 1272 |
+
908F8F8E8D8D8C8B8B8A8989888787868585848383828181807F7F7E7D7D7C7B7B7A797978777776
|
| 1273 |
+
7575747373727171706F6F6E6D6D6C6B6B6A6969686767666565646362626160605F5E5E5D5C5C5B
|
| 1274 |
+
5A5A5958585756565554545352525150504F4E4E4D4C4C4B4A4A4948484746464544444342424140
|
| 1275 |
+
403F3E3E3D3C3C3B3A3A3938383736363534343332323130302F2E2E2D2C2C2B2A2A292828272626
|
| 1276 |
+
25242423222121201F1F1E1D1D1C1B1B1A1919181717161515141313121111100F0F0E0D0D0C0B0B
|
| 1277 |
+
0A090908070706050504030302010100
|
| 1278 |
+
>
|
| 1279 |
+
0
|
| 1280 |
+
1 %_Br
|
| 1281 |
+
[
|
| 1282 |
+
0 0.08 0.67 0 1 50 14 %_Bs
|
| 1283 |
+
1 1 0 0 1 50 100 %_Bs
|
| 1284 |
+
BD
|
| 1285 |
+
%AI5_EndGradient
|
| 1286 |
+
%AI5_End_NonPrinting--
|
| 1287 |
+
%AI5_BeginPalette
|
| 1288 |
+
144 170 Pb
|
| 1289 |
+
Pn
|
| 1290 |
+
Pc
|
| 1291 |
+
1 g
|
| 1292 |
+
Pc
|
| 1293 |
+
0 g
|
| 1294 |
+
Pc
|
| 1295 |
+
0 0 0 0 k
|
| 1296 |
+
Pc
|
| 1297 |
+
0.75 g
|
| 1298 |
+
Pc
|
| 1299 |
+
0.5 g
|
| 1300 |
+
Pc
|
| 1301 |
+
0.25 g
|
| 1302 |
+
Pc
|
| 1303 |
+
0 g
|
| 1304 |
+
Pc
|
| 1305 |
+
Bb
|
| 1306 |
+
2 (Black & White) -4014 4716 0 0 1 0 0 1 0 0 Bg
|
| 1307 |
+
0 BB
|
| 1308 |
+
Pc
|
| 1309 |
+
0.25 0 0 0 k
|
| 1310 |
+
Pc
|
| 1311 |
+
0.5 0 0 0 k
|
| 1312 |
+
Pc
|
| 1313 |
+
0.75 0 0 0 k
|
| 1314 |
+
Pc
|
| 1315 |
+
1 0 0 0 k
|
| 1316 |
+
Pc
|
| 1317 |
+
0.25 0.25 0 0 k
|
| 1318 |
+
Pc
|
| 1319 |
+
0.5 0.5 0 0 k
|
| 1320 |
+
Pc
|
| 1321 |
+
0.75 0.75 0 0 k
|
| 1322 |
+
Pc
|
| 1323 |
+
1 1 0 0 k
|
| 1324 |
+
Pc
|
| 1325 |
+
Bb
|
| 1326 |
+
2 (Red & Yellow) -4014 4716 0 0 1 0 0 1 0 0 Bg
|
| 1327 |
+
0 BB
|
| 1328 |
+
Pc
|
| 1329 |
+
0 0.25 0 0 k
|
| 1330 |
+
Pc
|
| 1331 |
+
0 0.5 0 0 k
|
| 1332 |
+
Pc
|
| 1333 |
+
0 0.75 0 0 k
|
| 1334 |
+
Pc
|
| 1335 |
+
0 1 0 0 k
|
| 1336 |
+
Pc
|
| 1337 |
+
0 0.25 0.25 0 k
|
| 1338 |
+
Pc
|
| 1339 |
+
0 0.5 0.5 0 k
|
| 1340 |
+
Pc
|
| 1341 |
+
0 0.75 0.75 0 k
|
| 1342 |
+
Pc
|
| 1343 |
+
0 1 1 0 k
|
| 1344 |
+
Pc
|
| 1345 |
+
Bb
|
| 1346 |
+
0 0 0 0 Bh
|
| 1347 |
+
2 (Yellow & Blue Radial) -4014 4716 0 0 1 0 0 1 0 0 Bg
|
| 1348 |
+
0 BB
|
| 1349 |
+
Pc
|
| 1350 |
+
0 0 0.25 0 k
|
| 1351 |
+
Pc
|
| 1352 |
+
0 0 0.5 0 k
|
| 1353 |
+
Pc
|
| 1354 |
+
0 0 0.75 0 k
|
| 1355 |
+
Pc
|
| 1356 |
+
0 0 1 0 k
|
| 1357 |
+
Pc
|
| 1358 |
+
0.25 0 0.25 0 k
|
| 1359 |
+
Pc
|
| 1360 |
+
0.5 0 0.5 0 k
|
| 1361 |
+
Pc
|
| 1362 |
+
0.75 0 0.75 0 k
|
| 1363 |
+
Pc
|
| 1364 |
+
1 0 1 0 k
|
| 1365 |
+
Pc
|
| 1366 |
+
(Yellow Stripe) 0 0 1 1 0 0 0 0 0 [1 0 0 1 0 0] p
|
| 1367 |
+
Pc
|
| 1368 |
+
0.25 0.125 0 0 k
|
| 1369 |
+
Pc
|
| 1370 |
+
0.5 0.25 0 0 k
|
| 1371 |
+
Pc
|
| 1372 |
+
0.75 0.375 0 0 k
|
| 1373 |
+
Pc
|
| 1374 |
+
1 0.5 0 0 k
|
| 1375 |
+
Pc
|
| 1376 |
+
0.125 0.25 0 0 k
|
| 1377 |
+
Pc
|
| 1378 |
+
0.25 0.5 0 0 k
|
| 1379 |
+
Pc
|
| 1380 |
+
0.375 0.75 0 0 k
|
| 1381 |
+
Pc
|
| 1382 |
+
0.5 1 0 0 k
|
| 1383 |
+
Pc
|
| 1384 |
+
0.375 0.375 0.75 0 k
|
| 1385 |
+
Pc
|
| 1386 |
+
0 0.25 0.125 0 k
|
| 1387 |
+
Pc
|
| 1388 |
+
0 0.5 0.25 0 k
|
| 1389 |
+
Pc
|
| 1390 |
+
0 0.75 0.375 0 k
|
| 1391 |
+
Pc
|
| 1392 |
+
0 1 0.5 0 k
|
| 1393 |
+
Pc
|
| 1394 |
+
0 0.125 0.25 0 k
|
| 1395 |
+
Pc
|
| 1396 |
+
0 0.25 0.5 0 k
|
| 1397 |
+
Pc
|
| 1398 |
+
0 0.375 0.75 0 k
|
| 1399 |
+
Pc
|
| 1400 |
+
0 0.5 1 0 k
|
| 1401 |
+
Pc
|
| 1402 |
+
0 0.79 0.91 0 (TCL RED) 0 x
|
| 1403 |
+
Pc
|
| 1404 |
+
0.125 0 0.25 0 k
|
| 1405 |
+
Pc
|
| 1406 |
+
0.25 0 0.5 0 k
|
| 1407 |
+
Pc
|
| 1408 |
+
0.375 0 0.75 0 k
|
| 1409 |
+
Pc
|
| 1410 |
+
0.5 0 1 0 k
|
| 1411 |
+
Pc
|
| 1412 |
+
0.25 0 0.125 0 k
|
| 1413 |
+
Pc
|
| 1414 |
+
0.5 0 0.25 0 k
|
| 1415 |
+
Pc
|
| 1416 |
+
0.75 0 0.375 0 k
|
| 1417 |
+
Pc
|
| 1418 |
+
1 0 0.5 0 k
|
| 1419 |
+
Pc
|
| 1420 |
+
0.5 1 0 0 k
|
| 1421 |
+
Pc
|
| 1422 |
+
0.25 0.125 0.125 0 k
|
| 1423 |
+
Pc
|
| 1424 |
+
0.5 0.25 0.25 0 k
|
| 1425 |
+
Pc
|
| 1426 |
+
0.75 0.375 0.375 0 k
|
| 1427 |
+
Pc
|
| 1428 |
+
1 0.5 0.5 0 k
|
| 1429 |
+
Pc
|
| 1430 |
+
0.25 0.25 0.125 0 k
|
| 1431 |
+
Pc
|
| 1432 |
+
0.5 0.5 0.25 0 k
|
| 1433 |
+
Pc
|
| 1434 |
+
0.75 0.75 0.375 0 k
|
| 1435 |
+
Pc
|
| 1436 |
+
1 1 0.5 0 k
|
| 1437 |
+
Pc
|
| 1438 |
+
0 1 0.5 0 k
|
| 1439 |
+
Pc
|
| 1440 |
+
0.125 0.25 0.125 0 k
|
| 1441 |
+
Pc
|
| 1442 |
+
0.25 0.5 0.25 0 k
|
| 1443 |
+
Pc
|
| 1444 |
+
0.375 0.75 0.375 0 k
|
| 1445 |
+
Pc
|
| 1446 |
+
0.5 1 0.5 0 k
|
| 1447 |
+
Pc
|
| 1448 |
+
0.125 0.25 0.25 0 k
|
| 1449 |
+
Pc
|
| 1450 |
+
0.25 0.5 0.5 0 k
|
| 1451 |
+
Pc
|
| 1452 |
+
0.375 0.75 0.75 0 k
|
| 1453 |
+
Pc
|
| 1454 |
+
0.5 1 1 0 k
|
| 1455 |
+
Pc
|
| 1456 |
+
0.75 0.75 0.375 0 k
|
| 1457 |
+
Pc
|
| 1458 |
+
0.125 0.125 0.25 0 k
|
| 1459 |
+
Pc
|
| 1460 |
+
0.25 0.25 0.5 0 k
|
| 1461 |
+
Pc
|
| 1462 |
+
0.375 0.375 0.75 0 k
|
| 1463 |
+
Pc
|
| 1464 |
+
0.5 0.5 1 0 k
|
| 1465 |
+
Pc
|
| 1466 |
+
0.25 0.125 0.25 0 k
|
| 1467 |
+
Pc
|
| 1468 |
+
0.5 0.25 0.5 0 k
|
| 1469 |
+
Pc
|
| 1470 |
+
0.75 0.375 0.75 0 k
|
| 1471 |
+
Pc
|
| 1472 |
+
1 0.5 1 0 k
|
| 1473 |
+
Pc
|
| 1474 |
+
0 0.79 0.91 0 (TCL RED) 0 x
|
| 1475 |
+
Pc
|
| 1476 |
+
0 0 0 0 k
|
| 1477 |
+
Pc
|
| 1478 |
+
Pc
|
| 1479 |
+
Pc
|
| 1480 |
+
Pc
|
| 1481 |
+
Pc
|
| 1482 |
+
Pc
|
| 1483 |
+
Pc
|
| 1484 |
+
Pc
|
| 1485 |
+
1 0.5 0.5 0 k
|
| 1486 |
+
Pc
|
| 1487 |
+
0 0 0 0 k
|
| 1488 |
+
Pc
|
| 1489 |
+
Pc
|
| 1490 |
+
Pc
|
| 1491 |
+
Pc
|
| 1492 |
+
Pc
|
| 1493 |
+
Pc
|
| 1494 |
+
Pc
|
| 1495 |
+
Pc
|
| 1496 |
+
0 0.25 1 0 (Orange Yellow) 0 x
|
| 1497 |
+
Pc
|
| 1498 |
+
0 0 0 0 k
|
| 1499 |
+
Pc
|
| 1500 |
+
Pc
|
| 1501 |
+
Pc
|
| 1502 |
+
Pc
|
| 1503 |
+
Pc
|
| 1504 |
+
Pc
|
| 1505 |
+
Pc
|
| 1506 |
+
Pc
|
| 1507 |
+
0 1 0.5 0 k
|
| 1508 |
+
Pc
|
| 1509 |
+
0 0 0 0 k
|
| 1510 |
+
Pc
|
| 1511 |
+
Pc
|
| 1512 |
+
Pc
|
| 1513 |
+
Pc
|
| 1514 |
+
Pc
|
| 1515 |
+
Pc
|
| 1516 |
+
Pc
|
| 1517 |
+
Pc
|
| 1518 |
+
1 0 0.5 0 k
|
| 1519 |
+
Pc
|
| 1520 |
+
0 0 0 0 k
|
| 1521 |
+
Pc
|
| 1522 |
+
Pc
|
| 1523 |
+
Pc
|
| 1524 |
+
Pc
|
| 1525 |
+
Pc
|
| 1526 |
+
Pc
|
| 1527 |
+
Pc
|
| 1528 |
+
Pc
|
| 1529 |
+
0 0.45 1 0 (Orange) 0 x
|
| 1530 |
+
Pc
|
| 1531 |
+
0 0 0 0 k
|
| 1532 |
+
Pc
|
| 1533 |
+
Pc
|
| 1534 |
+
Pc
|
| 1535 |
+
Pc
|
| 1536 |
+
Pc
|
| 1537 |
+
Pc
|
| 1538 |
+
Pc
|
| 1539 |
+
Pc
|
| 1540 |
+
0.375 0.375 0.75 0 k
|
| 1541 |
+
Pc
|
| 1542 |
+
0 0 0 0 k
|
| 1543 |
+
Pc
|
| 1544 |
+
Pc
|
| 1545 |
+
Pc
|
| 1546 |
+
Pc
|
| 1547 |
+
Pc
|
| 1548 |
+
Pc
|
| 1549 |
+
Pc
|
| 1550 |
+
Pc
|
| 1551 |
+
0 0.79 0.91 0 (TCL RED) 0 x
|
| 1552 |
+
Pc
|
| 1553 |
+
0 0 0 0 k
|
| 1554 |
+
Pc
|
| 1555 |
+
Pc
|
| 1556 |
+
Pc
|
| 1557 |
+
Pc
|
| 1558 |
+
Pc
|
| 1559 |
+
Pc
|
| 1560 |
+
Pc
|
| 1561 |
+
Pc
|
| 1562 |
+
1 0.65 0 0 k
|
| 1563 |
+
Pc
|
| 1564 |
+
0 0 0 0 k
|
| 1565 |
+
Pc
|
| 1566 |
+
Pc
|
| 1567 |
+
Pc
|
| 1568 |
+
Pc
|
| 1569 |
+
Pc
|
| 1570 |
+
Pc
|
| 1571 |
+
Pc
|
| 1572 |
+
Pc
|
| 1573 |
+
0 0 1 0 k
|
| 1574 |
+
Pc
|
| 1575 |
+
PB
|
| 1576 |
+
%AI5_EndPalette
|
| 1577 |
+
%%EndSetup
|
| 1578 |
+
%AI5_BeginLayer
|
| 1579 |
+
1 1 1 1 0 0 0 79 128 255 Lb
|
| 1580 |
+
(Layer 1) Ln
|
| 1581 |
+
0 A
|
| 1582 |
+
u
|
| 1583 |
+
1 Ap
|
| 1584 |
+
0 O
|
| 1585 |
+
0 0.79 0.91 0 (TCL RED) 0 x
|
| 1586 |
+
800 Ar
|
| 1587 |
+
0 J 0 j 1.25 w 4 M []0 d
|
| 1588 |
+
%AI3_Note:
|
| 1589 |
+
0 D
|
| 1590 |
+
294.5207 335.3041 m
|
| 1591 |
+
368.2181 333.001 L
|
| 1592 |
+
363.6121 423.9713 L
|
| 1593 |
+
370.5213 507.1689 L
|
| 1594 |
+
336.5513 505.4417 L
|
| 1595 |
+
320.7179 511.775 L
|
| 1596 |
+
251.3386 508.0325 L
|
| 1597 |
+
254.7931 425.9866 L
|
| 1598 |
+
251.3386 331.5616 L
|
| 1599 |
+
294.5207 335.3041 L
|
| 1600 |
+
f
|
| 1601 |
+
u
|
| 1602 |
+
0 Ap
|
| 1603 |
+
1 0.65 0 0 k
|
| 1604 |
+
1 w
|
| 1605 |
+
318.1366 400.9627 m
|
| 1606 |
+
311.8663 399.2526 l
|
| 1607 |
+
315.2864 407.5177 l
|
| 1608 |
+
318.7064 430.6032 l
|
| 1609 |
+
314.4314 431.4581 l
|
| 1610 |
+
319.5616 438.5832 l
|
| 1611 |
+
325.9526 462.6014 l
|
| 1612 |
+
314.7164 460.2436 l
|
| 1613 |
+
320.6412 471.0911 326.9284 478.1557 v
|
| 1614 |
+
318.7064 484.469 l
|
| 1615 |
+
292.2183 472.8011 299.3434 434.8954 v
|
| 1616 |
+
293.8679 435.8542 l
|
| 1617 |
+
299.1189 396.1175 l
|
| 1618 |
+
294.6797 394.9775 l
|
| 1619 |
+
299.2277 385.6974 305.5963 381.2973 v
|
| 1620 |
+
306.1744 380.8979 297.6162 412.3629 306.7363 443.7133 c
|
| 1621 |
+
307.5914 441.7183 l
|
| 1622 |
+
300.3238 408.3015 307.5914 381.2973 v
|
| 1623 |
+
307.9261 380.656 311.5598 381.0836 v
|
| 1624 |
+
318.1366 393.4813 318.1366 400.9627 v
|
| 1625 |
+
f
|
| 1626 |
+
u
|
| 1627 |
+
*u
|
| 1628 |
+
1 g
|
| 1629 |
+
271.4311 372.5074 m
|
| 1630 |
+
272.7184 372.5074 L
|
| 1631 |
+
272.7184 375.1913 L
|
| 1632 |
+
273.2858 375.1913 273.8313 375.1913 274.3768 375.2786 c
|
| 1633 |
+
274.3768 372.5074 L
|
| 1634 |
+
276.2969 372.5074 L
|
| 1635 |
+
276.2969 372.0056 L
|
| 1636 |
+
274.3768 372.0056 L
|
| 1637 |
+
274.3768 365.3286 L
|
| 1638 |
+
274.3768 364.9359 274.3768 364.3467 275.2059 364.3467 c
|
| 1639 |
+
275.7733 364.3467 276.0787 364.7395 276.4279 365.1541 c
|
| 1640 |
+
276.777 364.9141 L
|
| 1641 |
+
276.3624 364.0849 275.2932 363.583 274.4204 363.583 c
|
| 1642 |
+
272.8494 363.583 272.6748 364.434 272.6748 365.4814 c
|
| 1643 |
+
272.6748 372.0056 L
|
| 1644 |
+
271.4311 372.0056 L
|
| 1645 |
+
271.4311 372.5074 l
|
| 1646 |
+
f
|
| 1647 |
+
*U
|
| 1648 |
+
*u
|
| 1649 |
+
290.5617 366.5724 m
|
| 1650 |
+
290.0598 365.0232 289.187 363.6703 286.9178 363.583 c
|
| 1651 |
+
283.5356 363.583 282.5101 366.3978 282.5101 367.9034 c
|
| 1652 |
+
282.5101 371.7874 285.6304 372.7256 286.8741 372.7256 c
|
| 1653 |
+
288.2924 372.7256 290.2999 372.071 290.2999 370.3909 c
|
| 1654 |
+
290.2999 369.8018 289.9289 369.2344 289.318 369.2344 c
|
| 1655 |
+
288.7288 369.2344 288.2924 369.6272 288.2924 370.26 c
|
| 1656 |
+
288.2924 371.111 288.9907 371.2201 288.9907 371.4601 c
|
| 1657 |
+
288.9907 372.0492 287.616 372.2892 287.136 372.2892 c
|
| 1658 |
+
285.0412 372.2892 284.4957 370.7618 284.4957 367.9034 c
|
| 1659 |
+
284.4957 366.5942 284.823 365.5905 284.9539 365.285 c
|
| 1660 |
+
285.2812 364.5649 285.9577 364.1067 287.0923 364.0413 c
|
| 1661 |
+
288.3579 363.9758 289.5798 365.0013 290.1035 366.5724 C
|
| 1662 |
+
290.5617 366.5724 l
|
| 1663 |
+
f
|
| 1664 |
+
*U
|
| 1665 |
+
*u
|
| 1666 |
+
296.6 363.8667 m
|
| 1667 |
+
296.6 364.3686 L
|
| 1668 |
+
298.2802 364.3686 L
|
| 1669 |
+
298.2802 378.3989 L
|
| 1670 |
+
296.6 378.3989 L
|
| 1671 |
+
296.6 378.9007 L
|
| 1672 |
+
297.5383 378.9007 L
|
| 1673 |
+
298.3457 378.9007 299.1966 378.9444 299.9822 379.0971 c
|
| 1674 |
+
299.9822 364.3686 L
|
| 1675 |
+
301.6623 364.3686 L
|
| 1676 |
+
301.6623 363.8667 L
|
| 1677 |
+
296.6 363.8667 l
|
| 1678 |
+
f
|
| 1679 |
+
*U
|
| 1680 |
+
*u
|
| 1681 |
+
317.4527 372.5074 m
|
| 1682 |
+
318.7401 372.5074 L
|
| 1683 |
+
318.7401 375.1913 L
|
| 1684 |
+
319.3074 375.1913 319.8529 375.1913 320.3984 375.2786 c
|
| 1685 |
+
320.3984 372.5074 L
|
| 1686 |
+
322.3186 372.5074 L
|
| 1687 |
+
322.3186 372.0056 L
|
| 1688 |
+
320.3984 372.0056 L
|
| 1689 |
+
320.3984 365.3286 L
|
| 1690 |
+
320.3984 364.9359 320.3984 364.3467 321.2276 364.3467 c
|
| 1691 |
+
321.7949 364.3467 322.1004 364.7395 322.4495 365.1541 c
|
| 1692 |
+
322.7986 364.9141 L
|
| 1693 |
+
322.384 364.0849 321.3148 363.583 320.442 363.583 c
|
| 1694 |
+
318.871 363.583 318.6964 364.434 318.6964 365.4814 c
|
| 1695 |
+
318.6964 372.0056 L
|
| 1696 |
+
317.4527 372.0056 L
|
| 1697 |
+
317.4527 372.5074 l
|
| 1698 |
+
f
|
| 1699 |
+
*U
|
| 1700 |
+
*u
|
| 1701 |
+
333.7467 372.0056 m
|
| 1702 |
+
333.7467 372.5074 L
|
| 1703 |
+
337.3252 372.5074 L
|
| 1704 |
+
337.3252 372.0056 L
|
| 1705 |
+
335.9942 372.0056 L
|
| 1706 |
+
332.983 369.3872 L
|
| 1707 |
+
337.1288 364.3686 L
|
| 1708 |
+
338.0453 364.3686 L
|
| 1709 |
+
338.0453 363.8667 L
|
| 1710 |
+
333.8995 363.8667 L
|
| 1711 |
+
333.8995 364.3686 L
|
| 1712 |
+
334.9905 364.3686 L
|
| 1713 |
+
331.3465 368.798 L
|
| 1714 |
+
335.0341 371.9401 L
|
| 1715 |
+
335.0341 372.0056 L
|
| 1716 |
+
333.7467 372.0056 l
|
| 1717 |
+
f
|
| 1718 |
+
328.4881 363.8667 m
|
| 1719 |
+
328.4881 364.3686 L
|
| 1720 |
+
329.6227 364.3686 L
|
| 1721 |
+
329.6227 378.3989 L
|
| 1722 |
+
328.4881 378.3989 L
|
| 1723 |
+
328.4881 378.9007 L
|
| 1724 |
+
328.8809 378.9007 L
|
| 1725 |
+
329.6882 378.9007 330.5392 378.9444 331.3247 379.0971 c
|
| 1726 |
+
331.3247 364.3686 L
|
| 1727 |
+
332.6339 364.3686 L
|
| 1728 |
+
332.6339 363.8667 L
|
| 1729 |
+
328.4881 363.8667 l
|
| 1730 |
+
f
|
| 1731 |
+
*U
|
| 1732 |
+
u
|
| 1733 |
+
309.5341 446.5364 m
|
| 1734 |
+
305.6878 429.3874 306.7947 401.5837 v
|
| 1735 |
+
307.1266 393.2441 308.0387 385.5779 309.1527 378.9301 C
|
| 1736 |
+
309.1587 378.9297 L
|
| 1737 |
+
309.8832 373.0923 310.3679 370.9791 312.2568 363.9454 C
|
| 1738 |
+
312.1466 359.4091 L
|
| 1739 |
+
297.0216 407.7015 309.5341 446.5364 V
|
| 1740 |
+
f
|
| 1741 |
+
318.8187 461.4058 m
|
| 1742 |
+
322.2203 463.1 327.0966 463.7165 v
|
| 1743 |
+
332.427 453.9463 319.3087 437.2655 v
|
| 1744 |
+
327.1346 454.735 325.2889 460.2079 v
|
| 1745 |
+
323.225 461.4903 318.8187 461.4058 v
|
| 1746 |
+
f
|
| 1747 |
+
317.2065 432.0795 m
|
| 1748 |
+
320.2613 431.3723 321.7279 432.5601 v
|
| 1749 |
+
318.8383 421.2839 319.5958 415.0813 v
|
| 1750 |
+
320.3533 408.8787 314.8881 404.9079 y
|
| 1751 |
+
319.5435 410.7982 318.0802 415.5959 v
|
| 1752 |
+
317.0657 418.9214 318.2006 427.4326 319.4809 430.1349 c
|
| 1753 |
+
318.2853 430.3025 317.2065 432.0795 v
|
| 1754 |
+
f
|
| 1755 |
+
314.1861 402.3703 m
|
| 1756 |
+
319.2343 402.9744 319.7646 405.5244 v
|
| 1757 |
+
320.3824 390.2725 313.3689 383.9873 v
|
| 1758 |
+
318.7204 392.3347 317.8807 400.9697 v
|
| 1759 |
+
314.1861 402.3703 l
|
| 1760 |
+
f
|
| 1761 |
+
299.9864 396.0219 m
|
| 1762 |
+
298.3586 394.1986 293.4739 398.2203 v
|
| 1763 |
+
295.0301 387.9694 304.6978 383.2767 v
|
| 1764 |
+
298.0444 388.2897 296.2519 393.7045 v
|
| 1765 |
+
298.6029 394.3966 299.9864 396.0219 v
|
| 1766 |
+
f
|
| 1767 |
+
298.4281 399.9096 m
|
| 1768 |
+
291.8229 416.6749 293.2382 439.3286 v
|
| 1769 |
+
294.7808 435.2261 299.738 433.7875 v
|
| 1770 |
+
297.4026 433.3101 296.0372 433.517 v
|
| 1771 |
+
292.5816 423.9535 298.4281 399.9096 v
|
| 1772 |
+
f
|
| 1773 |
+
326.1736 477.812 m
|
| 1774 |
+
323.6983 496.0028 308.2122 477.6066 v
|
| 1775 |
+
295.8813 462.9582 297.3508 450.5217 298.1072 443.5831 c
|
| 1776 |
+
298.3007 441.8079 295.8131 462.1138 309.3231 475.4768 c
|
| 1777 |
+
322.8328 488.8398 325.8846 478.5879 326.1736 477.812 c
|
| 1778 |
+
f
|
| 1779 |
+
U
|
| 1780 |
+
0 0 1 0 k
|
| 1781 |
+
303.3623 493.3274 m
|
| 1782 |
+
291.211 496.7978 287.3437 456.5222 v
|
| 1783 |
+
284.3599 468.9535 292.0777 486.5353 v
|
| 1784 |
+
299.7955 504.1172 303.3623 493.3274 y
|
| 1785 |
+
f
|
| 1786 |
+
288.2873 496.2718 m
|
| 1787 |
+
282.0897 486.9502 283.4958 477.0213 v
|
| 1788 |
+
278.7953 495.712 288.2873 496.2718 v
|
| 1789 |
+
f
|
| 1790 |
+
333.8987 470.1328 m
|
| 1791 |
+
341.2276 472.8361 330.7334 445.5571 v
|
| 1792 |
+
336.1654 453.5292 339.5844 466.0531 v
|
| 1793 |
+
341.7789 474.0903 333.8987 470.1328 y
|
| 1794 |
+
f
|
| 1795 |
+
345.752 472.2583 m
|
| 1796 |
+
350.9334 467.5681 347.2615 461.3636 v
|
| 1797 |
+
356.4779 471.0481 345.752 472.2583 v
|
| 1798 |
+
f
|
| 1799 |
+
U
|
| 1800 |
+
*u
|
| 1801 |
+
273.1765 354.3318 m
|
| 1802 |
+
273.1765 353.7507 273.1305 353.2908 272.5159 353.2908 c
|
| 1803 |
+
271.8846 353.2908 271.8554 353.7674 271.8554 354.3318 c
|
| 1804 |
+
271.8554 356.485 L
|
| 1805 |
+
272.148 356.485 L
|
| 1806 |
+
272.148 354.3486 L
|
| 1807 |
+
272.148 353.8259 272.1773 353.5751 272.5159 353.5751 c
|
| 1808 |
+
272.8504 353.5751 272.8839 353.8259 272.8839 354.3486 c
|
| 1809 |
+
272.8839 356.485 L
|
| 1810 |
+
273.1765 356.485 L
|
| 1811 |
+
273.1765 354.3318 l
|
| 1812 |
+
f
|
| 1813 |
+
*U
|
| 1814 |
+
*u
|
| 1815 |
+
277.1612 356.485 m
|
| 1816 |
+
276.9062 356.485 L
|
| 1817 |
+
276.9062 354.3862 l
|
| 1818 |
+
276.9062 354.2482 276.9271 354.1061 276.9355 353.9681 C
|
| 1819 |
+
276.9229 353.9681 l
|
| 1820 |
+
276.8937 354.0768 276.8644 354.1855 276.8268 354.2942 C
|
| 1821 |
+
276.1035 356.485 L
|
| 1822 |
+
275.8484 356.485 L
|
| 1823 |
+
275.8484 353.3326 L
|
| 1824 |
+
276.1035 353.3326 L
|
| 1825 |
+
276.1035 355.2474 l
|
| 1826 |
+
276.1035 355.4523 276.0826 355.653 276.07 355.8579 C
|
| 1827 |
+
276.0867 355.8579 l
|
| 1828 |
+
276.1244 355.7241 276.1495 355.5819 276.1954 355.4523 C
|
| 1829 |
+
276.9062 353.3326 L
|
| 1830 |
+
277.1612 353.3326 l
|
| 1831 |
+
277.1612 356.485 L
|
| 1832 |
+
f
|
| 1833 |
+
*U
|
| 1834 |
+
*u
|
| 1835 |
+
280.1421 353.3326 m
|
| 1836 |
+
279.8494 353.3326 L
|
| 1837 |
+
279.8494 356.485 L
|
| 1838 |
+
280.1421 356.485 L
|
| 1839 |
+
280.1421 353.3326 l
|
| 1840 |
+
f
|
| 1841 |
+
*U
|
| 1842 |
+
*u
|
| 1843 |
+
283.5141 353.3326 m
|
| 1844 |
+
283.2549 353.3326 L
|
| 1845 |
+
282.6194 356.485 L
|
| 1846 |
+
282.9205 356.485 L
|
| 1847 |
+
283.3344 354.1897 L
|
| 1848 |
+
283.3511 354.1102 283.3678 353.9054 283.3845 353.7632 c
|
| 1849 |
+
283.4013 353.7632 L
|
| 1850 |
+
283.4138 353.9054 283.4305 354.1144 283.4431 354.1897 c
|
| 1851 |
+
283.8528 356.485 L
|
| 1852 |
+
284.1496 356.485 L
|
| 1853 |
+
283.5141 353.3326 l
|
| 1854 |
+
f
|
| 1855 |
+
*U
|
| 1856 |
+
*u
|
| 1857 |
+
287.6238 356.2174 m
|
| 1858 |
+
286.9256 356.2174 L
|
| 1859 |
+
286.9256 355.1053 L
|
| 1860 |
+
287.6029 355.1053 L
|
| 1861 |
+
287.6029 354.8377 L
|
| 1862 |
+
286.9256 354.8377 L
|
| 1863 |
+
286.9256 353.6002 L
|
| 1864 |
+
287.6238 353.6002 L
|
| 1865 |
+
287.6238 353.3326 L
|
| 1866 |
+
286.6329 353.3326 L
|
| 1867 |
+
286.6329 356.485 L
|
| 1868 |
+
287.6238 356.485 L
|
| 1869 |
+
287.6238 356.2174 l
|
| 1870 |
+
f
|
| 1871 |
+
*U
|
| 1872 |
+
*u
|
| 1873 |
+
290.2278 353.3326 m
|
| 1874 |
+
290.2278 356.485 L
|
| 1875 |
+
290.5414 356.485 L
|
| 1876 |
+
290.9804 356.485 291.4026 356.4515 291.4026 355.6823 c
|
| 1877 |
+
291.4026 355.2809 291.3148 354.8879 290.8089 354.8712 c
|
| 1878 |
+
291.5072 353.3326 L
|
| 1879 |
+
291.1978 353.3326 L
|
| 1880 |
+
290.5288 354.8753 L
|
| 1881 |
+
290.5205 354.8753 L
|
| 1882 |
+
290.5205 353.3326 L
|
| 1883 |
+
290.2278 353.3326 l
|
| 1884 |
+
f
|
| 1885 |
+
290.5205 355.1137 m
|
| 1886 |
+
290.625 355.1137 L
|
| 1887 |
+
291.0347 355.1137 291.1016 355.2558 291.1016 355.6697 c
|
| 1888 |
+
291.1016 356.1672 290.9511 356.2174 290.579 356.2174 c
|
| 1889 |
+
290.5205 356.2174 L
|
| 1890 |
+
290.5205 355.1137 l
|
| 1891 |
+
f
|
| 1892 |
+
*U
|
| 1893 |
+
*u
|
| 1894 |
+
295.0981 355.9875 m
|
| 1895 |
+
294.9727 356.1296 294.8347 356.2425 294.634 356.2425 c
|
| 1896 |
+
294.3414 356.2425 294.1783 356 294.1783 355.7324 c
|
| 1897 |
+
294.1783 355.3645 294.4459 355.1931 294.7176 355.0091 c
|
| 1898 |
+
294.9852 354.821 295.2528 354.6203 295.2528 354.1855 c
|
| 1899 |
+
295.2528 353.7256 294.9559 353.2908 294.4626 353.2908 c
|
| 1900 |
+
294.287 353.2908 294.1072 353.341 293.9651 353.4497 c
|
| 1901 |
+
293.9651 353.8301 L
|
| 1902 |
+
294.0989 353.688 294.2745 353.5751 294.4751 353.5751 c
|
| 1903 |
+
294.7845 353.5751 294.9559 353.8468 294.9518 354.1311 c
|
| 1904 |
+
294.9559 354.4991 294.6842 354.6621 294.4166 354.8503 c
|
| 1905 |
+
294.149 355.0342 293.8773 355.2391 293.8773 355.6906 c
|
| 1906 |
+
293.8773 356.1129 294.1365 356.5268 294.6006 356.5268 c
|
| 1907 |
+
294.7887 356.5268 294.9476 356.4641 295.0981 356.3596 C
|
| 1908 |
+
295.0981 355.9875 l
|
| 1909 |
+
f
|
| 1910 |
+
*U
|
| 1911 |
+
*u
|
| 1912 |
+
299.0865 353.3326 m
|
| 1913 |
+
298.773 353.3326 L
|
| 1914 |
+
298.6559 353.9806 L
|
| 1915 |
+
297.9869 353.9806 L
|
| 1916 |
+
297.8741 353.3326 L
|
| 1917 |
+
297.5605 353.3326 L
|
| 1918 |
+
298.1793 356.485 L
|
| 1919 |
+
298.4552 356.485 L
|
| 1920 |
+
299.0865 353.3326 l
|
| 1921 |
+
f
|
| 1922 |
+
298.6099 354.2357 m
|
| 1923 |
+
298.4009 355.444 L
|
| 1924 |
+
298.3632 355.6572 298.3465 355.8746 298.3214 356.0878 c
|
| 1925 |
+
298.3047 356.0878 L
|
| 1926 |
+
298.2754 355.8746 298.2545 355.6572 298.2211 355.444 c
|
| 1927 |
+
298.0371 354.2357 L
|
| 1928 |
+
298.6099 354.2357 l
|
| 1929 |
+
f
|
| 1930 |
+
*U
|
| 1931 |
+
*u
|
| 1932 |
+
301.8124 353.6002 m
|
| 1933 |
+
302.4981 353.6002 L
|
| 1934 |
+
302.4981 353.3326 L
|
| 1935 |
+
301.5198 353.3326 L
|
| 1936 |
+
301.5198 356.485 L
|
| 1937 |
+
301.8124 356.485 L
|
| 1938 |
+
301.8124 353.6002 l
|
| 1939 |
+
f
|
| 1940 |
+
*U
|
| 1941 |
+
*u
|
| 1942 |
+
309.0754 355.9875 m
|
| 1943 |
+
308.95 356.1296 308.812 356.2425 308.6114 356.2425 c
|
| 1944 |
+
308.3187 356.2425 308.1556 356 308.1556 355.7324 c
|
| 1945 |
+
308.1556 355.3645 308.4232 355.1931 308.695 355.0091 c
|
| 1946 |
+
308.9626 354.821 309.2301 354.6203 309.2301 354.1855 c
|
| 1947 |
+
309.2301 353.7256 308.9333 353.2908 308.4399 353.2908 c
|
| 1948 |
+
308.2643 353.2908 308.0846 353.341 307.9424 353.4497 c
|
| 1949 |
+
307.9424 353.8301 L
|
| 1950 |
+
308.0762 353.688 308.2518 353.5751 308.4525 353.5751 c
|
| 1951 |
+
308.7619 353.5751 308.9333 353.8468 308.9291 354.1311 c
|
| 1952 |
+
308.9333 354.4991 308.6615 354.6621 308.3939 354.8503 c
|
| 1953 |
+
308.1264 355.0342 307.8546 355.2391 307.8546 355.6906 c
|
| 1954 |
+
307.8546 356.1129 308.1138 356.5268 308.5779 356.5268 c
|
| 1955 |
+
308.766 356.5268 308.9249 356.4641 309.0754 356.3596 C
|
| 1956 |
+
309.0754 355.9875 l
|
| 1957 |
+
f
|
| 1958 |
+
*U
|
| 1959 |
+
*u
|
| 1960 |
+
312.9468 353.7172 m
|
| 1961 |
+
312.8339 353.6378 312.7001 353.5751 312.558 353.5751 c
|
| 1962 |
+
311.9977 353.5751 311.9977 354.5492 311.9977 354.9172 c
|
| 1963 |
+
311.9977 355.5025 312.0688 356.2425 312.5789 356.2425 c
|
| 1964 |
+
312.7252 356.2425 312.8297 356.184 312.9468 356.1045 C
|
| 1965 |
+
312.9468 356.4265 l
|
| 1966 |
+
312.8506 356.4975 312.6918 356.5268 312.5747 356.5268 c
|
| 1967 |
+
311.7134 356.5268 311.6967 355.306 311.6967 354.7959 c
|
| 1968 |
+
311.6967 354.2566 311.8054 353.2908 312.5454 353.2908 c
|
| 1969 |
+
312.6834 353.2908 312.8381 353.3451 312.9468 353.4204 c
|
| 1970 |
+
312.9468 353.7172 L
|
| 1971 |
+
f
|
| 1972 |
+
*U
|
| 1973 |
+
*u
|
| 1974 |
+
315.5053 353.3326 m
|
| 1975 |
+
315.5053 356.485 L
|
| 1976 |
+
315.8188 356.485 L
|
| 1977 |
+
316.2578 356.485 316.6801 356.4515 316.6801 355.6823 c
|
| 1978 |
+
316.6801 355.2809 316.5923 354.8879 316.0864 354.8712 c
|
| 1979 |
+
316.7846 353.3326 L
|
| 1980 |
+
316.4752 353.3326 L
|
| 1981 |
+
315.8063 354.8753 L
|
| 1982 |
+
315.7979 354.8753 L
|
| 1983 |
+
315.7979 353.3326 L
|
| 1984 |
+
315.5053 353.3326 l
|
| 1985 |
+
f
|
| 1986 |
+
315.7979 355.1137 m
|
| 1987 |
+
315.9025 355.1137 L
|
| 1988 |
+
316.3122 355.1137 316.3791 355.2558 316.3791 355.6697 c
|
| 1989 |
+
316.3791 356.1672 316.2286 356.2174 315.8565 356.2174 c
|
| 1990 |
+
315.7979 356.2174 L
|
| 1991 |
+
315.7979 355.1137 l
|
| 1992 |
+
f
|
| 1993 |
+
*U
|
| 1994 |
+
*u
|
| 1995 |
+
319.5728 353.3326 m
|
| 1996 |
+
319.2802 353.3326 L
|
| 1997 |
+
319.2802 356.485 L
|
| 1998 |
+
319.5728 356.485 L
|
| 1999 |
+
319.5728 353.3326 l
|
| 2000 |
+
f
|
| 2001 |
+
*U
|
| 2002 |
+
*u
|
| 2003 |
+
322.2551 353.3326 m
|
| 2004 |
+
322.2551 356.485 L
|
| 2005 |
+
322.5812 356.485 L
|
| 2006 |
+
323.0327 356.485 323.4341 356.4432 323.4341 355.6655 c
|
| 2007 |
+
323.4341 355.0551 323.2209 354.8419 322.623 354.8419 c
|
| 2008 |
+
322.5477 354.8419 L
|
| 2009 |
+
322.5477 353.3326 L
|
| 2010 |
+
322.2551 353.3326 l
|
| 2011 |
+
f
|
| 2012 |
+
322.5477 355.1095 m
|
| 2013 |
+
322.6606 355.1095 L
|
| 2014 |
+
323.0703 355.1095 323.1205 355.26 323.1331 355.6655 c
|
| 2015 |
+
323.1331 356.1004 323.016 356.2174 322.6063 356.2174 c
|
| 2016 |
+
322.5477 356.2174 L
|
| 2017 |
+
322.5477 355.1095 l
|
| 2018 |
+
f
|
| 2019 |
+
*U
|
| 2020 |
+
*u
|
| 2021 |
+
326.9539 356.485 m
|
| 2022 |
+
325.7164 356.485 L
|
| 2023 |
+
325.7164 356.2174 L
|
| 2024 |
+
326.1888 356.2174 L
|
| 2025 |
+
326.1888 353.3326 L
|
| 2026 |
+
326.4815 353.3326 L
|
| 2027 |
+
326.4815 356.2174 L
|
| 2028 |
+
326.9539 356.2174 l
|
| 2029 |
+
326.9539 356.485 L
|
| 2030 |
+
f
|
| 2031 |
+
*U
|
| 2032 |
+
*u
|
| 2033 |
+
329.7077 353.3326 m
|
| 2034 |
+
329.4151 353.3326 L
|
| 2035 |
+
329.4151 356.485 L
|
| 2036 |
+
329.7077 356.485 L
|
| 2037 |
+
329.7077 353.3326 l
|
| 2038 |
+
f
|
| 2039 |
+
*U
|
| 2040 |
+
*u
|
| 2041 |
+
333.7028 353.3326 m
|
| 2042 |
+
333.4477 353.3326 L
|
| 2043 |
+
332.737 355.4523 L
|
| 2044 |
+
332.691 355.5819 332.6659 355.7241 332.6283 355.8579 c
|
| 2045 |
+
332.6116 355.8579 L
|
| 2046 |
+
332.6241 355.653 332.645 355.4523 332.645 355.2474 c
|
| 2047 |
+
332.645 353.3326 L
|
| 2048 |
+
332.39 353.3326 L
|
| 2049 |
+
332.39 356.485 L
|
| 2050 |
+
332.645 356.485 L
|
| 2051 |
+
333.3683 354.2942 L
|
| 2052 |
+
333.4059 354.1855 333.4352 354.0768 333.4645 353.9681 c
|
| 2053 |
+
333.477 353.9681 L
|
| 2054 |
+
333.4686 354.1061 333.4477 354.2482 333.4477 354.3862 c
|
| 2055 |
+
333.4477 356.485 L
|
| 2056 |
+
333.7028 356.485 L
|
| 2057 |
+
333.7028 353.3326 l
|
| 2058 |
+
f
|
| 2059 |
+
*U
|
| 2060 |
+
*u
|
| 2061 |
+
336.9846 354.9966 m
|
| 2062 |
+
337.7037 354.9966 L
|
| 2063 |
+
337.7037 354.4154 L
|
| 2064 |
+
337.7037 353.9179 337.6787 353.2908 337.0264 353.2908 c
|
| 2065 |
+
336.3617 353.2908 336.299 353.989 336.299 354.9841 c
|
| 2066 |
+
336.299 355.7283 336.3868 356.5268 337.0557 356.5268 c
|
| 2067 |
+
337.432 356.5268 337.6201 356.276 337.6996 355.9331 c
|
| 2068 |
+
337.4111 355.8202 L
|
| 2069 |
+
337.3776 356.0084 337.2982 356.2425 337.0682 356.2425 c
|
| 2070 |
+
336.6334 356.2383 336.6 355.5652 336.6 355.0091 c
|
| 2071 |
+
336.6 353.8427 336.7463 353.5751 337.0515 353.5751 c
|
| 2072 |
+
337.3818 353.5751 337.4111 353.8176 337.4111 354.4907 c
|
| 2073 |
+
337.4111 354.729 L
|
| 2074 |
+
336.9846 354.729 L
|
| 2075 |
+
336.9846 354.9966 l
|
| 2076 |
+
f
|
| 2077 |
+
*U
|
| 2078 |
+
U
|
| 2079 |
+
U
|
| 2080 |
+
337.6667 -3924 m
|
| 2081 |
+
(N) *
|
| 2082 |
+
337.6667 4716 m
|
| 2083 |
+
(N) *
|
| 2084 |
+
LB
|
| 2085 |
+
%AI5_EndLayer--
|
| 2086 |
+
%%PageTrailer
|
| 2087 |
+
gsave annotatepage grestore showpage
|
| 2088 |
+
%%Trailer
|
| 2089 |
+
Adobe_IllustratorA_AI5 /terminate get exec
|
| 2090 |
+
Adobe_level2_AI5 /terminate get exec
|
| 2091 |
+
%%EOF
|
my_container_sandbox/workspace/anaconda3/lib/tk8.6/images/logo100.gif
ADDED
|