Repository: venaxyt/mysterium Branch: main Commit: a8cff3064319 Files: 68 Total size: 586.1 KB Directory structure: gitextract_heteqtr4/ ├── LICENSE ├── README.md ├── executable/ │ └── pyinstxtractor.py ├── modules/ │ ├── blue.py │ ├── dhooks.py │ ├── fade.py │ ├── getmac.py │ ├── gratient.py │ ├── json.py │ ├── playsound.py │ ├── pycenter.py │ ├── pyfade.py │ ├── pyproxies.py │ ├── random.py │ ├── re.py │ ├── requests/ │ │ ├── __init__.py │ │ ├── __version__.py │ │ ├── _internal_utils.py │ │ ├── adapters.py │ │ ├── api.py │ │ ├── auth.py │ │ ├── certs.py │ │ ├── compat.py │ │ ├── cookies.py │ │ ├── exceptions.py │ │ ├── help.py │ │ ├── hooks.py │ │ ├── models.py │ │ ├── packages.py │ │ ├── sessions.py │ │ ├── status_codes.py │ │ ├── structures.py │ │ └── utils.py │ ├── tkinter/ │ │ ├── colorchooser.py │ │ ├── commondialog.py │ │ ├── constants.py │ │ ├── dialog.py │ │ ├── dnd.py │ │ ├── filedialog.py │ │ ├── font.py │ │ ├── messagebox.py │ │ ├── scrolledtext.py │ │ ├── simpledialog.py │ │ └── test/ │ │ ├── README │ │ ├── runtktests.py │ │ ├── support.py │ │ └── test_tkinter/ │ │ ├── test_colorchooser.py │ │ ├── test_font.py │ │ ├── test_geometry_managers.py │ │ ├── test_images.py │ │ ├── test_loadtk.py │ │ ├── test_misc.py │ │ ├── test_simpledialog.py │ │ ├── test_text.py │ │ ├── test_variables.py │ │ └── test_widgets.py │ └── urllib3/ │ ├── connection.py │ ├── connectionpool.py │ └── contrib/ │ ├── _appengine_environ.py │ ├── _securetransport/ │ │ ├── bindings.py │ │ └── low_level.py │ ├── appengine.py │ ├── ntlmpool.py │ ├── pyopenssl.py │ ├── securetransport.py │ └── socks.py ├── mysterium.py └── requirements.txt ================================================ FILE CONTENTS ================================================ ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2021 Venax Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # ­ ­ ­ ­ ­ **𝙈 ­ ­ ­ ­ ­ ­ ­ 𝙔 ­ ­ ­ ­ ­ ­ ­ 𝙎 ­ ­ ­ ­ ­ ­ ­ 𝙏 ­ ­ ­ ­ ­ ­ ­ 𝙀 ­ ­ ­ ­ ­ ­ ­ 𝙍 ­ ­ ­ ­ ­ ­ ­ 𝙄 ­ ­ ­ ­ ­ ­ ­ 𝙐 ­ ­ ­ ­ ­ ­ ­ 𝙈** ![mysterium_logo (1)](https://user-images.githubusercontent.com/81310818/132258721-dc02bb73-772c-4530-a636-4daffbcdc23a.png) ``` Mysterium has been entirely made by @venaxyt. ``` ``` Mysterium usage: - If the file to be inspect extension is ".py" or ".pyc": You only have to drag it into Mysterium. - If the file to be inspect extension is ".exe": If you don't know how to decompile an executable Python file, so you can wait for next Mysterium updates. - If the file to be inspect is obfuscated with Pyarmor: You put the Python file and the pytransform folder into a zip file, drag it into Mysterium and input the Python file name. ``` ![mysterium](https://user-images.githubusercontent.com/81310818/132141525-0bfb0f6e-a0d4-4770-8861-97622160baff.PNG) ### **YouTube Tutorial (FR): https://www.youtube.com/watch?v=1idhXNeluCc**
> ### **Contact me on Telegram if you want to buy my PyArmor complete decryption tool (200$): https://t.me/srevna**
### **All I ask is that you don't contact me for nothing, I remind you that the tool is chargeable and that I don't provide anything until we've completed the transaction, too many people have ripped me off or wasted my time, whether it's for screenshots or anything else, so I don't do it again until payment has been made. Thank you for your understanding. (Because of this type of people: https://github.com/venaxyt/mysterium/blob/main/just%20an%20example.jpg)**
### **VIDEO Tutorial (FR): https://www.youtube.com/watch?v=1idhXNeluCc**
https://t.me/blatoise (The tool costs 200$) (!!Discounted to 150$ until the end of the year)
> ## **V ­ ­ E ­ ­ N ­ ­ A ­ ­ X** ================================================ FILE: executable/pyinstxtractor.py ================================================ """ PyInstaller Extractor v2.0 (Supports pyinstaller 4.6, 4.5.1, 4.5, 4.4, 4.3, 4.2, 4.1, 4.0, 3.6, 3.5, 3.4, 3.3, 3.2, 3.1, 3.0, 2.1, 2.0) Author : Extreme Coders E-mail : extremecoders(at)hotmail(dot)com Web : https://0xec.blogspot.com Date : 26-March-2020 Url : https://github.com/extremecoders-re/pyinstxtractor For any suggestions, leave a comment on https://forum.tuts4you.com/topic/34455-pyinstaller-extractor/ This script extracts a pyinstaller generated executable file. Pyinstaller installation is not needed. The script has it all. For best results, it is recommended to run this script in the same version of python as was used to create the executable. This is just to prevent unmarshalling errors(if any) while extracting the PYZ archive. Usage : Just copy this script to the directory where your exe resides and run the script with the exe file name as a parameter C:\path\to\exe\>python pyinstxtractor.py $ /path/to/exe/python pyinstxtractor.py Licensed under GNU General Public License (GPL) v3. You are free to modify this source. CHANGELOG ================================================ Version 1.1 (Jan 28, 2014) ------------------------------------------------- - First Release - Supports only pyinstaller 2.0 Version 1.2 (Sept 12, 2015) ------------------------------------------------- - Added support for pyinstaller 2.1 and 3.0 dev - Cleaned up code - Script is now more verbose - Executable extracted within a dedicated sub-directory (Support for pyinstaller 3.0 dev is experimental) Version 1.3 (Dec 12, 2015) ------------------------------------------------- - Added support for pyinstaller 3.0 final - Script is compatible with both python 2.x & 3.x (Thanks to Moritz Kroll @ Avira Operations GmbH & Co. KG) Version 1.4 (Jan 19, 2016) ------------------------------------------------- - Fixed a bug when writing pyc files >= version 3.3 (Thanks to Daniello Alto: https://github.com/Djamana) Version 1.5 (March 1, 2016) ------------------------------------------------- - Added support for pyinstaller 3.1 (Thanks to Berwyn Hoyt for reporting) Version 1.6 (Sept 5, 2016) ------------------------------------------------- - Added support for pyinstaller 3.2 - Extractor will use a random name while extracting unnamed files. - For encrypted pyz archives it will dump the contents as is. Previously, the tool would fail. Version 1.7 (March 13, 2017) ------------------------------------------------- - Made the script compatible with python 2.6 (Thanks to Ross for reporting) Version 1.8 (April 28, 2017) ------------------------------------------------- - Support for sub-directories in .pyz files (Thanks to Moritz Kroll @ Avira Operations GmbH & Co. KG) Version 1.9 (November 29, 2017) ------------------------------------------------- - Added support for pyinstaller 3.3 - Display the scripts which are run at entry (Thanks to Michael Gillespie @ malwarehunterteam for the feature request) Version 2.0 (March 26, 2020) ------------------------------------------------- - Project migrated to github - Supports pyinstaller 3.6 - Added support for Python 3.7, 3.8 - The header of all extracted pyc's are now automatically fixed """ from __future__ import print_function import os import struct import marshal import zlib import sys from uuid import uuid4 as uniquename # imp is deprecated in Python3 in favour of importlib if sys.version_info.major == 3: from importlib.util import MAGIC_NUMBER pyc_magic = MAGIC_NUMBER else: import imp pyc_magic = imp.get_magic() class CTOCEntry: def __init__(self, position, cmprsdDataSize, uncmprsdDataSize, cmprsFlag, typeCmprsData, name): self.position = position self.cmprsdDataSize = cmprsdDataSize self.uncmprsdDataSize = uncmprsdDataSize self.cmprsFlag = cmprsFlag self.typeCmprsData = typeCmprsData self.name = name class PyInstArchive: PYINST20_COOKIE_SIZE = 24 # For pyinstaller 2.0 PYINST21_COOKIE_SIZE = 24 + 64 # For pyinstaller 2.1+ MAGIC = b'MEI\014\013\012\013\016' # Magic number which identifies pyinstaller def __init__(self, path): self.filePath = path def open(self): try: self.fPtr = open(self.filePath, 'rb') self.fileSize = os.stat(self.filePath).st_size except: print('[!] Error: Could not open {0}'.format(self.filePath)) return False return True def close(self): try: self.fPtr.close() except: pass def checkFile(self): print('[+] Processing {0}'.format(self.filePath)) searchChunkSize = 8192 endPos = self.fileSize self.cookiePos = -1 if endPos < len(self.MAGIC): print('[!] Error : File is too short or truncated') return False while True: startPos = endPos - searchChunkSize if endPos >= searchChunkSize else 0 chunkSize = endPos - startPos if chunkSize < len(self.MAGIC): break self.fPtr.seek(startPos, os.SEEK_SET) data = self.fPtr.read(chunkSize) offs = data.rfind(self.MAGIC) if offs != -1: self.cookiePos = startPos + offs break endPos = startPos + len(self.MAGIC) - 1 if startPos == 0: break if self.cookiePos == -1: print('[!] Error : Missing cookie, unsupported pyinstaller version or not a pyinstaller archive') return False self.fPtr.seek(self.cookiePos + self.PYINST20_COOKIE_SIZE, os.SEEK_SET) if b'python' in self.fPtr.read(64): print('[+] Pyinstaller version: 2.1+') self.pyinstVer = 21 # pyinstaller 2.1+ else: self.pyinstVer = 20 # pyinstaller 2.0 print('[+] Pyinstaller version: 2.0') return True def getCArchiveInfo(self): try: if self.pyinstVer == 20: self.fPtr.seek(self.cookiePos, os.SEEK_SET) # Read CArchive cookie (magic, lengthofPackage, toc, tocLen, self.pyver) = \ struct.unpack('!8siiii', self.fPtr.read(self.PYINST20_COOKIE_SIZE)) elif self.pyinstVer == 21: self.fPtr.seek(self.cookiePos, os.SEEK_SET) # Read CArchive cookie (magic, lengthofPackage, toc, tocLen, self.pyver, pylibname) = \ struct.unpack('!8siiii64s', self.fPtr.read(self.PYINST21_COOKIE_SIZE)) except: print('[!] Error : The file is not a pyinstaller archive') return False print('[+] Python version: {0}'.format(self.pyver)) # Additional data after the cookie tailBytes = self.fileSize - self.cookiePos - (self.PYINST20_COOKIE_SIZE if self.pyinstVer == 20 else self.PYINST21_COOKIE_SIZE) # Overlay is the data appended at the end of the PE self.overlaySize = lengthofPackage + tailBytes self.overlayPos = self.fileSize - self.overlaySize self.tableOfContentsPos = self.overlayPos + toc self.tableOfContentsSize = tocLen print('[+] Length of package: {0} bytes'.format(lengthofPackage)) return True def parseTOC(self): # Go to the table of contents self.fPtr.seek(self.tableOfContentsPos, os.SEEK_SET) self.tocList = [] parsedLen = 0 # Parse table of contents while parsedLen < self.tableOfContentsSize: (entrySize, ) = struct.unpack('!i', self.fPtr.read(4)) nameLen = struct.calcsize('!iiiiBc') (entryPos, cmprsdDataSize, uncmprsdDataSize, cmprsFlag, typeCmprsData, name) = \ struct.unpack( \ '!iiiBc{0}s'.format(entrySize - nameLen), \ self.fPtr.read(entrySize - 4)) name = name.decode('utf-8').rstrip('\0') if len(name) == 0: name = str(uniquename()) print('[!] Warning: Found an unamed file in CArchive. Using random name {0}'.format(name)) self.tocList.append( \ CTOCEntry( \ self.overlayPos + entryPos, \ cmprsdDataSize, \ uncmprsdDataSize, \ cmprsFlag, \ typeCmprsData, \ name \ )) parsedLen += entrySize print('[+] Found {0} files in CArchive'.format(len(self.tocList))) def _writeRawData(self, filepath, data): nm = filepath.replace('\\', os.path.sep).replace('/', os.path.sep).replace('..', '__') nmDir = os.path.dirname(nm) if nmDir != '' and not os.path.exists(nmDir): # Check if path exists, create if not os.makedirs(nmDir) with open(nm, 'wb') as f: f.write(data) def extractFiles(self): print('[+] Beginning extraction...please standby') extractionDir = os.path.join(os.getcwd(), os.path.basename(self.filePath) + '_extracted') if not os.path.exists(extractionDir): os.mkdir(extractionDir) os.chdir(extractionDir) for entry in self.tocList: basePath = os.path.dirname(entry.name) if basePath != '': # Check if path exists, create if not if not os.path.exists(basePath): os.makedirs(basePath) self.fPtr.seek(entry.position, os.SEEK_SET) data = self.fPtr.read(entry.cmprsdDataSize) if entry.cmprsFlag == 1: data = zlib.decompress(data) # Malware may tamper with the uncompressed size # Comment out the assertion in such a case assert len(data) == entry.uncmprsdDataSize # Sanity Check if entry.typeCmprsData == b's': # s -> ARCHIVE_ITEM_PYSOURCE # Entry point are expected to be python scripts print('[+] Possible entry point: {0}.pyc'.format(entry.name)) self._writePyc(entry.name + '.pyc', data) elif entry.typeCmprsData == b'M' or entry.typeCmprsData == b'm': # M -> ARCHIVE_ITEM_PYPACKAGE # m -> ARCHIVE_ITEM_PYMODULE # packages and modules are pyc files with their header's intact self._writeRawData(entry.name + '.pyc', data) else: self._writeRawData(entry.name, data) if entry.typeCmprsData == b'z' or entry.typeCmprsData == b'Z': self._extractPyz(entry.name) def _writePyc(self, filename, data): with open(filename, 'wb') as pycFile: pycFile.write(pyc_magic) # pyc magic if self.pyver >= 37: # PEP 552 -- Deterministic pycs pycFile.write(b'\0' * 4) # Bitfield pycFile.write(b'\0' * 8) # (Timestamp + size) || hash else: pycFile.write(b'\0' * 4) # Timestamp if self.pyver >= 33: pycFile.write(b'\0' * 4) # Size parameter added in Python 3.3 pycFile.write(data) def _extractPyz(self, name): dirName = name + '_extracted' # Create a directory for the contents of the pyz if not os.path.exists(dirName): os.mkdir(dirName) with open(name, 'rb') as f: pyzMagic = f.read(4) assert pyzMagic == b'PYZ\0' # Sanity Check pycHeader = f.read(4) # Python magic value # Skip PYZ extraction if not running under the same python version if pyc_magic != pycHeader: print('[!] Warning: This script is running in a different Python version than the one used to build the executable.') print('[!] Please run this script in Python{0} to prevent extraction errors during unmarshalling'.format(self.pyver)) print('[!] Skipping pyz extraction') return (tocPosition, ) = struct.unpack('!i', f.read(4)) f.seek(tocPosition, os.SEEK_SET) try: toc = marshal.load(f) except: print('[!] Unmarshalling FAILED. Cannot extract {0}. Extracting remaining files.'.format(name)) return print('[+] Found {0} files in PYZ archive'.format(len(toc))) # From pyinstaller 3.1+ toc is a list of tuples if type(toc) == list: toc = dict(toc) for key in toc.keys(): (ispkg, pos, length) = toc[key] f.seek(pos, os.SEEK_SET) fileName = key try: # for Python > 3.3 some keys are bytes object some are str object fileName = fileName.decode('utf-8') except: pass # Prevent writing outside dirName fileName = fileName.replace('..', '__').replace('.', os.path.sep) if ispkg == 1: filePath = os.path.join(dirName, fileName, '__init__.pyc') else: filePath = os.path.join(dirName, fileName + '.pyc') fileDir = os.path.dirname(filePath) if not os.path.exists(fileDir): os.makedirs(fileDir) try: data = f.read(length) data = zlib.decompress(data) except: print('[!] Error: Failed to decompress {0}, probably encrypted. Extracting as is.'.format(filePath)) open(filePath + '.encrypted', 'wb').write(data) else: self._writePyc(filePath, data) def main(): if len(sys.argv) < 2: print('[+] Usage: pyinstxtractor.py ') else: arch = PyInstArchive(sys.argv[1]) if arch.open(): if arch.checkFile(): if arch.getCArchiveInfo(): arch.parseTOC() arch.extractFiles() arch.close() print('[+] Successfully extracted pyinstaller archive: {0}'.format(sys.argv[1])) print('') print('You can now use a python decompiler on the pyc files within the extracted directory') return arch.close() if __name__ == '__main__': main() ================================================ FILE: modules/blue.py ================================================ # Module created by @Bleu-No / Bleu#7728 from datetime import datetime from colorama import Fore, Style from re import search from time import time from inspect import getfullargspec class Blue: def __init__(self, filePath, fileName): self.start = time() self.fake_variables_array = ['printl'] self.logs = [] self.variables = {} self.imports = {} self.data = {} self.init_ = 0 self.filePath = filePath self.content = None self.fileName = fileName self.options = { "bloque_request": False, "save_logs": False, "create_code": False } self.debug(f'{Fore.GREEN}INFO{Style.RESET_ALL}', 'Welcome to Blue Mysterium made by Bleu#7728 (v1.0) !') self.settings() print('\n\n') self.debug(f'{Fore.GREEN}INFO{Style.RESET_ALL}', 'Starting debug...') self.reading_file() self.define_fake_variables_and_function({ "print": "printl" }) self.execute_file() self.finding_variables() if self.options['save_logs'] == True: self.save_logs() if self.options['create_code'] == True: self.create_source_code() print('\n') self.end = time() self.debug(f'{Fore.BLUE}FINISH{Style.RESET_ALL}', f'Finish to debug file "{Fore.YELLOW}{self.fileName}{Style.RESET_ALL}" ! Time: {Fore.RED}{str(self.end - self.start)[slice(3)]}s{Style.RESET_ALL}.') def add_logs(self, text): if self.options['save_logs'] == False: return now = datetime.now().timetuple() self.logs.append( f'#SOURCE CODE [{now.tm_hour}:{now.tm_min}:{now.tm_sec}] {text}\n' ) def debug(self, type, value, a=False) -> str: now = datetime.now().timetuple() return print(f'~{Fore.RED}{now.tm_hour}:{now.tm_min}:{now.tm_sec}{Style.RESET_ALL}{Fore.MAGENTA}~{Style.RESET_ALL} > {type} < {Fore.MAGENTA}<-->{Style.RESET_ALL} {Fore.YELLOW}{value}{Style.RESET_ALL}') def question_options(self, text, option): op = input(str(text)).lower() if op in ["y", "n"]: self.options[option] = True if op == "y" else False else: print(f'{Fore.RED}[ERROR] Invalid option !{Style.RESET_ALL}') exit() def settings(self): self.question_options( f'[{Fore.CYAN}QUESTION{Style.RESET_ALL}]{Fore.YELLOW} Do you want to block the requests? (Y/N) {Style.RESET_ALL}', 'bloque_request' ) self.question_options( f'[{Fore.CYAN}QUESTION{Style.RESET_ALL}]{Fore.YELLOW} Do you want to save the logs? (Y/N) {Style.RESET_ALL}', 'save_logs' ) self.question_options( f'[{Fore.CYAN}QUESTION{Style.RESET_ALL}]{Fore.YELLOW} Do you want the software to try to recreate the code? (Y/N) {Style.RESET_ALL}', 'create_code' ) def reading_file(self): self.debug(f'{Fore.GREEN}INFO{Style.RESET_ALL}', 'Opening and reading file...') with open(self.filePath, 'rb') as f: self.content = f.read().decode('latin-1') f.close() def define_fake_variables_and_function(self, v_name): self.debug(f'{Fore.GREEN}INFO{Style.RESET_ALL}', 'Defining fake variables and functions...') fake_variables = ""; for name in v_name: fake_variables += f'{name}={v_name[name]};' self.content = f'{fake_variables}\n\n{self.content}'; def finding_variables(self): print("\n") for name in self.variables: if "$", str(self.variables[name])) name_function = name_function_s.group(2) code_function = name_function_s.group(4) if name_function in self.fake_variables_array: continue self.data[self.init_] = { 'type': 'function', 'data': { 'name': name_function, 'code': code_function, } } self.init_ += 1 self.debug(f'{Fore.RED}DEBUG{Style.RESET_ALL}', f'{Fore.CYAN}VARIABLE_FUNCTION{Style.RESET_ALL}: {str(self.variables[name])} || {Fore.LIGHTMAGENTA_EX}NAME{Style.RESET_ALL}: {name}') self.add_logs(f'VARIABLE_FUNCTION: {str(self.variables[name])} || NAME: {name}\ndef {name}():\n\treturn') elif "$", str(self.variables[name])) name_import = match_import.group(2) path_import = match_import.group(4) self.debug(f'{Fore.RED}DEBUG{Style.RESET_ALL}', f'{Fore.CYAN}IMPORT_FUNCTION{Style.RESET_ALL}: {name_import} || {Fore.LIGHTMAGENTA_EX}PATH{Style.RESET_ALL}: {path_import}') self.add_logs(f'IMPORT_FUNCTION: {name_import} || PATH: {path_import}\nimport {name_import}') self.imports[name_import] = path_import else: content_variable = str(f'{str(self.variables[name])[slice(4)]}... ({len(str(self.variables[name]))-4} characters)').replace("\n", "") if type(self.variables[name]) is bool or type(self.variables[name]) is int or type(self.variables[name]) is list: self.add_logs(f'VARIABLE: {name} || CODE: {name} = "{content_variable}"; || VALUE: {content_variable}\n{name} = {self.variables[name]};') elif type(self.variables[name]) is str: if len(self.variables[name]) > 10: self.add_logs(f'VARIABLE: {name} || CODE: {name} = "{content_variable}"; || VALUE: {content_variable}\n{name} = """{self.variables[name]}""";') else: self.add_logs(f'VARIABLE: {name} || CODE: {name} = "{content_variable}"; || VALUE: {content_variable}\n{name} = "{self.variables[name]}";') else: self.add_logs(f'VARIABLE: {name} || CODE: {name} = "{content_variable}"; || VALUE: {content_variable}\n{name} = "{self.variables[name]}";') self.data[self.init_] = { 'type': 'variable', 'data': { 'name': name, 'content': self.variables[name], } } self.init_ += 1 self.debug(f'{Fore.RED}DEBUG{Style.RESET_ALL}', f'{Fore.CYAN}VARIABLE{Style.RESET_ALL}: {name} || {Fore.LIGHTMAGENTA_EX}CODE{Style.RESET_ALL}: {name} = "{content_variable}"; || {Fore.GREEN}VALUE{Style.RESET_ALL}: {content_variable}', True) def execute_file(self): self.debug(f'{Fore.GREEN}INFO{Style.RESET_ALL}', 'Executing file and loading functions...') self.debug(f'{Fore.LIGHTYELLOW_EX}MODULE{Style.RESET_ALL}', 'Loading simple function...') global printl def printl(text): self.add_logs(f'FAKE_FUNCTION: print || CODE: print("{text}"); || VALUE: {text}\nprint("{text}");') self.data[self.init_] = {'type': 'fake_function','data': {'code': f'print("{text}");'}} self.init_ += 1 return self.debug(f'{Fore.RED}DEBUG{Style.RESET_ALL}', f'{Fore.CYAN}FAKE_FUNCTION{Style.RESET_ALL}: print || {Fore.LIGHTMAGENTA_EX}CODE{Style.RESET_ALL}: print("{text}"); || {Fore.GREEN}VALUE{Style.RESET_ALL}: {text}') print('\n\n') exec(self.content, globals(), self.variables) def save_logs(self): print('\n') self.debug(f'{Fore.MAGENTA}LOGS{Style.RESET_ALL}', f'Saving logs... ( {len(self.logs)} log(s) )') with open(f'{self.fileName}_logs_{time()}.py', 'w') as f: for name in self.logs: f.write(f'{name}\n') f.close() def create_source_code(self): with open(f'{self.fileName}_debug.py', 'w') as f: for name_import in self.imports: f.write(f'import {name_import}\n') f.write('\n') for x in self.data: if self.data[x]['type'] == 'function': print(getfullargspec(self.data[x]["data"]["name"])) f.write(f'def {str(self.data[x]["data"]["name"])}(): \n\treturn\n\n') elif self.data[x]['type'] == 'variable': if type(self.data[x]['data']['content']) is str: if len(self.data[x]['data']['content']) > 10: f.write(f'{self.data[x]["data"]["name"]} = """{self.data[x]["data"]["content"]}""";\n') else: f.write(f"{self.data[x]['data']['name']} = \"{self.data[x]['data']['content']}\";\n") elif type(self.data[x]['data']['content']) is bool or type(self.data[x]['data']['content']) is int or type(self.data[x]['data']['content']) is list: f.write(f"{self.data[x]['data']['name']} = {self.data[x]['data']['content']};\n") else: f.write(f"{self.data[x]['data']['name']} = \"{self.data[x]['data']['content']}\";\n") elif self.data[x]['type'] == "fake_function": f.write(f'{self.data[x]["data"]["code"]}\n\n') ================================================ FILE: modules/dhooks.py ================================================ print("import dhooks") class Webhook: def __init__(self, url, **kwargs): print("from dhooks import Webhook") print(f'webhook = dhooks.Webhook(url="{url}")') def send(self, content: str = "", **kwargs): print(f'webhook.send(content="{content}")') def modify(self, name, **kwargs): print(f'webhook.modify(name="{name}")') def get_info(self): print("webhook.get_info()") return "" def close(self): print("webhook.close()") def delete(self): print("webhook.delete()") ================================================ FILE: modules/fade.py ================================================ print("import fade") def blackwhite(text): print(f'fade.blackwhite(text="{text}")') return text def purplepink(text): print(f'fade.purplepink(text="{text}")') return text def greenblue(text): print(f'fade.greenblue(text="{text}")') return text def pinkred(text): print(f'fade.pinkred(text="{text}")') return text def purpleblue(text): print(f'fade.purpleblue(text="{text}")') return text def water(text): print(f'fade.water(text="{text}")') return text def fire(text): print(f'fade.fire(text="{text}")') return text def brazil(text): print(f'fade.brazil(text="{text}")') return text def random(text): print(f'fade.random(text="{text}")') return text ================================================ FILE: modules/getmac.py ================================================ print("import getmac") def get_mac_address(interface=None, ip=None, ip6=None, hostname=None, network_request=True): get_mac_address_content = f"{f'interface={interface}' if interface else ''}{f', ip={ip}' if ip else ''}{f', ip6={ip6}' if ip6 else ''}{f', hostname={hostname}' if hostname else ''}{f', network_request={network_request}' if network_request and not network_request == True else ''}" print(f'getmac.get_mac_address({get_mac_address_content})') return "00:00:00:00:00:00" ================================================ FILE: modules/gratient.py ================================================ print("import gratient") def black(text): print(f'gratient.black(text="{text}")') return text def green(text): print(f'gratient.green(text="{text}")') return text def blue(text): print(f'gratient.blue(text="{text}")') return text def purple(text): print(f'gratient.purple(text="{text}")') return text def yellow(text): print(f'gratient.yellow(text="{text}")') return text def red(text): print(f'gratient.red(text="{text}")') return text ================================================ FILE: modules/json.py ================================================ print("import json") def dump(obj, fp=False, *, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, cls=None, indent=None, separators=None, default=None, sort_keys=False, **kw): dump_content = f"{f'obj={obj}' if obj else ''}{f', fp={fp}' if fp else ''}{f', skipkeys={skipkeys}' if skipkeys else ''}{f', ensure_ascii={ensure_ascii}' if ensure_ascii and not ensure_ascii == True else ''}{f', check_circular={check_circular}' if check_circular and not check_circular == True else ''}{f', allow_nan={allow_nan}' if allow_nan and not allow_nan == True else ''}{f', cls={cls}' if cls else ''}{f', indent={indent}' if indent else ''}{f', separators={separators}' if separators else ''}{f', default={default}' if default else ''}" print(f"json.dump({dump_content})") return "" def dumps(obj, *, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, cls=None, indent=None, separators=None, default=None, sort_keys=False, **kw): dumps_content = f"{f'obj={obj}' if obj else ''}{f', skipkeys={skipkeys}' if skipkeys else ''}{f', ensure_ascii={ensure_ascii}' if ensure_ascii and not ensure_ascii == True else ''}{f', check_circular={check_circular}' if check_circular and not check_circular == True else ''}{f', allow_nan={allow_nan}' if allow_nan and not allow_nan == True else ''}{f', cls={cls}' if cls else ''}{f', indent={indent}' if indent else ''}{f', separators={separators}' if separators else ''}{f', default={default}' if default else ''}" print(f"json.dumps({dumps_content})") return "" def detect_encoding(b): print(f"json.detect_encoding(b={b})") return "" def load(fp, *, cls=None, object_hook=None, parse_float=None, parse_int=None, parse_constant=None, object_pairs_hook=None, **kw): load_content = f"{f'fp={fp}' if fp else ''}{f', cls={cls}' if cls else ''}{f', object_hook={object_hook}' if object_hook else ''}{f', parse_float={parse_float}' if parse_float else ''}{f', parse_int={parse_int}' if parse_int else ''}{f', parse_constant={parse_constant}' if parse_constant else ''}{f', object_pairs_hook={object_pairs_hook}' if object_pairs_hook else ''}" print(f"json.load({load_content})") return "" def loads(s, *, cls=None, object_hook=None, parse_float=None, parse_int=None, parse_constant=None, object_pairs_hook=None, **kw): loads_content = f"{f's={s}' if s else ''}{f', cls={cls}' if cls else ''}{f', object_hook={object_hook}' if object_hook else ''}{f', parse_float={parse_float}' if parse_float else ''}{f', parse_int={parse_int}' if parse_int else ''}{f', parse_constant={parse_constant}' if parse_constant else ''}{f', object_pairs_hook={object_pairs_hook}' if object_pairs_hook else ''}" print(f"json.load({loads_content})") return "" ================================================ FILE: modules/playsound.py ================================================ print("import playsound") def playsound(sound, block=True): sound = f'"{sound}"' print(f"playsound.playsound({sound}{f', block={block}' if block else ''})") ================================================ FILE: modules/pycenter.py ================================================ print("import pycenter") def center(var: str, space: int = None, icon: str = "", sep: bool = False): center_content = f"{f'var={var}' if var else ''}{f', space={space}' if space else ''}{f', icon={icon}' if icon else ''}{f', sep={sep}' if sep else ''}" print(f'pycenter.center("{center_content}")') return var def makebox(content: str): print(f'pycenter.makebox("{content}")') return content ================================================ FILE: modules/pyfade.py ================================================ print("import pyfade") def init(): print("pyfade.init()") def ditto(text): return f'"{text}"' class Fade: def check(line): print(f'Fade.check(line="{line}")') return False def Vertical(color, text: str, speed: int = 1, start: int = 0, stop: int = 0): vertical_content = f"{f'color={color}' if color else ''}{f', text={ditto(text)}' if text else ''}{f', speed={speed}' if speed and not speed == 1 else ''}{f', start={start}' if start and not start == 0 else ''}{f', stop={stop}' if stop and not stop == 0 else ''}" print(f"Fade.Vertical({vertical_content})") return text def Horizontal(color, text: str, speed: int = 1, start: int = 0, stop: int = 0): horizontal_content = f"{f'color={color}' if color else ''}{f', text={ditto(text)}' if text else ''}{f', speed={speed}' if speed and not speed == 1 else ''}{f', start={start}' if start and not start == 0 else ''}{f', stop={stop}' if stop and not stop == 0 else ''}" print(f"Fade.Horizontal({horizontal_content})") return text def Diagonal(color, text: str, speed: int = 1, start: int = 0, stop: int = 0): diagonal_content = f"{f'color={color}' if color else ''}{f', text={ditto(text)}' if text else ''}{f', speed={speed}' if speed and not speed == 1 else ''}{f', start={start}' if start and not start == 0 else ''}{f', stop={stop}' if stop and not stop == 0 else ''}" print(f"Fade.diagonal({diagonal_content})") return text class Colors: black_to_white = ["m;m;m"] black_to_red = ["m;0;0"] black_to_green = ["0;m;0"] black_to_blue = ["0;0;m"] white_to_black = ["n;n;n"] white_to_red = ["255;n;n"] white_to_green = ["n;255;n"] white_to_blue = ["n;n;255"] red_to_black = ["n;0;0"] red_to_white = ["255;m;m"] red_to_yellow = ["255;m;0"] red_to_purple = ["255;0;m"] green_to_black = ["0;n;0"] green_to_white = ["m;255;m"] green_to_yellow = ["m;255;0"] green_to_cyan = ["0;255;m"] blue_to_black = ["0;0;n"] blue_to_white = ["m;m;255"] blue_to_cyan = ["0;m;255"] blue_to_purple = ["m;0;255"] yellow_to_red = ["255;n;0"] yellow_to_green = ["n;255;0"] purple_to_red = ["255;0;n"] purple_to_blue = ["n;0;255"] cyan_to_green = ["0;255;n"] cyan_to_blue = ["0;n;255"] ================================================ FILE: modules/pyproxies.py ================================================ print("import pyproxies") def proxy(server, timeout = 5): print(f'pyproxies.proxy(server="{server}"{f", timeout={timeout}" if timeout and not timeout == 5 else ""})') return {"https": f"http://198.162.1.1:8080"} ================================================ FILE: modules/random.py ================================================ print("import random") class Random: def randbytes(n): print(f"random.randbytes({n})") return 0 def randrange(start, stop=None, step=1): randrange_content = f"{f'start={start}' if start else ''}{f', stop={stop}' if stop else ''}{f', step={step}' if step and not step == 1 else ''}" print(f"random.randrange({randrange_content})") return 0 def randint(a, b): print(f"random.randint(a={a}, b={b})") return 0 def choice(seq): print(f"random.choice(seq={seq})") return 0 def gauss(mu, sigma): print(f"random.gauss(mu={mu}, sigma={sigma})") return 0 def random(): print("random.random()") return 0 def getrandbits(k): print(f"random.getrandbits(k={k})") return 0 ================================================ FILE: modules/re.py ================================================ print("import re") def findall(pattern, string, flags=0): print(f"re.findall({pattern}, {string})") return "" def split(pattern, string, maxsplit=0, flags=0): print(f"re.split({pattern}, {string})") return "" def subn(pattern, repl, string, count=0, flags=0): print(f"re.subn({pattern}, {repl}, {string})") return "" def sub(pattern, repl, string, count=0, flags=0): print(f"re.sub({pattern}, {repl}, {string})") return "" def search(pattern, string, flags=0): print(f"re.search({pattern}, {string})") return "" def fullmatch(pattern, string, flags=0): print(f"re.fullmatch({pattern}, {string})") return None def match(pattern, string, flags=0): print(f"re.match({pattern}, {string})") return None def finditer(pattern, string, flags=0): print(f"re.finditer({pattern}, {string})") return "" def purge(): print("re.purge()") return "" def template(pattern, flags=0): print(f"re.template({pattern})") return "" def escape(pattern): print(f"re.escape({pattern})") return "" def compile(pattern, flags=0): pattern = "" return "" ASCII = "" IGNORECASE = "" LOCALE = "" UNICODE = "" MULTILINE = "" DOTALL = "" VERBOSE = "" TEMPLATE = "" DEBUG = "" print("import re") def findall(pattern, string, flags=0): print(f"re.findall({pattern}, {string})") return "" def split(pattern, string, maxsplit=0, flags=0): print(f"re.split({pattern}, {string})") return "" def subn(pattern, repl, string, count=0, flags=0): print(f"re.subn({pattern}, {repl}, {string})") return "" def sub(pattern, repl, string, count=0, flags=0): print(f"re.sub({pattern}, {repl}, {string})") return "" def search(pattern, string, flags=0): print(f"re.search({pattern}, {string})") return "" def fullmatch(pattern, string, flags=0): print(f"re.fullmatch({pattern}, {string})") return None def match(pattern, string, flags=0): print(f"re.match({pattern}, {string})") return None def finditer(pattern, string, flags=0): print(f"re.finditer({pattern}, {string})") return "" def purge(): print("re.purge()") return "" def template(pattern, flags=0): print(f"re.template({pattern})") return "" def escape(pattern): print(f"re.escape({pattern})") return "" def compile(pattern, flags=0): pattern = "" return "" ASCII = "" IGNORECASE = "" LOCALE = "" UNICODE = "" MULTILINE = "" DOTALL = "" VERBOSE = "" TEMPLATE = "" DEBUG = "" ================================================ FILE: modules/requests/__init__.py ================================================ print("import requests") class Session: pass def get(url=False, auth=None, json=False, data=False, hooks=None, files=False, params=False, proxies=False, headers=False, timeout=False, encoding=False, allow_redirects=True): url = f'"{url}"' request_content = f"{f'url={url}'}{f', auth={auth}' if auth else ''}{f', json={json}' if json else ''}{f', data={data}' if data else ''}{f', hooks={hooks}' if hooks else ''}{f', files={files}' if files else ''}{f', params={params}' if params else ''}{f', proxies={proxies}' if proxies else ''}{f', headers={headers}' if headers else ''}{f', timeout={timeout}' if timeout else ''}{f', encoding={encoding}' if encoding else ''}{f', allow_redirects={allow_redirects}' if allow_redirects and not allow_redirects == True else ''}" print(f"requests.get({request_content})") class fake_response: text = "" json = "" content = "" cookies = "" headers = "" status_code = 200 return fake_response def post(url=False, auth=None, json=False, data=False, hooks=None, files=False, params=False, proxies=False, headers=False, timeout=False, encoding=False, allow_redirects=True): url = f'"{url}"' request_content = f"{f'url={url}'}{f', auth={auth}' if auth else ''}{f', json={json}' if json else ''}{f', data={data}' if data else ''}{f', hooks={hooks}' if hooks else ''}{f', files={files}' if files else ''}{f', params={params}' if params else ''}{f', proxies={proxies}' if proxies else ''}{f', headers={headers}' if headers else ''}{f', timeout={timeout}' if timeout else ''}{f', encoding={encoding}' if encoding else ''}{f', allow_redirects={allow_redirects}' if allow_redirects and not allow_redirects == True else ''}" print(f"requests.post({request_content})") class fake_response: text = "" json = "" content = "" cookies = "" headers = "" status_code = 200 return fake_response def patch(url=False, auth=None, json=False, data=False, hooks=None, files=False, params=False, proxies=False, headers=False, timeout=False, encoding=False, allow_redirects=True): url = f'"{url}"' request_content = f"{f'url={url}'}{f', auth={auth}' if auth else ''}{f', json={json}' if json else ''}{f', data={data}' if data else ''}{f', hooks={hooks}' if hooks else ''}{f', files={files}' if files else ''}{f', params={params}' if params else ''}{f', proxies={proxies}' if proxies else ''}{f', headers={headers}' if headers else ''}{f', timeout={timeout}' if timeout else ''}{f', encoding={encoding}' if encoding else ''}{f', allow_redirects={allow_redirects}' if allow_redirects and not allow_redirects == True else ''}" print(f"requests.patch({request_content})") class fake_response: text = "" json = "" content = "" cookies = "" headers = "" status_code = 200 return fake_response def delete(url=False, auth=None, json=False, data=False, hooks=None, files=False, params=False, proxies=False, headers=False, timeout=False, encoding=False, allow_redirects=True): url = f'"{url}"' request_content = f"{f'url={url}'}{f', auth={auth}' if auth else ''}{f', json={json}' if json else ''}{f', data={data}' if data else ''}{f', hooks={hooks}' if hooks else ''}{f', files={files}' if files else ''}{f', params={params}' if params else ''}{f', proxies={proxies}' if proxies else ''}{f', headers={headers}' if headers else ''}{f', timeout={timeout}' if timeout else ''}{f', encoding={encoding}' if encoding else ''}{f', allow_redirects={allow_redirects}' if allow_redirects and not allow_redirects == True else ''}" print(f"requests.delete({request_content})") class fake_response: text = "" json = "" content = "" cookies = "" headers = "" status_code = 200 return fake_response ================================================ FILE: modules/requests/__version__.py ================================================ # .-. .-. .-. . . .-. .-. .-. .-. # |( |- |.| | | |- `-. | `-. # ' ' `-' `-`.`-' `-' `-' ' `-' __title__ = 'requests' __description__ = 'Python HTTP for Humans.' __url__ = 'https://requests.readthedocs.io' __version__ = '2.26.0' __build__ = 0x022600 __author__ = 'Kenneth Reitz' __author_email__ = 'me@kennethreitz.org' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2020 Kenneth Reitz' __cake__ = u'\u2728 \U0001f370 \u2728' ================================================ FILE: modules/requests/_internal_utils.py ================================================ # -*- coding: utf-8 -*- """ requests._internal_utils ~~~~~~~~~~~~~~ Provides utility functions that are consumed internally by Requests which depend on extremely few external helpers (such as compat) """ from .compat import is_py2, builtin_str, str def to_native_string(string, encoding='ascii'): """Given a string object, regardless of type, returns a representation of that string in the native string type, encoding and decoding where necessary. This assumes ASCII unless told otherwise. """ if isinstance(string, builtin_str): out = string else: if is_py2: out = string.encode(encoding) else: out = string.decode(encoding) return out def unicode_is_ascii(u_string): """Determine if unicode string only contains ASCII characters. :param str u_string: unicode string to check. Must be unicode and not Python 2 `str`. :rtype: bool """ assert isinstance(u_string, str) try: u_string.encode('ascii') return True except UnicodeEncodeError: return False ================================================ FILE: modules/requests/adapters.py ================================================ # -*- coding: utf-8 -*- """ requests.adapters ~~~~~~~~~~~~~~~~~ This module contains the transport adapters that Requests uses to define and maintain connections. """ import os.path import socket from urllib3.poolmanager import PoolManager, proxy_from_url from urllib3.response import HTTPResponse from urllib3.util import parse_url from urllib3.util import Timeout as TimeoutSauce from urllib3.util.retry import Retry from urllib3.exceptions import ClosedPoolError from urllib3.exceptions import ConnectTimeoutError from urllib3.exceptions import HTTPError as _HTTPError from urllib3.exceptions import MaxRetryError from urllib3.exceptions import NewConnectionError from urllib3.exceptions import ProxyError as _ProxyError from urllib3.exceptions import ProtocolError from urllib3.exceptions import ReadTimeoutError from urllib3.exceptions import SSLError as _SSLError from urllib3.exceptions import ResponseError from urllib3.exceptions import LocationValueError from .models import Response from .compat import urlparse, basestring from .utils import (DEFAULT_CA_BUNDLE_PATH, extract_zipped_paths, get_encoding_from_headers, prepend_scheme_if_needed, get_auth_from_url, urldefragauth, select_proxy) from .structures import CaseInsensitiveDict from .cookies import extract_cookies_to_jar from .exceptions import (ConnectionError, ConnectTimeout, ReadTimeout, SSLError, ProxyError, RetryError, InvalidSchema, InvalidProxyURL, InvalidURL) from .auth import _basic_auth_str try: from urllib3.contrib.socks import SOCKSProxyManager except ImportError: def SOCKSProxyManager(*args, **kwargs): raise InvalidSchema("Missing dependencies for SOCKS support.") DEFAULT_POOLBLOCK = False DEFAULT_POOLSIZE = 10 DEFAULT_RETRIES = 0 DEFAULT_POOL_TIMEOUT = None class BaseAdapter(object): """The Base Transport Adapter""" def __init__(self): super(BaseAdapter, self).__init__() def send(self, request, stream=False, timeout=None, verify=True, cert=None, proxies=None): """Sends PreparedRequest object. Returns Response object. :param request: The :class:`PreparedRequest ` being sent. :param stream: (optional) Whether to stream the request content. :param timeout: (optional) How long to wait for the server to send data before giving up, as a float, or a :ref:`(connect timeout, read timeout) ` tuple. :type timeout: float or tuple :param verify: (optional) Either a boolean, in which case it controls whether we verify the server's TLS certificate, or a string, in which case it must be a path to a CA bundle to use :param cert: (optional) Any user-provided SSL certificate to be trusted. :param proxies: (optional) The proxies dictionary to apply to the request. """ raise NotImplementedError def close(self): """Cleans up adapter specific items.""" raise NotImplementedError class HTTPAdapter(BaseAdapter): """The built-in HTTP Adapter for urllib3. Provides a general-case interface for Requests sessions to contact HTTP and HTTPS urls by implementing the Transport Adapter interface. This class will usually be created by the :class:`Session ` class under the covers. :param pool_connections: The number of urllib3 connection pools to cache. :param pool_maxsize: The maximum number of connections to save in the pool. :param max_retries: The maximum number of retries each connection should attempt. Note, this applies only to failed DNS lookups, socket connections and connection timeouts, never to requests where data has made it to the server. By default, Requests does not retry failed connections. If you need granular control over the conditions under which we retry a request, import urllib3's ``Retry`` class and pass that instead. :param pool_block: Whether the connection pool should block for connections. Usage:: >>> import requests >>> s = requests.Session() >>> a = requests.adapters.HTTPAdapter(max_retries=3) >>> s.mount('http://', a) """ __attrs__ = ['max_retries', 'config', '_pool_connections', '_pool_maxsize', '_pool_block'] def __init__(self, pool_connections=DEFAULT_POOLSIZE, pool_maxsize=DEFAULT_POOLSIZE, max_retries=DEFAULT_RETRIES, pool_block=DEFAULT_POOLBLOCK): if max_retries == DEFAULT_RETRIES: self.max_retries = Retry(0, read=False) else: self.max_retries = Retry.from_int(max_retries) self.config = {} self.proxy_manager = {} super(HTTPAdapter, self).__init__() self._pool_connections = pool_connections self._pool_maxsize = pool_maxsize self._pool_block = pool_block self.init_poolmanager(pool_connections, pool_maxsize, block=pool_block) def __getstate__(self): return {attr: getattr(self, attr, None) for attr in self.__attrs__} def __setstate__(self, state): # Can't handle by adding 'proxy_manager' to self.__attrs__ because # self.poolmanager uses a lambda function, which isn't pickleable. self.proxy_manager = {} self.config = {} for attr, value in state.items(): setattr(self, attr, value) self.init_poolmanager(self._pool_connections, self._pool_maxsize, block=self._pool_block) def init_poolmanager(self, connections, maxsize, block=DEFAULT_POOLBLOCK, **pool_kwargs): """Initializes a urllib3 PoolManager. This method should not be called from user code, and is only exposed for use when subclassing the :class:`HTTPAdapter `. :param connections: The number of urllib3 connection pools to cache. :param maxsize: The maximum number of connections to save in the pool. :param block: Block when no free connections are available. :param pool_kwargs: Extra keyword arguments used to initialize the Pool Manager. """ # save these values for pickling self._pool_connections = connections self._pool_maxsize = maxsize self._pool_block = block self.poolmanager = PoolManager(num_pools=connections, maxsize=maxsize, block=block, strict=True, **pool_kwargs) def proxy_manager_for(self, proxy, **proxy_kwargs): """Return urllib3 ProxyManager for the given proxy. This method should not be called from user code, and is only exposed for use when subclassing the :class:`HTTPAdapter `. :param proxy: The proxy to return a urllib3 ProxyManager for. :param proxy_kwargs: Extra keyword arguments used to configure the Proxy Manager. :returns: ProxyManager :rtype: urllib3.ProxyManager """ if proxy in self.proxy_manager: manager = self.proxy_manager[proxy] elif proxy.lower().startswith('socks'): username, password = get_auth_from_url(proxy) manager = self.proxy_manager[proxy] = SOCKSProxyManager( proxy, username=username, password=password, num_pools=self._pool_connections, maxsize=self._pool_maxsize, block=self._pool_block, **proxy_kwargs ) else: proxy_headers = self.proxy_headers(proxy) manager = self.proxy_manager[proxy] = proxy_from_url( proxy, proxy_headers=proxy_headers, num_pools=self._pool_connections, maxsize=self._pool_maxsize, block=self._pool_block, **proxy_kwargs) return manager def cert_verify(self, conn, url, verify, cert): """Verify a SSL certificate. This method should not be called from user code, and is only exposed for use when subclassing the :class:`HTTPAdapter `. :param conn: The urllib3 connection object associated with the cert. :param url: The requested URL. :param verify: Either a boolean, in which case it controls whether we verify the server's TLS certificate, or a string, in which case it must be a path to a CA bundle to use :param cert: The SSL certificate to verify. """ if url.lower().startswith('https') and verify: cert_loc = None # Allow self-specified cert location. if verify is not True: cert_loc = verify if not cert_loc: cert_loc = extract_zipped_paths(DEFAULT_CA_BUNDLE_PATH) if not cert_loc or not os.path.exists(cert_loc): raise IOError("Could not find a suitable TLS CA certificate bundle, " "invalid path: {}".format(cert_loc)) conn.cert_reqs = 'CERT_REQUIRED' if not os.path.isdir(cert_loc): conn.ca_certs = cert_loc else: conn.ca_cert_dir = cert_loc else: conn.cert_reqs = 'CERT_NONE' conn.ca_certs = None conn.ca_cert_dir = None if cert: if not isinstance(cert, basestring): conn.cert_file = cert[0] conn.key_file = cert[1] else: conn.cert_file = cert conn.key_file = None if conn.cert_file and not os.path.exists(conn.cert_file): raise IOError("Could not find the TLS certificate file, " "invalid path: {}".format(conn.cert_file)) if conn.key_file and not os.path.exists(conn.key_file): raise IOError("Could not find the TLS key file, " "invalid path: {}".format(conn.key_file)) def build_response(self, req, resp): """Builds a :class:`Response ` object from a urllib3 response. This should not be called from user code, and is only exposed for use when subclassing the :class:`HTTPAdapter ` :param req: The :class:`PreparedRequest ` used to generate the response. :param resp: The urllib3 response object. :rtype: requests.Response """ response = Response() # Fallback to None if there's no status_code, for whatever reason. response.status_code = getattr(resp, 'status', None) # Make headers case-insensitive. response.headers = CaseInsensitiveDict(getattr(resp, 'headers', {})) # Set encoding. response.encoding = get_encoding_from_headers(response.headers) response.raw = resp response.reason = response.raw.reason if isinstance(req.url, bytes): response.url = req.url.decode('utf-8') else: response.url = req.url # Add new cookies from the server. extract_cookies_to_jar(response.cookies, req, resp) # Give the Response some context. response.request = req response.connection = self return response def get_connection(self, url, proxies=None): """Returns a urllib3 connection for the given URL. This should not be called from user code, and is only exposed for use when subclassing the :class:`HTTPAdapter `. :param url: The URL to connect to. :param proxies: (optional) A Requests-style dictionary of proxies used on this request. :rtype: urllib3.ConnectionPool """ proxy = select_proxy(url, proxies) if proxy: proxy = prepend_scheme_if_needed(proxy, 'http') proxy_url = parse_url(proxy) if not proxy_url.host: raise InvalidProxyURL("Please check proxy URL. It is malformed" " and could be missing the host.") proxy_manager = self.proxy_manager_for(proxy) conn = proxy_manager.connection_from_url(url) else: # Only scheme should be lower case parsed = urlparse(url) url = parsed.geturl() conn = self.poolmanager.connection_from_url(url) return conn def close(self): """Disposes of any internal state. Currently, this closes the PoolManager and any active ProxyManager, which closes any pooled connections. """ self.poolmanager.clear() for proxy in self.proxy_manager.values(): proxy.clear() def request_url(self, request, proxies): """Obtain the url to use when making the final request. If the message is being sent through a HTTP proxy, the full URL has to be used. Otherwise, we should only use the path portion of the URL. This should not be called from user code, and is only exposed for use when subclassing the :class:`HTTPAdapter `. :param request: The :class:`PreparedRequest ` being sent. :param proxies: A dictionary of schemes or schemes and hosts to proxy URLs. :rtype: str """ proxy = select_proxy(request.url, proxies) scheme = urlparse(request.url).scheme is_proxied_http_request = (proxy and scheme != 'https') using_socks_proxy = False if proxy: proxy_scheme = urlparse(proxy).scheme.lower() using_socks_proxy = proxy_scheme.startswith('socks') url = request.path_url if is_proxied_http_request and not using_socks_proxy: url = urldefragauth(request.url) return url def add_headers(self, request, **kwargs): """Add any headers needed by the connection. As of v2.0 this does nothing by default, but is left for overriding by users that subclass the :class:`HTTPAdapter `. This should not be called from user code, and is only exposed for use when subclassing the :class:`HTTPAdapter `. :param request: The :class:`PreparedRequest ` to add headers to. :param kwargs: The keyword arguments from the call to send(). """ pass def proxy_headers(self, proxy): """Returns a dictionary of the headers to add to any request sent through a proxy. This works with urllib3 magic to ensure that they are correctly sent to the proxy, rather than in a tunnelled request if CONNECT is being used. This should not be called from user code, and is only exposed for use when subclassing the :class:`HTTPAdapter `. :param proxy: The url of the proxy being used for this request. :rtype: dict """ headers = {} username, password = get_auth_from_url(proxy) if username: headers['Proxy-Authorization'] = _basic_auth_str(username, password) return headers def send(self, request, stream=False, timeout=None, verify=True, cert=None, proxies=None): """Sends PreparedRequest object. Returns Response object. :param request: The :class:`PreparedRequest ` being sent. :param stream: (optional) Whether to stream the request content. :param timeout: (optional) How long to wait for the server to send data before giving up, as a float, or a :ref:`(connect timeout, read timeout) ` tuple. :type timeout: float or tuple or urllib3 Timeout object :param verify: (optional) Either a boolean, in which case it controls whether we verify the server's TLS certificate, or a string, in which case it must be a path to a CA bundle to use :param cert: (optional) Any user-provided SSL certificate to be trusted. :param proxies: (optional) The proxies dictionary to apply to the request. :rtype: requests.Response """ try: conn = self.get_connection(request.url, proxies) except LocationValueError as e: raise InvalidURL(e, request=request) self.cert_verify(conn, request.url, verify, cert) url = self.request_url(request, proxies) self.add_headers(request, stream=stream, timeout=timeout, verify=verify, cert=cert, proxies=proxies) chunked = not (request.body is None or 'Content-Length' in request.headers) if isinstance(timeout, tuple): try: connect, read = timeout timeout = TimeoutSauce(connect=connect, read=read) except ValueError as e: # this may raise a string formatting error. err = ("Invalid timeout {}. Pass a (connect, read) " "timeout tuple, or a single float to set " "both timeouts to the same value".format(timeout)) raise ValueError(err) elif isinstance(timeout, TimeoutSauce): pass else: timeout = TimeoutSauce(connect=timeout, read=timeout) try: if not chunked: resp = conn.urlopen( method=request.method, url=url, body=request.body, headers=request.headers, redirect=False, assert_same_host=False, preload_content=False, decode_content=False, retries=self.max_retries, timeout=timeout ) # Send the request. else: if hasattr(conn, 'proxy_pool'): conn = conn.proxy_pool low_conn = conn._get_conn(timeout=DEFAULT_POOL_TIMEOUT) try: low_conn.putrequest(request.method, url, skip_accept_encoding=True) for header, value in request.headers.items(): low_conn.putheader(header, value) low_conn.endheaders() for i in request.body: low_conn.send(hex(len(i))[2:].encode('utf-8')) low_conn.send(b'\r\n') low_conn.send(i) low_conn.send(b'\r\n') low_conn.send(b'0\r\n\r\n') # Receive the response from the server try: # For Python 2.7, use buffering of HTTP responses r = low_conn.getresponse(buffering=True) except TypeError: # For compatibility with Python 3.3+ r = low_conn.getresponse() resp = HTTPResponse.from_httplib( r, pool=conn, connection=low_conn, preload_content=False, decode_content=False ) except: # If we hit any problems here, clean up the connection. # Then, reraise so that we can handle the actual exception. low_conn.close() raise except (ProtocolError, socket.error) as err: raise ConnectionError(err, request=request) except MaxRetryError as e: if isinstance(e.reason, ConnectTimeoutError): # TODO: Remove this in 3.0.0: see #2811 if not isinstance(e.reason, NewConnectionError): raise ConnectTimeout(e, request=request) if isinstance(e.reason, ResponseError): raise RetryError(e, request=request) if isinstance(e.reason, _ProxyError): raise ProxyError(e, request=request) if isinstance(e.reason, _SSLError): # This branch is for urllib3 v1.22 and later. raise SSLError(e, request=request) raise ConnectionError(e, request=request) except ClosedPoolError as e: raise ConnectionError(e, request=request) except _ProxyError as e: raise ProxyError(e) except (_SSLError, _HTTPError) as e: if isinstance(e, _SSLError): # This branch is for urllib3 versions earlier than v1.22 raise SSLError(e, request=request) elif isinstance(e, ReadTimeoutError): raise ReadTimeout(e, request=request) else: raise return self.build_response(request, resp) ================================================ FILE: modules/requests/api.py ================================================ # -*- coding: utf-8 -*- """ requests.api ~~~~~~~~~~~~ This module implements the Requests API. :copyright: (c) 2012 by Kenneth Reitz. :license: Apache2, see LICENSE for more details. """ from . import sessions def request(method, url, **kwargs): """Constructs and sends a :class:`Request `. :param method: method for the new :class:`Request` object: ``GET``, ``OPTIONS``, ``HEAD``, ``POST``, ``PUT``, ``PATCH``, or ``DELETE``. :param url: URL for the new :class:`Request` object. :param params: (optional) Dictionary, list of tuples or bytes to send in the query string for the :class:`Request`. :param data: (optional) Dictionary, list of tuples, bytes, or file-like object to send in the body of the :class:`Request`. :param json: (optional) A JSON serializable Python object to send in the body of the :class:`Request`. :param headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`. :param cookies: (optional) Dict or CookieJar object to send with the :class:`Request`. :param files: (optional) Dictionary of ``'name': file-like-objects`` (or ``{'name': file-tuple}``) for multipart encoding upload. ``file-tuple`` can be a 2-tuple ``('filename', fileobj)``, 3-tuple ``('filename', fileobj, 'content_type')`` or a 4-tuple ``('filename', fileobj, 'content_type', custom_headers)``, where ``'content-type'`` is a string defining the content type of the given file and ``custom_headers`` a dict-like object containing additional headers to add for the file. :param auth: (optional) Auth tuple to enable Basic/Digest/Custom HTTP Auth. :param timeout: (optional) How many seconds to wait for the server to send data before giving up, as a float, or a :ref:`(connect timeout, read timeout) ` tuple. :type timeout: float or tuple :param allow_redirects: (optional) Boolean. Enable/disable GET/OPTIONS/POST/PUT/PATCH/DELETE/HEAD redirection. Defaults to ``True``. :type allow_redirects: bool :param proxies: (optional) Dictionary mapping protocol to the URL of the proxy. :param verify: (optional) Either a boolean, in which case it controls whether we verify the server's TLS certificate, or a string, in which case it must be a path to a CA bundle to use. Defaults to ``True``. :param stream: (optional) if ``False``, the response content will be immediately downloaded. :param cert: (optional) if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair. :return: :class:`Response ` object :rtype: requests.Response Usage:: >>> import requests >>> req = requests.request('GET', 'https://httpbin.org/get') >>> req """ # By using the 'with' statement we are sure the session is closed, thus we # avoid leaving sockets open which can trigger a ResourceWarning in some # cases, and look like a memory leak in others. with sessions.Session() as session: return session.request(method=method, url=url, **kwargs) def get(url, params=None, **kwargs): r"""Sends a GET request. :param url: URL for the new :class:`Request` object. :param params: (optional) Dictionary, list of tuples or bytes to send in the query string for the :class:`Request`. :param \*\*kwargs: Optional arguments that ``request`` takes. :return: :class:`Response ` object :rtype: requests.Response """ return request('get', url, params=params, **kwargs) def options(url, **kwargs): r"""Sends an OPTIONS request. :param url: URL for the new :class:`Request` object. :param \*\*kwargs: Optional arguments that ``request`` takes. :return: :class:`Response ` object :rtype: requests.Response """ return request('options', url, **kwargs) def head(url, **kwargs): r"""Sends a HEAD request. :param url: URL for the new :class:`Request` object. :param \*\*kwargs: Optional arguments that ``request`` takes. If `allow_redirects` is not provided, it will be set to `False` (as opposed to the default :meth:`request` behavior). :return: :class:`Response ` object :rtype: requests.Response """ kwargs.setdefault('allow_redirects', False) return request('head', url, **kwargs) def post(url, data=None, json=None, **kwargs): r"""Sends a POST request. :param url: URL for the new :class:`Request` object. :param data: (optional) Dictionary, list of tuples, bytes, or file-like object to send in the body of the :class:`Request`. :param json: (optional) json data to send in the body of the :class:`Request`. :param \*\*kwargs: Optional arguments that ``request`` takes. :return: :class:`Response ` object :rtype: requests.Response """ return request('post', url, data=data, json=json, **kwargs) def put(url, data=None, **kwargs): r"""Sends a PUT request. :param url: URL for the new :class:`Request` object. :param data: (optional) Dictionary, list of tuples, bytes, or file-like object to send in the body of the :class:`Request`. :param json: (optional) json data to send in the body of the :class:`Request`. :param \*\*kwargs: Optional arguments that ``request`` takes. :return: :class:`Response ` object :rtype: requests.Response """ return request('put', url, data=data, **kwargs) def patch(url, data=None, **kwargs): r"""Sends a PATCH request. :param url: URL for the new :class:`Request` object. :param data: (optional) Dictionary, list of tuples, bytes, or file-like object to send in the body of the :class:`Request`. :param json: (optional) json data to send in the body of the :class:`Request`. :param \*\*kwargs: Optional arguments that ``request`` takes. :return: :class:`Response ` object :rtype: requests.Response """ return request('patch', url, data=data, **kwargs) def delete(url, **kwargs): r"""Sends a DELETE request. :param url: URL for the new :class:`Request` object. :param \*\*kwargs: Optional arguments that ``request`` takes. :return: :class:`Response ` object :rtype: requests.Response """ return request('delete', url, **kwargs) ================================================ FILE: modules/requests/auth.py ================================================ # -*- coding: utf-8 -*- """ requests.auth ~~~~~~~~~~~~~ This module contains the authentication handlers for Requests. """ import os import re import time import hashlib import threading import warnings from base64 import b64encode from .compat import urlparse, str, basestring from .cookies import extract_cookies_to_jar from ._internal_utils import to_native_string from .utils import parse_dict_header CONTENT_TYPE_FORM_URLENCODED = 'application/x-www-form-urlencoded' CONTENT_TYPE_MULTI_PART = 'multipart/form-data' def _basic_auth_str(username, password): """Returns a Basic Auth string.""" # "I want us to put a big-ol' comment on top of it that # says that this behaviour is dumb but we need to preserve # it because people are relying on it." # - Lukasa # # These are here solely to maintain backwards compatibility # for things like ints. This will be removed in 3.0.0. if not isinstance(username, basestring): warnings.warn( "Non-string usernames will no longer be supported in Requests " "3.0.0. Please convert the object you've passed in ({!r}) to " "a string or bytes object in the near future to avoid " "problems.".format(username), category=DeprecationWarning, ) username = str(username) if not isinstance(password, basestring): warnings.warn( "Non-string passwords will no longer be supported in Requests " "3.0.0. Please convert the object you've passed in ({!r}) to " "a string or bytes object in the near future to avoid " "problems.".format(type(password)), category=DeprecationWarning, ) password = str(password) # -- End Removal -- if isinstance(username, str): username = username.encode('latin1') if isinstance(password, str): password = password.encode('latin1') authstr = 'Basic ' + to_native_string( b64encode(b':'.join((username, password))).strip() ) return authstr class AuthBase(object): """Base class that all auth implementations derive from""" def __call__(self, r): raise NotImplementedError('Auth hooks must be callable.') class HTTPBasicAuth(AuthBase): """Attaches HTTP Basic Authentication to the given Request object.""" def __init__(self, username, password): self.username = username self.password = password def __eq__(self, other): return all([ self.username == getattr(other, 'username', None), self.password == getattr(other, 'password', None) ]) def __ne__(self, other): return not self == other def __call__(self, r): r.headers['Authorization'] = _basic_auth_str(self.username, self.password) return r class HTTPProxyAuth(HTTPBasicAuth): """Attaches HTTP Proxy Authentication to a given Request object.""" def __call__(self, r): r.headers['Proxy-Authorization'] = _basic_auth_str(self.username, self.password) return r class HTTPDigestAuth(AuthBase): """Attaches HTTP Digest Authentication to the given Request object.""" def __init__(self, username, password): self.username = username self.password = password # Keep state in per-thread local storage self._thread_local = threading.local() def init_per_thread_state(self): # Ensure state is initialized just once per-thread if not hasattr(self._thread_local, 'init'): self._thread_local.init = True self._thread_local.last_nonce = '' self._thread_local.nonce_count = 0 self._thread_local.chal = {} self._thread_local.pos = None self._thread_local.num_401_calls = None def build_digest_header(self, method, url): """ :rtype: str """ realm = self._thread_local.chal['realm'] nonce = self._thread_local.chal['nonce'] qop = self._thread_local.chal.get('qop') algorithm = self._thread_local.chal.get('algorithm') opaque = self._thread_local.chal.get('opaque') hash_utf8 = None if algorithm is None: _algorithm = 'MD5' else: _algorithm = algorithm.upper() # lambdas assume digest modules are imported at the top level if _algorithm == 'MD5' or _algorithm == 'MD5-SESS': def md5_utf8(x): if isinstance(x, str): x = x.encode('utf-8') return hashlib.md5(x).hexdigest() hash_utf8 = md5_utf8 elif _algorithm == 'SHA': def sha_utf8(x): if isinstance(x, str): x = x.encode('utf-8') return hashlib.sha1(x).hexdigest() hash_utf8 = sha_utf8 elif _algorithm == 'SHA-256': def sha256_utf8(x): if isinstance(x, str): x = x.encode('utf-8') return hashlib.sha256(x).hexdigest() hash_utf8 = sha256_utf8 elif _algorithm == 'SHA-512': def sha512_utf8(x): if isinstance(x, str): x = x.encode('utf-8') return hashlib.sha512(x).hexdigest() hash_utf8 = sha512_utf8 KD = lambda s, d: hash_utf8("%s:%s" % (s, d)) if hash_utf8 is None: return None # XXX not implemented yet entdig = None p_parsed = urlparse(url) #: path is request-uri defined in RFC 2616 which should not be empty path = p_parsed.path or "/" if p_parsed.query: path += '?' + p_parsed.query A1 = '%s:%s:%s' % (self.username, realm, self.password) A2 = '%s:%s' % (method, path) HA1 = hash_utf8(A1) HA2 = hash_utf8(A2) if nonce == self._thread_local.last_nonce: self._thread_local.nonce_count += 1 else: self._thread_local.nonce_count = 1 ncvalue = '%08x' % self._thread_local.nonce_count s = str(self._thread_local.nonce_count).encode('utf-8') s += nonce.encode('utf-8') s += time.ctime().encode('utf-8') s += os.urandom(8) cnonce = (hashlib.sha1(s).hexdigest()[:16]) if _algorithm == 'MD5-SESS': HA1 = hash_utf8('%s:%s:%s' % (HA1, nonce, cnonce)) if not qop: respdig = KD(HA1, "%s:%s" % (nonce, HA2)) elif qop == 'auth' or 'auth' in qop.split(','): noncebit = "%s:%s:%s:%s:%s" % ( nonce, ncvalue, cnonce, 'auth', HA2 ) respdig = KD(HA1, noncebit) else: # XXX handle auth-int. return None self._thread_local.last_nonce = nonce # XXX should the partial digests be encoded too? base = 'username="%s", realm="%s", nonce="%s", uri="%s", ' \ 'response="%s"' % (self.username, realm, nonce, path, respdig) if opaque: base += ', opaque="%s"' % opaque if algorithm: base += ', algorithm="%s"' % algorithm if entdig: base += ', digest="%s"' % entdig if qop: base += ', qop="auth", nc=%s, cnonce="%s"' % (ncvalue, cnonce) return 'Digest %s' % (base) def handle_redirect(self, r, **kwargs): """Reset num_401_calls counter on redirects.""" if r.is_redirect: self._thread_local.num_401_calls = 1 def handle_401(self, r, **kwargs): """ Takes the given response and tries digest-auth, if needed. :rtype: requests.Response """ # If response is not 4xx, do not auth # See https://github.com/psf/requests/issues/3772 if not 400 <= r.status_code < 500: self._thread_local.num_401_calls = 1 return r if self._thread_local.pos is not None: # Rewind the file position indicator of the body to where # it was to resend the request. r.request.body.seek(self._thread_local.pos) s_auth = r.headers.get('www-authenticate', '') if 'digest' in s_auth.lower() and self._thread_local.num_401_calls < 2: self._thread_local.num_401_calls += 1 pat = re.compile(r'digest ', flags=re.IGNORECASE) self._thread_local.chal = parse_dict_header(pat.sub('', s_auth, count=1)) # Consume content and release the original connection # to allow our new request to reuse the same one. r.content r.close() prep = r.request.copy() extract_cookies_to_jar(prep._cookies, r.request, r.raw) prep.prepare_cookies(prep._cookies) prep.headers['Authorization'] = self.build_digest_header( prep.method, prep.url) _r = r.connection.send(prep, **kwargs) _r.history.append(r) _r.request = prep return _r self._thread_local.num_401_calls = 1 return r def __call__(self, r): # Initialize per-thread state, if needed self.init_per_thread_state() # If we have a saved nonce, skip the 401 if self._thread_local.last_nonce: r.headers['Authorization'] = self.build_digest_header(r.method, r.url) try: self._thread_local.pos = r.body.tell() except AttributeError: # In the case of HTTPDigestAuth being reused and the body of # the previous request was a file-like object, pos has the # file position of the previous body. Ensure it's set to # None. self._thread_local.pos = None r.register_hook('response', self.handle_401) r.register_hook('response', self.handle_redirect) self._thread_local.num_401_calls = 1 return r def __eq__(self, other): return all([ self.username == getattr(other, 'username', None), self.password == getattr(other, 'password', None) ]) def __ne__(self, other): return not self == other ================================================ FILE: modules/requests/certs.py ================================================ #!/usr/bin/env python # -*- coding: utf-8 -*- """ requests.certs ~~~~~~~~~~~~~~ This module returns the preferred default CA certificate bundle. There is only one — the one from the certifi package. If you are packaging Requests, e.g., for a Linux distribution or a managed environment, you can change the definition of where() to return a separately packaged CA bundle. """ from certifi import where if __name__ == '__main__': print(where()) ================================================ FILE: modules/requests/compat.py ================================================ # -*- coding: utf-8 -*- """ requests.compat ~~~~~~~~~~~~~~~ This module handles import compatibility issues between Python 2 and Python 3. """ try: import chardet except ImportError: import charset_normalizer as chardet import sys # ------- # Pythons # ------- # Syntax sugar. _ver = sys.version_info #: Python 2.x? is_py2 = (_ver[0] == 2) #: Python 3.x? is_py3 = (_ver[0] == 3) try: import simplejson as json except ImportError: import json # --------- # Specifics # --------- if is_py2: from urllib import ( quote, unquote, quote_plus, unquote_plus, urlencode, getproxies, proxy_bypass, proxy_bypass_environment, getproxies_environment) from urlparse import urlparse, urlunparse, urljoin, urlsplit, urldefrag from urllib2 import parse_http_list import cookielib from Cookie import Morsel from StringIO import StringIO # Keep OrderedDict for backwards compatibility. from collections import Callable, Mapping, MutableMapping, OrderedDict builtin_str = str bytes = str str = unicode basestring = basestring numeric_types = (int, long, float) integer_types = (int, long) elif is_py3: from urllib.parse import urlparse, urlunparse, urljoin, urlsplit, urlencode, quote, unquote, quote_plus, unquote_plus, urldefrag from urllib.request import parse_http_list, getproxies, proxy_bypass, proxy_bypass_environment, getproxies_environment from http import cookiejar as cookielib from http.cookies import Morsel from io import StringIO # Keep OrderedDict for backwards compatibility. from collections import OrderedDict from collections.abc import Callable, Mapping, MutableMapping builtin_str = str str = str bytes = bytes basestring = (str, bytes) numeric_types = (int, float) integer_types = (int,) ================================================ FILE: modules/requests/cookies.py ================================================ # -*- coding: utf-8 -*- """ requests.cookies ~~~~~~~~~~~~~~~~ Compatibility code to be able to use `cookielib.CookieJar` with requests. requests.utils imports from here, so be careful with imports. """ import copy import time import calendar from ._internal_utils import to_native_string from .compat import cookielib, urlparse, urlunparse, Morsel, MutableMapping try: import threading except ImportError: import dummy_threading as threading class MockRequest(object): """Wraps a `requests.Request` to mimic a `urllib2.Request`. The code in `cookielib.CookieJar` expects this interface in order to correctly manage cookie policies, i.e., determine whether a cookie can be set, given the domains of the request and the cookie. The original request object is read-only. The client is responsible for collecting the new headers via `get_new_headers()` and interpreting them appropriately. You probably want `get_cookie_header`, defined below. """ def __init__(self, request): self._r = request self._new_headers = {} self.type = urlparse(self._r.url).scheme def get_type(self): return self.type def get_host(self): return urlparse(self._r.url).netloc def get_origin_req_host(self): return self.get_host() def get_full_url(self): # Only return the response's URL if the user hadn't set the Host # header if not self._r.headers.get('Host'): return self._r.url # If they did set it, retrieve it and reconstruct the expected domain host = to_native_string(self._r.headers['Host'], encoding='utf-8') parsed = urlparse(self._r.url) # Reconstruct the URL as we expect it return urlunparse([ parsed.scheme, host, parsed.path, parsed.params, parsed.query, parsed.fragment ]) def is_unverifiable(self): return True def has_header(self, name): return name in self._r.headers or name in self._new_headers def get_header(self, name, default=None): return self._r.headers.get(name, self._new_headers.get(name, default)) def add_header(self, key, val): """cookielib has no legitimate use for this method; add it back if you find one.""" raise NotImplementedError("Cookie headers should be added with add_unredirected_header()") def add_unredirected_header(self, name, value): self._new_headers[name] = value def get_new_headers(self): return self._new_headers @property def unverifiable(self): return self.is_unverifiable() @property def origin_req_host(self): return self.get_origin_req_host() @property def host(self): return self.get_host() class MockResponse(object): """Wraps a `httplib.HTTPMessage` to mimic a `urllib.addinfourl`. ...what? Basically, expose the parsed HTTP headers from the server response the way `cookielib` expects to see them. """ def __init__(self, headers): """Make a MockResponse for `cookielib` to read. :param headers: a httplib.HTTPMessage or analogous carrying the headers """ self._headers = headers def info(self): return self._headers def getheaders(self, name): self._headers.getheaders(name) def extract_cookies_to_jar(jar, request, response): """Extract the cookies from the response into a CookieJar. :param jar: cookielib.CookieJar (not necessarily a RequestsCookieJar) :param request: our own requests.Request object :param response: urllib3.HTTPResponse object """ if not (hasattr(response, '_original_response') and response._original_response): return # the _original_response field is the wrapped httplib.HTTPResponse object, req = MockRequest(request) # pull out the HTTPMessage with the headers and put it in the mock: res = MockResponse(response._original_response.msg) jar.extract_cookies(res, req) def get_cookie_header(jar, request): """ Produce an appropriate Cookie header string to be sent with `request`, or None. :rtype: str """ r = MockRequest(request) jar.add_cookie_header(r) return r.get_new_headers().get('Cookie') def remove_cookie_by_name(cookiejar, name, domain=None, path=None): """Unsets a cookie by name, by default over all domains and paths. Wraps CookieJar.clear(), is O(n). """ clearables = [] for cookie in cookiejar: if cookie.name != name: continue if domain is not None and domain != cookie.domain: continue if path is not None and path != cookie.path: continue clearables.append((cookie.domain, cookie.path, cookie.name)) for domain, path, name in clearables: cookiejar.clear(domain, path, name) class CookieConflictError(RuntimeError): """There are two cookies that meet the criteria specified in the cookie jar. Use .get and .set and include domain and path args in order to be more specific. """ class RequestsCookieJar(cookielib.CookieJar, MutableMapping): """Compatibility class; is a cookielib.CookieJar, but exposes a dict interface. This is the CookieJar we create by default for requests and sessions that don't specify one, since some clients may expect response.cookies and session.cookies to support dict operations. Requests does not use the dict interface internally; it's just for compatibility with external client code. All requests code should work out of the box with externally provided instances of ``CookieJar``, e.g. ``LWPCookieJar`` and ``FileCookieJar``. Unlike a regular CookieJar, this class is pickleable. .. warning:: dictionary operations that are normally O(1) may be O(n). """ def get(self, name, default=None, domain=None, path=None): """Dict-like get() that also supports optional domain and path args in order to resolve naming collisions from using one cookie jar over multiple domains. .. warning:: operation is O(n), not O(1). """ try: return self._find_no_duplicates(name, domain, path) except KeyError: return default def set(self, name, value, **kwargs): """Dict-like set() that also supports optional domain and path args in order to resolve naming collisions from using one cookie jar over multiple domains. """ # support client code that unsets cookies by assignment of a None value: if value is None: remove_cookie_by_name(self, name, domain=kwargs.get('domain'), path=kwargs.get('path')) return if isinstance(value, Morsel): c = morsel_to_cookie(value) else: c = create_cookie(name, value, **kwargs) self.set_cookie(c) return c def iterkeys(self): """Dict-like iterkeys() that returns an iterator of names of cookies from the jar. .. seealso:: itervalues() and iteritems(). """ for cookie in iter(self): yield cookie.name def keys(self): """Dict-like keys() that returns a list of names of cookies from the jar. .. seealso:: values() and items(). """ return list(self.iterkeys()) def itervalues(self): """Dict-like itervalues() that returns an iterator of values of cookies from the jar. .. seealso:: iterkeys() and iteritems(). """ for cookie in iter(self): yield cookie.value def values(self): """Dict-like values() that returns a list of values of cookies from the jar. .. seealso:: keys() and items(). """ return list(self.itervalues()) def iteritems(self): """Dict-like iteritems() that returns an iterator of name-value tuples from the jar. .. seealso:: iterkeys() and itervalues(). """ for cookie in iter(self): yield cookie.name, cookie.value def items(self): """Dict-like items() that returns a list of name-value tuples from the jar. Allows client-code to call ``dict(RequestsCookieJar)`` and get a vanilla python dict of key value pairs. .. seealso:: keys() and values(). """ return list(self.iteritems()) def list_domains(self): """Utility method to list all the domains in the jar.""" domains = [] for cookie in iter(self): if cookie.domain not in domains: domains.append(cookie.domain) return domains def list_paths(self): """Utility method to list all the paths in the jar.""" paths = [] for cookie in iter(self): if cookie.path not in paths: paths.append(cookie.path) return paths def multiple_domains(self): """Returns True if there are multiple domains in the jar. Returns False otherwise. :rtype: bool """ domains = [] for cookie in iter(self): if cookie.domain is not None and cookie.domain in domains: return True domains.append(cookie.domain) return False # there is only one domain in jar def get_dict(self, domain=None, path=None): """Takes as an argument an optional domain and path and returns a plain old Python dict of name-value pairs of cookies that meet the requirements. :rtype: dict """ dictionary = {} for cookie in iter(self): if ( (domain is None or cookie.domain == domain) and (path is None or cookie.path == path) ): dictionary[cookie.name] = cookie.value return dictionary def __contains__(self, name): try: return super(RequestsCookieJar, self).__contains__(name) except CookieConflictError: return True def __getitem__(self, name): """Dict-like __getitem__() for compatibility with client code. Throws exception if there are more than one cookie with name. In that case, use the more explicit get() method instead. .. warning:: operation is O(n), not O(1). """ return self._find_no_duplicates(name) def __setitem__(self, name, value): """Dict-like __setitem__ for compatibility with client code. Throws exception if there is already a cookie of that name in the jar. In that case, use the more explicit set() method instead. """ self.set(name, value) def __delitem__(self, name): """Deletes a cookie given a name. Wraps ``cookielib.CookieJar``'s ``remove_cookie_by_name()``. """ remove_cookie_by_name(self, name) def set_cookie(self, cookie, *args, **kwargs): if hasattr(cookie.value, 'startswith') and cookie.value.startswith('"') and cookie.value.endswith('"'): cookie.value = cookie.value.replace('\\"', '') return super(RequestsCookieJar, self).set_cookie(cookie, *args, **kwargs) def update(self, other): """Updates this jar with cookies from another CookieJar or dict-like""" if isinstance(other, cookielib.CookieJar): for cookie in other: self.set_cookie(copy.copy(cookie)) else: super(RequestsCookieJar, self).update(other) def _find(self, name, domain=None, path=None): """Requests uses this method internally to get cookie values. If there are conflicting cookies, _find arbitrarily chooses one. See _find_no_duplicates if you want an exception thrown if there are conflicting cookies. :param name: a string containing name of cookie :param domain: (optional) string containing domain of cookie :param path: (optional) string containing path of cookie :return: cookie.value """ for cookie in iter(self): if cookie.name == name: if domain is None or cookie.domain == domain: if path is None or cookie.path == path: return cookie.value raise KeyError('name=%r, domain=%r, path=%r' % (name, domain, path)) def _find_no_duplicates(self, name, domain=None, path=None): """Both ``__get_item__`` and ``get`` call this function: it's never used elsewhere in Requests. :param name: a string containing name of cookie :param domain: (optional) string containing domain of cookie :param path: (optional) string containing path of cookie :raises KeyError: if cookie is not found :raises CookieConflictError: if there are multiple cookies that match name and optionally domain and path :return: cookie.value """ toReturn = None for cookie in iter(self): if cookie.name == name: if domain is None or cookie.domain == domain: if path is None or cookie.path == path: if toReturn is not None: # if there are multiple cookies that meet passed in criteria raise CookieConflictError('There are multiple cookies with name, %r' % (name)) toReturn = cookie.value # we will eventually return this as long as no cookie conflict if toReturn: return toReturn raise KeyError('name=%r, domain=%r, path=%r' % (name, domain, path)) def __getstate__(self): """Unlike a normal CookieJar, this class is pickleable.""" state = self.__dict__.copy() # remove the unpickleable RLock object state.pop('_cookies_lock') return state def __setstate__(self, state): """Unlike a normal CookieJar, this class is pickleable.""" self.__dict__.update(state) if '_cookies_lock' not in self.__dict__: self._cookies_lock = threading.RLock() def copy(self): """Return a copy of this RequestsCookieJar.""" new_cj = RequestsCookieJar() new_cj.set_policy(self.get_policy()) new_cj.update(self) return new_cj def get_policy(self): """Return the CookiePolicy instance used.""" return self._policy def _copy_cookie_jar(jar): if jar is None: return None if hasattr(jar, 'copy'): # We're dealing with an instance of RequestsCookieJar return jar.copy() # We're dealing with a generic CookieJar instance new_jar = copy.copy(jar) new_jar.clear() for cookie in jar: new_jar.set_cookie(copy.copy(cookie)) return new_jar def create_cookie(name, value, **kwargs): """Make a cookie from underspecified parameters. By default, the pair of `name` and `value` will be set for the domain '' and sent on every request (this is sometimes called a "supercookie"). """ result = { 'version': 0, 'name': name, 'value': value, 'port': None, 'domain': '', 'path': '/', 'secure': False, 'expires': None, 'discard': True, 'comment': None, 'comment_url': None, 'rest': {'HttpOnly': None}, 'rfc2109': False, } badargs = set(kwargs) - set(result) if badargs: err = 'create_cookie() got unexpected keyword arguments: %s' raise TypeError(err % list(badargs)) result.update(kwargs) result['port_specified'] = bool(result['port']) result['domain_specified'] = bool(result['domain']) result['domain_initial_dot'] = result['domain'].startswith('.') result['path_specified'] = bool(result['path']) return cookielib.Cookie(**result) def morsel_to_cookie(morsel): """Convert a Morsel object into a Cookie containing the one k/v pair.""" expires = None if morsel['max-age']: try: expires = int(time.time() + int(morsel['max-age'])) except ValueError: raise TypeError('max-age: %s must be integer' % morsel['max-age']) elif morsel['expires']: time_template = '%a, %d-%b-%Y %H:%M:%S GMT' expires = calendar.timegm( time.strptime(morsel['expires'], time_template) ) return create_cookie( comment=morsel['comment'], comment_url=bool(morsel['comment']), discard=False, domain=morsel['domain'], expires=expires, name=morsel.key, path=morsel['path'], port=None, rest={'HttpOnly': morsel['httponly']}, rfc2109=False, secure=bool(morsel['secure']), value=morsel.value, version=morsel['version'] or 0, ) def cookiejar_from_dict(cookie_dict, cookiejar=None, overwrite=True): """Returns a CookieJar from a key/value dictionary. :param cookie_dict: Dict of key/values to insert into CookieJar. :param cookiejar: (optional) A cookiejar to add the cookies to. :param overwrite: (optional) If False, will not replace cookies already in the jar with new ones. :rtype: CookieJar """ if cookiejar is None: cookiejar = RequestsCookieJar() if cookie_dict is not None: names_from_jar = [cookie.name for cookie in cookiejar] for name in cookie_dict: if overwrite or (name not in names_from_jar): cookiejar.set_cookie(create_cookie(name, cookie_dict[name])) return cookiejar def merge_cookies(cookiejar, cookies): """Add cookies to cookiejar and returns a merged CookieJar. :param cookiejar: CookieJar object to add the cookies to. :param cookies: Dictionary or CookieJar object to be added. :rtype: CookieJar """ if not isinstance(cookiejar, cookielib.CookieJar): raise ValueError('You can only merge into CookieJar') if isinstance(cookies, dict): cookiejar = cookiejar_from_dict( cookies, cookiejar=cookiejar, overwrite=False) elif isinstance(cookies, cookielib.CookieJar): try: cookiejar.update(cookies) except AttributeError: for cookie_in_jar in cookies: cookiejar.set_cookie(cookie_in_jar) return cookiejar ================================================ FILE: modules/requests/exceptions.py ================================================ # -*- coding: utf-8 -*- """ requests.exceptions ~~~~~~~~~~~~~~~~~~~ This module contains the set of Requests' exceptions. """ from urllib3.exceptions import HTTPError as BaseHTTPError class RequestException(IOError): """There was an ambiguous exception that occurred while handling your request. """ def __init__(self, *args, **kwargs): """Initialize RequestException with `request` and `response` objects.""" response = kwargs.pop('response', None) self.response = response self.request = kwargs.pop('request', None) if (response is not None and not self.request and hasattr(response, 'request')): self.request = self.response.request super(RequestException, self).__init__(*args, **kwargs) class InvalidJSONError(RequestException): """A JSON error occurred.""" class HTTPError(RequestException): """An HTTP error occurred.""" class ConnectionError(RequestException): """A Connection error occurred.""" class ProxyError(ConnectionError): """A proxy error occurred.""" class SSLError(ConnectionError): """An SSL error occurred.""" class Timeout(RequestException): """The request timed out. Catching this error will catch both :exc:`~requests.exceptions.ConnectTimeout` and :exc:`~requests.exceptions.ReadTimeout` errors. """ class ConnectTimeout(ConnectionError, Timeout): """The request timed out while trying to connect to the remote server. Requests that produced this error are safe to retry. """ class ReadTimeout(Timeout): """The server did not send any data in the allotted amount of time.""" class URLRequired(RequestException): """A valid URL is required to make a request.""" class TooManyRedirects(RequestException): """Too many redirects.""" class MissingSchema(RequestException, ValueError): """The URL schema (e.g. http or https) is missing.""" class InvalidSchema(RequestException, ValueError): """See defaults.py for valid schemas.""" class InvalidURL(RequestException, ValueError): """The URL provided was somehow invalid.""" class InvalidHeader(RequestException, ValueError): """The header value provided was somehow invalid.""" class InvalidProxyURL(InvalidURL): """The proxy URL provided is invalid.""" class ChunkedEncodingError(RequestException): """The server declared chunked encoding but sent an invalid chunk.""" class ContentDecodingError(RequestException, BaseHTTPError): """Failed to decode response content.""" class StreamConsumedError(RequestException, TypeError): """The content for this response was already consumed.""" class RetryError(RequestException): """Custom retries logic failed""" class UnrewindableBodyError(RequestException): """Requests encountered an error when trying to rewind a body.""" # Warnings class RequestsWarning(Warning): """Base warning for Requests.""" class FileModeWarning(RequestsWarning, DeprecationWarning): """A file was opened in text mode, but Requests determined its binary length.""" class RequestsDependencyWarning(RequestsWarning): """An imported dependency doesn't match the expected version range.""" ================================================ FILE: modules/requests/help.py ================================================ """Module containing bug report helper(s).""" from __future__ import print_function import json import platform import sys import ssl import idna import urllib3 from . import __version__ as requests_version try: import charset_normalizer except ImportError: charset_normalizer = None try: import chardet except ImportError: chardet = None try: from urllib3.contrib import pyopenssl except ImportError: pyopenssl = None OpenSSL = None cryptography = None else: import OpenSSL import cryptography def _implementation(): """Return a dict with the Python implementation and version. Provide both the name and the version of the Python implementation currently running. For example, on CPython 2.7.5 it will return {'name': 'CPython', 'version': '2.7.5'}. This function works best on CPython and PyPy: in particular, it probably doesn't work for Jython or IronPython. Future investigation should be done to work out the correct shape of the code for those platforms. """ implementation = platform.python_implementation() if implementation == 'CPython': implementation_version = platform.python_version() elif implementation == 'PyPy': implementation_version = '%s.%s.%s' % (sys.pypy_version_info.major, sys.pypy_version_info.minor, sys.pypy_version_info.micro) if sys.pypy_version_info.releaselevel != 'final': implementation_version = ''.join([ implementation_version, sys.pypy_version_info.releaselevel ]) elif implementation == 'Jython': implementation_version = platform.python_version() # Complete Guess elif implementation == 'IronPython': implementation_version = platform.python_version() # Complete Guess else: implementation_version = 'Unknown' return {'name': implementation, 'version': implementation_version} def info(): """Generate information for a bug report.""" try: platform_info = { 'system': platform.system(), 'release': platform.release(), } except IOError: platform_info = { 'system': 'Unknown', 'release': 'Unknown', } implementation_info = _implementation() urllib3_info = {'version': urllib3.__version__} charset_normalizer_info = {'version': None} chardet_info = {'version': None} if charset_normalizer: charset_normalizer_info = {'version': charset_normalizer.__version__} if chardet: chardet_info = {'version': chardet.__version__} pyopenssl_info = { 'version': None, 'openssl_version': '', } if OpenSSL: pyopenssl_info = { 'version': OpenSSL.__version__, 'openssl_version': '%x' % OpenSSL.SSL.OPENSSL_VERSION_NUMBER, } cryptography_info = { 'version': getattr(cryptography, '__version__', ''), } idna_info = { 'version': getattr(idna, '__version__', ''), } system_ssl = ssl.OPENSSL_VERSION_NUMBER system_ssl_info = { 'version': '%x' % system_ssl if system_ssl is not None else '' } return { 'platform': platform_info, 'implementation': implementation_info, 'system_ssl': system_ssl_info, 'using_pyopenssl': pyopenssl is not None, 'using_charset_normalizer': chardet is None, 'pyOpenSSL': pyopenssl_info, 'urllib3': urllib3_info, 'chardet': chardet_info, 'charset_normalizer': charset_normalizer_info, 'cryptography': cryptography_info, 'idna': idna_info, 'requests': { 'version': requests_version, }, } def main(): """Pretty-print the bug information as JSON.""" print(json.dumps(info(), sort_keys=True, indent=2)) if __name__ == '__main__': main() ================================================ FILE: modules/requests/hooks.py ================================================ # -*- coding: utf-8 -*- """ requests.hooks ~~~~~~~~~~~~~~ This module provides the capabilities for the Requests hooks system. Available hooks: ``response``: The response generated from a Request. """ HOOKS = ['response'] def default_hooks(): return {event: [] for event in HOOKS} # TODO: response is the only one def dispatch_hook(key, hooks, hook_data, **kwargs): """Dispatches a hook dictionary on a given piece of data.""" hooks = hooks or {} hooks = hooks.get(key) if hooks: if hasattr(hooks, '__call__'): hooks = [hooks] for hook in hooks: _hook_data = hook(hook_data, **kwargs) if _hook_data is not None: hook_data = _hook_data return hook_data ================================================ FILE: modules/requests/models.py ================================================ # -*- coding: utf-8 -*- """ requests.models ~~~~~~~~~~~~~~~ This module contains the primary objects that power Requests. """ import datetime import sys # Import encoding now, to avoid implicit import later. # Implicit import within threads may cause LookupError when standard library is in a ZIP, # such as in Embedded Python. See https://github.com/psf/requests/issues/3578. import encodings.idna from urllib3.fields import RequestField from urllib3.filepost import encode_multipart_formdata from urllib3.util import parse_url from urllib3.exceptions import ( DecodeError, ReadTimeoutError, ProtocolError, LocationParseError) from io import UnsupportedOperation from .hooks import default_hooks from .structures import CaseInsensitiveDict from .auth import HTTPBasicAuth from .cookies import cookiejar_from_dict, get_cookie_header, _copy_cookie_jar from .exceptions import ( HTTPError, MissingSchema, InvalidURL, ChunkedEncodingError, ContentDecodingError, ConnectionError, StreamConsumedError, InvalidJSONError) from ._internal_utils import to_native_string, unicode_is_ascii from .utils import ( guess_filename, get_auth_from_url, requote_uri, stream_decode_response_unicode, to_key_val_list, parse_header_links, iter_slices, guess_json_utf, super_len, check_header_validity) from .compat import ( Callable, Mapping, cookielib, urlunparse, urlsplit, urlencode, str, bytes, is_py2, chardet, builtin_str, basestring) from .compat import json as complexjson from .status_codes import codes #: The set of HTTP status codes that indicate an automatically #: processable redirect. REDIRECT_STATI = ( codes.moved, # 301 codes.found, # 302 codes.other, # 303 codes.temporary_redirect, # 307 codes.permanent_redirect, # 308 ) DEFAULT_REDIRECT_LIMIT = 30 CONTENT_CHUNK_SIZE = 10 * 1024 ITER_CHUNK_SIZE = 512 class RequestEncodingMixin(object): @property def path_url(self): """Build the path URL to use.""" url = [] p = urlsplit(self.url) path = p.path if not path: path = '/' url.append(path) query = p.query if query: url.append('?') url.append(query) return ''.join(url) @staticmethod def _encode_params(data): """Encode parameters in a piece of data. Will successfully encode parameters when passed as a dict or a list of 2-tuples. Order is retained if data is a list of 2-tuples but arbitrary if parameters are supplied as a dict. """ if isinstance(data, (str, bytes)): return data elif hasattr(data, 'read'): return data elif hasattr(data, '__iter__'): result = [] for k, vs in to_key_val_list(data): if isinstance(vs, basestring) or not hasattr(vs, '__iter__'): vs = [vs] for v in vs: if v is not None: result.append( (k.encode('utf-8') if isinstance(k, str) else k, v.encode('utf-8') if isinstance(v, str) else v)) return urlencode(result, doseq=True) else: return data @staticmethod def _encode_files(files, data): """Build the body for a multipart/form-data request. Will successfully encode files when passed as a dict or a list of tuples. Order is retained if data is a list of tuples but arbitrary if parameters are supplied as a dict. The tuples may be 2-tuples (filename, fileobj), 3-tuples (filename, fileobj, contentype) or 4-tuples (filename, fileobj, contentype, custom_headers). """ if (not files): raise ValueError("Files must be provided.") elif isinstance(data, basestring): raise ValueError("Data must not be a string.") new_fields = [] fields = to_key_val_list(data or {}) files = to_key_val_list(files or {}) for field, val in fields: if isinstance(val, basestring) or not hasattr(val, '__iter__'): val = [val] for v in val: if v is not None: # Don't call str() on bytestrings: in Py3 it all goes wrong. if not isinstance(v, bytes): v = str(v) new_fields.append( (field.decode('utf-8') if isinstance(field, bytes) else field, v.encode('utf-8') if isinstance(v, str) else v)) for (k, v) in files: # support for explicit filename ft = None fh = None if isinstance(v, (tuple, list)): if len(v) == 2: fn, fp = v elif len(v) == 3: fn, fp, ft = v else: fn, fp, ft, fh = v else: fn = guess_filename(v) or k fp = v if isinstance(fp, (str, bytes, bytearray)): fdata = fp elif hasattr(fp, 'read'): fdata = fp.read() elif fp is None: continue else: fdata = fp rf = RequestField(name=k, data=fdata, filename=fn, headers=fh) rf.make_multipart(content_type=ft) new_fields.append(rf) body, content_type = encode_multipart_formdata(new_fields) return body, content_type class RequestHooksMixin(object): def register_hook(self, event, hook): """Properly register a hook.""" if event not in self.hooks: raise ValueError('Unsupported event specified, with event name "%s"' % (event)) if isinstance(hook, Callable): self.hooks[event].append(hook) elif hasattr(hook, '__iter__'): self.hooks[event].extend(h for h in hook if isinstance(h, Callable)) def deregister_hook(self, event, hook): """Deregister a previously registered hook. Returns True if the hook existed, False if not. """ try: self.hooks[event].remove(hook) return True except ValueError: return False class Request(RequestHooksMixin): """A user-created :class:`Request ` object. Used to prepare a :class:`PreparedRequest `, which is sent to the server. :param method: HTTP method to use. :param url: URL to send. :param headers: dictionary of headers to send. :param files: dictionary of {filename: fileobject} files to multipart upload. :param data: the body to attach to the request. If a dictionary or list of tuples ``[(key, value)]`` is provided, form-encoding will take place. :param json: json for the body to attach to the request (if files or data is not specified). :param params: URL parameters to append to the URL. If a dictionary or list of tuples ``[(key, value)]`` is provided, form-encoding will take place. :param auth: Auth handler or (user, pass) tuple. :param cookies: dictionary or CookieJar of cookies to attach to this request. :param hooks: dictionary of callback hooks, for internal usage. Usage:: >>> import requests >>> req = requests.Request('GET', 'https://httpbin.org/get') >>> req.prepare() """ def __init__(self, method=None, url=None, headers=None, files=None, data=None, params=None, auth=None, cookies=None, hooks=None, json=None): # Default empty dicts for dict params. data = [] if data is None else data files = [] if files is None else files headers = {} if headers is None else headers params = {} if params is None else params hooks = {} if hooks is None else hooks self.hooks = default_hooks() for (k, v) in list(hooks.items()): self.register_hook(event=k, hook=v) self.method = method self.url = url self.headers = headers self.files = files self.data = data self.json = json self.params = params self.auth = auth self.cookies = cookies def __repr__(self): return '' % (self.method) def prepare(self): """Constructs a :class:`PreparedRequest ` for transmission and returns it.""" p = PreparedRequest() p.prepare( method=self.method, url=self.url, headers=self.headers, files=self.files, data=self.data, json=self.json, params=self.params, auth=self.auth, cookies=self.cookies, hooks=self.hooks, ) return p class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): """The fully mutable :class:`PreparedRequest ` object, containing the exact bytes that will be sent to the server. Instances are generated from a :class:`Request ` object, and should not be instantiated manually; doing so may produce undesirable effects. Usage:: >>> import requests >>> req = requests.Request('GET', 'https://httpbin.org/get') >>> r = req.prepare() >>> r >>> s = requests.Session() >>> s.send(r) """ def __init__(self): #: HTTP verb to send to the server. self.method = None #: HTTP URL to send the request to. self.url = None #: dictionary of HTTP headers. self.headers = None # The `CookieJar` used to create the Cookie header will be stored here # after prepare_cookies is called self._cookies = None #: request body to send to the server. self.body = None #: dictionary of callback hooks, for internal usage. self.hooks = default_hooks() #: integer denoting starting position of a readable file-like body. self._body_position = None def prepare(self, method=None, url=None, headers=None, files=None, data=None, params=None, auth=None, cookies=None, hooks=None, json=None): """Prepares the entire request with the given parameters.""" self.prepare_method(method) self.prepare_url(url, params) self.prepare_headers(headers) self.prepare_cookies(cookies) self.prepare_body(data, files, json) self.prepare_auth(auth, url) # Note that prepare_auth must be last to enable authentication schemes # such as OAuth to work on a fully prepared request. # This MUST go after prepare_auth. Authenticators could add a hook self.prepare_hooks(hooks) def __repr__(self): return '' % (self.method) def copy(self): p = PreparedRequest() p.method = self.method p.url = self.url p.headers = self.headers.copy() if self.headers is not None else None p._cookies = _copy_cookie_jar(self._cookies) p.body = self.body p.hooks = self.hooks p._body_position = self._body_position return p def prepare_method(self, method): """Prepares the given HTTP method.""" self.method = method if self.method is not None: self.method = to_native_string(self.method.upper()) @staticmethod def _get_idna_encoded_host(host): import idna try: host = idna.encode(host, uts46=True).decode('utf-8') except idna.IDNAError: raise UnicodeError return host def prepare_url(self, url, params): """Prepares the given HTTP URL.""" #: Accept objects that have string representations. #: We're unable to blindly call unicode/str functions #: as this will include the bytestring indicator (b'') #: on python 3.x. #: https://github.com/psf/requests/pull/2238 if isinstance(url, bytes): url = url.decode('utf8') else: url = unicode(url) if is_py2 else str(url) # Remove leading whitespaces from url url = url.lstrip() # Don't do any URL preparation for non-HTTP schemes like `mailto`, # `data` etc to work around exceptions from `url_parse`, which # handles RFC 3986 only. if ':' in url and not url.lower().startswith('http'): self.url = url return # Support for unicode domain names and paths. try: scheme, auth, host, port, path, query, fragment = parse_url(url) except LocationParseError as e: raise InvalidURL(*e.args) if not scheme: error = ("Invalid URL {0!r}: No schema supplied. Perhaps you meant http://{0}?") error = error.format(to_native_string(url, 'utf8')) raise MissingSchema(error) if not host: raise InvalidURL("Invalid URL %r: No host supplied" % url) # In general, we want to try IDNA encoding the hostname if the string contains # non-ASCII characters. This allows users to automatically get the correct IDNA # behaviour. For strings containing only ASCII characters, we need to also verify # it doesn't start with a wildcard (*), before allowing the unencoded hostname. if not unicode_is_ascii(host): try: host = self._get_idna_encoded_host(host) except UnicodeError: raise InvalidURL('URL has an invalid label.') elif host.startswith(u'*'): raise InvalidURL('URL has an invalid label.') # Carefully reconstruct the network location netloc = auth or '' if netloc: netloc += '@' netloc += host if port: netloc += ':' + str(port) # Bare domains aren't valid URLs. if not path: path = '/' if is_py2: if isinstance(scheme, str): scheme = scheme.encode('utf-8') if isinstance(netloc, str): netloc = netloc.encode('utf-8') if isinstance(path, str): path = path.encode('utf-8') if isinstance(query, str): query = query.encode('utf-8') if isinstance(fragment, str): fragment = fragment.encode('utf-8') if isinstance(params, (str, bytes)): params = to_native_string(params) enc_params = self._encode_params(params) if enc_params: if query: query = '%s&%s' % (query, enc_params) else: query = enc_params url = requote_uri(urlunparse([scheme, netloc, path, None, query, fragment])) self.url = url def prepare_headers(self, headers): """Prepares the given HTTP headers.""" self.headers = CaseInsensitiveDict() if headers: for header in headers.items(): # Raise exception on invalid header value. check_header_validity(header) name, value = header self.headers[to_native_string(name)] = value def prepare_body(self, data, files, json=None): """Prepares the given HTTP body data.""" # Check if file, fo, generator, iterator. # If not, run through normal process. # Nottin' on you. body = None content_type = None if not data and json is not None: # urllib3 requires a bytes-like body. Python 2's json.dumps # provides this natively, but Python 3 gives a Unicode string. content_type = 'application/json' try: body = complexjson.dumps(json, allow_nan=False) except ValueError as ve: raise InvalidJSONError(ve, request=self) if not isinstance(body, bytes): body = body.encode('utf-8') is_stream = all([ hasattr(data, '__iter__'), not isinstance(data, (basestring, list, tuple, Mapping)) ]) if is_stream: try: length = super_len(data) except (TypeError, AttributeError, UnsupportedOperation): length = None body = data if getattr(body, 'tell', None) is not None: # Record the current file position before reading. # This will allow us to rewind a file in the event # of a redirect. try: self._body_position = body.tell() except (IOError, OSError): # This differentiates from None, allowing us to catch # a failed `tell()` later when trying to rewind the body self._body_position = object() if files: raise NotImplementedError('Streamed bodies and files are mutually exclusive.') if length: self.headers['Content-Length'] = builtin_str(length) else: self.headers['Transfer-Encoding'] = 'chunked' else: # Multi-part file uploads. if files: (body, content_type) = self._encode_files(files, data) else: if data: body = self._encode_params(data) if isinstance(data, basestring) or hasattr(data, 'read'): content_type = None else: content_type = 'application/x-www-form-urlencoded' self.prepare_content_length(body) # Add content-type if it wasn't explicitly provided. if content_type and ('content-type' not in self.headers): self.headers['Content-Type'] = content_type self.body = body def prepare_content_length(self, body): """Prepare Content-Length header based on request method and body""" if body is not None: length = super_len(body) if length: # If length exists, set it. Otherwise, we fallback # to Transfer-Encoding: chunked. self.headers['Content-Length'] = builtin_str(length) elif self.method not in ('GET', 'HEAD') and self.headers.get('Content-Length') is None: # Set Content-Length to 0 for methods that can have a body # but don't provide one. (i.e. not GET or HEAD) self.headers['Content-Length'] = '0' def prepare_auth(self, auth, url=''): """Prepares the given HTTP auth data.""" # If no Auth is explicitly provided, extract it from the URL first. if auth is None: url_auth = get_auth_from_url(self.url) auth = url_auth if any(url_auth) else None if auth: if isinstance(auth, tuple) and len(auth) == 2: # special-case basic HTTP auth auth = HTTPBasicAuth(*auth) # Allow auth to make its changes. r = auth(self) # Update self to reflect the auth changes. self.__dict__.update(r.__dict__) # Recompute Content-Length self.prepare_content_length(self.body) def prepare_cookies(self, cookies): """Prepares the given HTTP cookie data. This function eventually generates a ``Cookie`` header from the given cookies using cookielib. Due to cookielib's design, the header will not be regenerated if it already exists, meaning this function can only be called once for the life of the :class:`PreparedRequest ` object. Any subsequent calls to ``prepare_cookies`` will have no actual effect, unless the "Cookie" header is removed beforehand. """ if isinstance(cookies, cookielib.CookieJar): self._cookies = cookies else: self._cookies = cookiejar_from_dict(cookies) cookie_header = get_cookie_header(self._cookies, self) if cookie_header is not None: self.headers['Cookie'] = cookie_header def prepare_hooks(self, hooks): """Prepares the given hooks.""" # hooks can be passed as None to the prepare method and to this # method. To prevent iterating over None, simply use an empty list # if hooks is False-y hooks = hooks or [] for event in hooks: self.register_hook(event, hooks[event]) class Response(object): """The :class:`Response ` object, which contains a server's response to an HTTP request. """ __attrs__ = [ '_content', 'status_code', 'headers', 'url', 'history', 'encoding', 'reason', 'cookies', 'elapsed', 'request' ] def __init__(self): self._content = False self._content_consumed = False self._next = None #: Integer Code of responded HTTP Status, e.g. 404 or 200. self.status_code = None #: Case-insensitive Dictionary of Response Headers. #: For example, ``headers['content-encoding']`` will return the #: value of a ``'Content-Encoding'`` response header. self.headers = CaseInsensitiveDict() #: File-like object representation of response (for advanced usage). #: Use of ``raw`` requires that ``stream=True`` be set on the request. #: This requirement does not apply for use internally to Requests. self.raw = None #: Final URL location of Response. self.url = None #: Encoding to decode with when accessing r.text. self.encoding = None #: A list of :class:`Response ` objects from #: the history of the Request. Any redirect responses will end #: up here. The list is sorted from the oldest to the most recent request. self.history = [] #: Textual reason of responded HTTP Status, e.g. "Not Found" or "OK". self.reason = None #: A CookieJar of Cookies the server sent back. self.cookies = cookiejar_from_dict({}) #: The amount of time elapsed between sending the request #: and the arrival of the response (as a timedelta). #: This property specifically measures the time taken between sending #: the first byte of the request and finishing parsing the headers. It #: is therefore unaffected by consuming the response content or the #: value of the ``stream`` keyword argument. self.elapsed = datetime.timedelta(0) #: The :class:`PreparedRequest ` object to which this #: is a response. self.request = None def __enter__(self): return self def __exit__(self, *args): self.close() def __getstate__(self): # Consume everything; accessing the content attribute makes # sure the content has been fully read. if not self._content_consumed: self.content return {attr: getattr(self, attr, None) for attr in self.__attrs__} def __setstate__(self, state): for name, value in state.items(): setattr(self, name, value) # pickled objects do not have .raw setattr(self, '_content_consumed', True) setattr(self, 'raw', None) def __repr__(self): return '' % (self.status_code) def __bool__(self): """Returns True if :attr:`status_code` is less than 400. This attribute checks if the status code of the response is between 400 and 600 to see if there was a client error or a server error. If the status code, is between 200 and 400, this will return True. This is **not** a check to see if the response code is ``200 OK``. """ return self.ok def __nonzero__(self): """Returns True if :attr:`status_code` is less than 400. This attribute checks if the status code of the response is between 400 and 600 to see if there was a client error or a server error. If the status code, is between 200 and 400, this will return True. This is **not** a check to see if the response code is ``200 OK``. """ return self.ok def __iter__(self): """Allows you to use a response as an iterator.""" return self.iter_content(128) @property def ok(self): """Returns True if :attr:`status_code` is less than 400, False if not. This attribute checks if the status code of the response is between 400 and 600 to see if there was a client error or a server error. If the status code is between 200 and 400, this will return True. This is **not** a check to see if the response code is ``200 OK``. """ try: self.raise_for_status() except HTTPError: return False return True @property def is_redirect(self): """True if this Response is a well-formed HTTP redirect that could have been processed automatically (by :meth:`Session.resolve_redirects`). """ return ('location' in self.headers and self.status_code in REDIRECT_STATI) @property def is_permanent_redirect(self): """True if this Response one of the permanent versions of redirect.""" return ('location' in self.headers and self.status_code in (codes.moved_permanently, codes.permanent_redirect)) @property def next(self): """Returns a PreparedRequest for the next request in a redirect chain, if there is one.""" return self._next @property def apparent_encoding(self): """The apparent encoding, provided by the charset_normalizer or chardet libraries.""" return chardet.detect(self.content)['encoding'] def iter_content(self, chunk_size=1, decode_unicode=False): """Iterates over the response data. When stream=True is set on the request, this avoids reading the content at once into memory for large responses. The chunk size is the number of bytes it should read into memory. This is not necessarily the length of each item returned as decoding can take place. chunk_size must be of type int or None. A value of None will function differently depending on the value of `stream`. stream=True will read data as it arrives in whatever size the chunks are received. If stream=False, data is returned as a single chunk. If decode_unicode is True, content will be decoded using the best available encoding based on the response. """ def generate(): # Special case for urllib3. if hasattr(self.raw, 'stream'): try: for chunk in self.raw.stream(chunk_size, decode_content=True): yield chunk except ProtocolError as e: raise ChunkedEncodingError(e) except DecodeError as e: raise ContentDecodingError(e) except ReadTimeoutError as e: raise ConnectionError(e) else: # Standard file-like object. while True: chunk = self.raw.read(chunk_size) if not chunk: break yield chunk self._content_consumed = True if self._content_consumed and isinstance(self._content, bool): raise StreamConsumedError() elif chunk_size is not None and not isinstance(chunk_size, int): raise TypeError("chunk_size must be an int, it is instead a %s." % type(chunk_size)) # simulate reading small chunks of the content reused_chunks = iter_slices(self._content, chunk_size) stream_chunks = generate() chunks = reused_chunks if self._content_consumed else stream_chunks if decode_unicode: chunks = stream_decode_response_unicode(chunks, self) return chunks def iter_lines(self, chunk_size=ITER_CHUNK_SIZE, decode_unicode=False, delimiter=None): """Iterates over the response data, one line at a time. When stream=True is set on the request, this avoids reading the content at once into memory for large responses. .. note:: This method is not reentrant safe. """ pending = None for chunk in self.iter_content(chunk_size=chunk_size, decode_unicode=decode_unicode): if pending is not None: chunk = pending + chunk if delimiter: lines = chunk.split(delimiter) else: lines = chunk.splitlines() if lines and lines[-1] and chunk and lines[-1][-1] == chunk[-1]: pending = lines.pop() else: pending = None for line in lines: yield line if pending is not None: yield pending @property def content(self): """Content of the response, in bytes.""" if self._content is False: # Read the contents. if self._content_consumed: raise RuntimeError( 'The content for this response was already consumed') if self.status_code == 0 or self.raw is None: self._content = None else: self._content = b''.join(self.iter_content(CONTENT_CHUNK_SIZE)) or b'' self._content_consumed = True # don't need to release the connection; that's been handled by urllib3 # since we exhausted the data. return self._content @property def text(self): """Content of the response, in unicode. If Response.encoding is None, encoding will be guessed using ``charset_normalizer`` or ``chardet``. The encoding of the response content is determined based solely on HTTP headers, following RFC 2616 to the letter. If you can take advantage of non-HTTP knowledge to make a better guess at the encoding, you should set ``r.encoding`` appropriately before accessing this property. """ # Try charset from content-type content = None encoding = self.encoding if not self.content: return str('') # Fallback to auto-detected encoding. if self.encoding is None: encoding = self.apparent_encoding # Decode unicode from given encoding. try: content = str(self.content, encoding, errors='replace') except (LookupError, TypeError): # A LookupError is raised if the encoding was not found which could # indicate a misspelling or similar mistake. # # A TypeError can be raised if encoding is None # # So we try blindly encoding. content = str(self.content, errors='replace') return content def json(self, **kwargs): r"""Returns the json-encoded content of a response, if any. :param \*\*kwargs: Optional arguments that ``json.loads`` takes. :raises simplejson.JSONDecodeError: If the response body does not contain valid json and simplejson is installed. :raises json.JSONDecodeError: If the response body does not contain valid json and simplejson is not installed on Python 3. :raises ValueError: If the response body does not contain valid json and simplejson is not installed on Python 2. """ if not self.encoding and self.content and len(self.content) > 3: # No encoding set. JSON RFC 4627 section 3 states we should expect # UTF-8, -16 or -32. Detect which one to use; If the detection or # decoding fails, fall back to `self.text` (using charset_normalizer to make # a best guess). encoding = guess_json_utf(self.content) if encoding is not None: try: return complexjson.loads( self.content.decode(encoding), **kwargs ) except UnicodeDecodeError: # Wrong UTF codec detected; usually because it's not UTF-8 # but some other 8-bit codec. This is an RFC violation, # and the server didn't bother to tell us what codec *was* # used. pass return complexjson.loads(self.text, **kwargs) @property def links(self): """Returns the parsed header links of the response, if any.""" header = self.headers.get('link') # l = MultiDict() l = {} if header: links = parse_header_links(header) for link in links: key = link.get('rel') or link.get('url') l[key] = link return l def raise_for_status(self): """Raises :class:`HTTPError`, if one occurred.""" http_error_msg = '' if isinstance(self.reason, bytes): # We attempt to decode utf-8 first because some servers # choose to localize their reason strings. If the string # isn't utf-8, we fall back to iso-8859-1 for all other # encodings. (See PR #3538) try: reason = self.reason.decode('utf-8') except UnicodeDecodeError: reason = self.reason.decode('iso-8859-1') else: reason = self.reason if 400 <= self.status_code < 500: http_error_msg = u'%s Client Error: %s for url: %s' % (self.status_code, reason, self.url) elif 500 <= self.status_code < 600: http_error_msg = u'%s Server Error: %s for url: %s' % (self.status_code, reason, self.url) if http_error_msg: raise HTTPError(http_error_msg, response=self) def close(self): """Releases the connection back to the pool. Once this method has been called the underlying ``raw`` object must not be accessed again. *Note: Should not normally need to be called explicitly.* """ if not self._content_consumed: self.raw.close() release_conn = getattr(self.raw, 'release_conn', None) if release_conn is not None: release_conn() ================================================ FILE: modules/requests/packages.py ================================================ import sys try: import chardet except ImportError: import charset_normalizer as chardet import warnings warnings.filterwarnings('ignore', 'Trying to detect', module='charset_normalizer') # This code exists for backwards compatibility reasons. # I don't like it either. Just look the other way. :) for package in ('urllib3', 'idna'): locals()[package] = __import__(package) # This traversal is apparently necessary such that the identities are # preserved (requests.packages.urllib3.* is urllib3.*) for mod in list(sys.modules): if mod == package or mod.startswith(package + '.'): sys.modules['requests.packages.' + mod] = sys.modules[mod] target = chardet.__name__ for mod in list(sys.modules): if mod == target or mod.startswith(target + '.'): sys.modules['requests.packages.' + target.replace(target, 'chardet')] = sys.modules[mod] # Kinda cool, though, right? ================================================ FILE: modules/requests/sessions.py ================================================ # -*- coding: utf-8 -*- """ requests.sessions ~~~~~~~~~~~~~~~~~ This module provides a Session object to manage and persist settings across requests (cookies, auth, proxies). """ import os import sys import time from datetime import timedelta from collections import OrderedDict from .auth import _basic_auth_str from .compat import cookielib, is_py3, urljoin, urlparse, Mapping from .cookies import ( cookiejar_from_dict, extract_cookies_to_jar, RequestsCookieJar, merge_cookies) from .models import Request, PreparedRequest, DEFAULT_REDIRECT_LIMIT from .hooks import default_hooks, dispatch_hook from ._internal_utils import to_native_string from .utils import to_key_val_list, default_headers, DEFAULT_PORTS from .exceptions import ( TooManyRedirects, InvalidSchema, ChunkedEncodingError, ContentDecodingError) from .structures import CaseInsensitiveDict from .adapters import HTTPAdapter from .utils import ( requote_uri, get_environ_proxies, get_netrc_auth, should_bypass_proxies, get_auth_from_url, rewind_body ) from .status_codes import codes # formerly defined here, reexposed here for backward compatibility from .models import REDIRECT_STATI # Preferred clock, based on which one is more accurate on a given system. if sys.platform == 'win32': try: # Python 3.4+ preferred_clock = time.perf_counter except AttributeError: # Earlier than Python 3. preferred_clock = time.clock else: preferred_clock = time.time def merge_setting(request_setting, session_setting, dict_class=OrderedDict): """Determines appropriate setting for a given request, taking into account the explicit setting on that request, and the setting in the session. If a setting is a dictionary, they will be merged together using `dict_class` """ if session_setting is None: return request_setting if request_setting is None: return session_setting # Bypass if not a dictionary (e.g. verify) if not ( isinstance(session_setting, Mapping) and isinstance(request_setting, Mapping) ): return request_setting merged_setting = dict_class(to_key_val_list(session_setting)) merged_setting.update(to_key_val_list(request_setting)) # Remove keys that are set to None. Extract keys first to avoid altering # the dictionary during iteration. none_keys = [k for (k, v) in merged_setting.items() if v is None] for key in none_keys: del merged_setting[key] return merged_setting def merge_hooks(request_hooks, session_hooks, dict_class=OrderedDict): """Properly merges both requests and session hooks. This is necessary because when request_hooks == {'response': []}, the merge breaks Session hooks entirely. """ if session_hooks is None or session_hooks.get('response') == []: return request_hooks if request_hooks is None or request_hooks.get('response') == []: return session_hooks return merge_setting(request_hooks, session_hooks, dict_class) class SessionRedirectMixin(object): def get_redirect_target(self, resp): """Receives a Response. Returns a redirect URI or ``None``""" # Due to the nature of how requests processes redirects this method will # be called at least once upon the original response and at least twice # on each subsequent redirect response (if any). # If a custom mixin is used to handle this logic, it may be advantageous # to cache the redirect location onto the response object as a private # attribute. if resp.is_redirect: location = resp.headers['location'] # Currently the underlying http module on py3 decode headers # in latin1, but empirical evidence suggests that latin1 is very # rarely used with non-ASCII characters in HTTP headers. # It is more likely to get UTF8 header rather than latin1. # This causes incorrect handling of UTF8 encoded location headers. # To solve this, we re-encode the location in latin1. if is_py3: location = location.encode('latin1') return to_native_string(location, 'utf8') return None def should_strip_auth(self, old_url, new_url): """Decide whether Authorization header should be removed when redirecting""" old_parsed = urlparse(old_url) new_parsed = urlparse(new_url) if old_parsed.hostname != new_parsed.hostname: return True # Special case: allow http -> https redirect when using the standard # ports. This isn't specified by RFC 7235, but is kept to avoid # breaking backwards compatibility with older versions of requests # that allowed any redirects on the same host. if (old_parsed.scheme == 'http' and old_parsed.port in (80, None) and new_parsed.scheme == 'https' and new_parsed.port in (443, None)): return False # Handle default port usage corresponding to scheme. changed_port = old_parsed.port != new_parsed.port changed_scheme = old_parsed.scheme != new_parsed.scheme default_port = (DEFAULT_PORTS.get(old_parsed.scheme, None), None) if (not changed_scheme and old_parsed.port in default_port and new_parsed.port in default_port): return False # Standard case: root URI must match return changed_port or changed_scheme def resolve_redirects(self, resp, req, stream=False, timeout=None, verify=True, cert=None, proxies=None, yield_requests=False, **adapter_kwargs): """Receives a Response. Returns a generator of Responses or Requests.""" hist = [] # keep track of history url = self.get_redirect_target(resp) previous_fragment = urlparse(req.url).fragment while url: prepared_request = req.copy() # Update history and keep track of redirects. # resp.history must ignore the original request in this loop hist.append(resp) resp.history = hist[1:] try: resp.content # Consume socket so it can be released except (ChunkedEncodingError, ContentDecodingError, RuntimeError): resp.raw.read(decode_content=False) if len(resp.history) >= self.max_redirects: raise TooManyRedirects('Exceeded {} redirects.'.format(self.max_redirects), response=resp) # Release the connection back into the pool. resp.close() # Handle redirection without scheme (see: RFC 1808 Section 4) if url.startswith('//'): parsed_rurl = urlparse(resp.url) url = ':'.join([to_native_string(parsed_rurl.scheme), url]) # Normalize url case and attach previous fragment if needed (RFC 7231 7.1.2) parsed = urlparse(url) if parsed.fragment == '' and previous_fragment: parsed = parsed._replace(fragment=previous_fragment) elif parsed.fragment: previous_fragment = parsed.fragment url = parsed.geturl() # Facilitate relative 'location' headers, as allowed by RFC 7231. # (e.g. '/path/to/resource' instead of 'http://domain.tld/path/to/resource') # Compliant with RFC3986, we percent encode the url. if not parsed.netloc: url = urljoin(resp.url, requote_uri(url)) else: url = requote_uri(url) prepared_request.url = to_native_string(url) self.rebuild_method(prepared_request, resp) # https://github.com/psf/requests/issues/1084 if resp.status_code not in (codes.temporary_redirect, codes.permanent_redirect): # https://github.com/psf/requests/issues/3490 purged_headers = ('Content-Length', 'Content-Type', 'Transfer-Encoding') for header in purged_headers: prepared_request.headers.pop(header, None) prepared_request.body = None headers = prepared_request.headers headers.pop('Cookie', None) # Extract any cookies sent on the response to the cookiejar # in the new request. Because we've mutated our copied prepared # request, use the old one that we haven't yet touched. extract_cookies_to_jar(prepared_request._cookies, req, resp.raw) merge_cookies(prepared_request._cookies, self.cookies) prepared_request.prepare_cookies(prepared_request._cookies) # Rebuild auth and proxy information. proxies = self.rebuild_proxies(prepared_request, proxies) self.rebuild_auth(prepared_request, resp) # A failed tell() sets `_body_position` to `object()`. This non-None # value ensures `rewindable` will be True, allowing us to raise an # UnrewindableBodyError, instead of hanging the connection. rewindable = ( prepared_request._body_position is not None and ('Content-Length' in headers or 'Transfer-Encoding' in headers) ) # Attempt to rewind consumed file-like object. if rewindable: rewind_body(prepared_request) # Override the original request. req = prepared_request if yield_requests: yield req else: resp = self.send( req, stream=stream, timeout=timeout, verify=verify, cert=cert, proxies=proxies, allow_redirects=False, **adapter_kwargs ) extract_cookies_to_jar(self.cookies, prepared_request, resp.raw) # extract redirect url, if any, for the next loop url = self.get_redirect_target(resp) yield resp def rebuild_auth(self, prepared_request, response): """When being redirected we may want to strip authentication from the request to avoid leaking credentials. This method intelligently removes and reapplies authentication where possible to avoid credential loss. """ headers = prepared_request.headers url = prepared_request.url if 'Authorization' in headers and self.should_strip_auth(response.request.url, url): # If we get redirected to a new host, we should strip out any # authentication headers. del headers['Authorization'] # .netrc might have more auth for us on our new host. new_auth = get_netrc_auth(url) if self.trust_env else None if new_auth is not None: prepared_request.prepare_auth(new_auth) def rebuild_proxies(self, prepared_request, proxies): """This method re-evaluates the proxy configuration by considering the environment variables. If we are redirected to a URL covered by NO_PROXY, we strip the proxy configuration. Otherwise, we set missing proxy keys for this URL (in case they were stripped by a previous redirect). This method also replaces the Proxy-Authorization header where necessary. :rtype: dict """ proxies = proxies if proxies is not None else {} headers = prepared_request.headers url = prepared_request.url scheme = urlparse(url).scheme new_proxies = proxies.copy() no_proxy = proxies.get('no_proxy') bypass_proxy = should_bypass_proxies(url, no_proxy=no_proxy) if self.trust_env and not bypass_proxy: environ_proxies = get_environ_proxies(url, no_proxy=no_proxy) proxy = environ_proxies.get(scheme, environ_proxies.get('all')) if proxy: new_proxies.setdefault(scheme, proxy) if 'Proxy-Authorization' in headers: del headers['Proxy-Authorization'] try: username, password = get_auth_from_url(new_proxies[scheme]) except KeyError: username, password = None, None if username and password: headers['Proxy-Authorization'] = _basic_auth_str(username, password) return new_proxies def rebuild_method(self, prepared_request, response): """When being redirected we may want to change the method of the request based on certain specs or browser behavior. """ method = prepared_request.method # https://tools.ietf.org/html/rfc7231#section-6.4.4 if response.status_code == codes.see_other and method != 'HEAD': method = 'GET' # Do what the browsers do, despite standards... # First, turn 302s into GETs. if response.status_code == codes.found and method != 'HEAD': method = 'GET' # Second, if a POST is responded to with a 301, turn it into a GET. # This bizarre behaviour is explained in Issue 1704. if response.status_code == codes.moved and method == 'POST': method = 'GET' prepared_request.method = method class Session(SessionRedirectMixin): """A Requests session. Provides cookie persistence, connection-pooling, and configuration. Basic Usage:: >>> import requests >>> s = requests.Session() >>> s.get('https://httpbin.org/get') Or as a context manager:: >>> with requests.Session() as s: ... s.get('https://httpbin.org/get') """ __attrs__ = [ 'headers', 'cookies', 'auth', 'proxies', 'hooks', 'params', 'verify', 'cert', 'adapters', 'stream', 'trust_env', 'max_redirects', ] def __init__(self): #: A case-insensitive dictionary of headers to be sent on each #: :class:`Request ` sent from this #: :class:`Session `. self.headers = default_headers() #: Default Authentication tuple or object to attach to #: :class:`Request `. self.auth = None #: Dictionary mapping protocol or protocol and host to the URL of the proxy #: (e.g. {'http': 'foo.bar:3128', 'http://host.name': 'foo.bar:4012'}) to #: be used on each :class:`Request `. self.proxies = {} #: Event-handling hooks. self.hooks = default_hooks() #: Dictionary of querystring data to attach to each #: :class:`Request `. The dictionary values may be lists for #: representing multivalued query parameters. self.params = {} #: Stream response content default. self.stream = False #: SSL Verification default. #: Defaults to `True`, requiring requests to verify the TLS certificate at the #: remote end. #: If verify is set to `False`, requests will accept any TLS certificate #: presented by the server, and will ignore hostname mismatches and/or #: expired certificates, which will make your application vulnerable to #: man-in-the-middle (MitM) attacks. #: Only set this to `False` for testing. self.verify = True #: SSL client certificate default, if String, path to ssl client #: cert file (.pem). If Tuple, ('cert', 'key') pair. self.cert = None #: Maximum number of redirects allowed. If the request exceeds this #: limit, a :class:`TooManyRedirects` exception is raised. #: This defaults to requests.models.DEFAULT_REDIRECT_LIMIT, which is #: 30. self.max_redirects = DEFAULT_REDIRECT_LIMIT #: Trust environment settings for proxy configuration, default #: authentication and similar. self.trust_env = True #: A CookieJar containing all currently outstanding cookies set on this #: session. By default it is a #: :class:`RequestsCookieJar `, but #: may be any other ``cookielib.CookieJar`` compatible object. self.cookies = cookiejar_from_dict({}) # Default connection adapters. self.adapters = OrderedDict() self.mount('https://', HTTPAdapter()) self.mount('http://', HTTPAdapter()) def __enter__(self): return self def __exit__(self, *args): self.close() def prepare_request(self, request): """Constructs a :class:`PreparedRequest ` for transmission and returns it. The :class:`PreparedRequest` has settings merged from the :class:`Request ` instance and those of the :class:`Session`. :param request: :class:`Request` instance to prepare with this session's settings. :rtype: requests.PreparedRequest """ cookies = request.cookies or {} # Bootstrap CookieJar. if not isinstance(cookies, cookielib.CookieJar): cookies = cookiejar_from_dict(cookies) # Merge with session cookies merged_cookies = merge_cookies( merge_cookies(RequestsCookieJar(), self.cookies), cookies) # Set environment's basic authentication if not explicitly set. auth = request.auth if self.trust_env and not auth and not self.auth: auth = get_netrc_auth(request.url) p = PreparedRequest() p.prepare( method=request.method.upper(), url=request.url, files=request.files, data=request.data, json=request.json, headers=merge_setting(request.headers, self.headers, dict_class=CaseInsensitiveDict), params=merge_setting(request.params, self.params), auth=merge_setting(auth, self.auth), cookies=merged_cookies, hooks=merge_hooks(request.hooks, self.hooks), ) return p def request(self, method, url, params=None, data=None, headers=None, cookies=None, files=None, auth=None, timeout=None, allow_redirects=True, proxies=None, hooks=None, stream=None, verify=None, cert=None, json=None): """Constructs a :class:`Request `, prepares it and sends it. Returns :class:`Response ` object. :param method: method for the new :class:`Request` object. :param url: URL for the new :class:`Request` object. :param params: (optional) Dictionary or bytes to be sent in the query string for the :class:`Request`. :param data: (optional) Dictionary, list of tuples, bytes, or file-like object to send in the body of the :class:`Request`. :param json: (optional) json to send in the body of the :class:`Request`. :param headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`. :param cookies: (optional) Dict or CookieJar object to send with the :class:`Request`. :param files: (optional) Dictionary of ``'filename': file-like-objects`` for multipart encoding upload. :param auth: (optional) Auth tuple or callable to enable Basic/Digest/Custom HTTP Auth. :param timeout: (optional) How long to wait for the server to send data before giving up, as a float, or a :ref:`(connect timeout, read timeout) ` tuple. :type timeout: float or tuple :param allow_redirects: (optional) Set to True by default. :type allow_redirects: bool :param proxies: (optional) Dictionary mapping protocol or protocol and hostname to the URL of the proxy. :param stream: (optional) whether to immediately download the response content. Defaults to ``False``. :param verify: (optional) Either a boolean, in which case it controls whether we verify the server's TLS certificate, or a string, in which case it must be a path to a CA bundle to use. Defaults to ``True``. When set to ``False``, requests will accept any TLS certificate presented by the server, and will ignore hostname mismatches and/or expired certificates, which will make your application vulnerable to man-in-the-middle (MitM) attacks. Setting verify to ``False`` may be useful during local development or testing. :param cert: (optional) if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair. :rtype: requests.Response """ # Create the Request. req = Request( method=method.upper(), url=url, headers=headers, files=files, data=data or {}, json=json, params=params or {}, auth=auth, cookies=cookies, hooks=hooks, ) prep = self.prepare_request(req) proxies = proxies or {} settings = self.merge_environment_settings( prep.url, proxies, stream, verify, cert ) # Send the request. send_kwargs = { 'timeout': timeout, 'allow_redirects': allow_redirects, } send_kwargs.update(settings) resp = self.send(prep, **send_kwargs) return resp def get(self, url, **kwargs): r"""Sends a GET request. Returns :class:`Response` object. :param url: URL for the new :class:`Request` object. :param \*\*kwargs: Optional arguments that ``request`` takes. :rtype: requests.Response """ kwargs.setdefault('allow_redirects', True) return self.request('GET', url, **kwargs) def options(self, url, **kwargs): r"""Sends a OPTIONS request. Returns :class:`Response` object. :param url: URL for the new :class:`Request` object. :param \*\*kwargs: Optional arguments that ``request`` takes. :rtype: requests.Response """ kwargs.setdefault('allow_redirects', True) return self.request('OPTIONS', url, **kwargs) def head(self, url, **kwargs): r"""Sends a HEAD request. Returns :class:`Response` object. :param url: URL for the new :class:`Request` object. :param \*\*kwargs: Optional arguments that ``request`` takes. :rtype: requests.Response """ kwargs.setdefault('allow_redirects', False) return self.request('HEAD', url, **kwargs) def post(self, url, data=None, json=None, **kwargs): r"""Sends a POST request. Returns :class:`Response` object. :param url: URL for the new :class:`Request` object. :param data: (optional) Dictionary, list of tuples, bytes, or file-like object to send in the body of the :class:`Request`. :param json: (optional) json to send in the body of the :class:`Request`. :param \*\*kwargs: Optional arguments that ``request`` takes. :rtype: requests.Response """ return self.request('POST', url, data=data, json=json, **kwargs) def put(self, url, data=None, **kwargs): r"""Sends a PUT request. Returns :class:`Response` object. :param url: URL for the new :class:`Request` object. :param data: (optional) Dictionary, list of tuples, bytes, or file-like object to send in the body of the :class:`Request`. :param \*\*kwargs: Optional arguments that ``request`` takes. :rtype: requests.Response """ return self.request('PUT', url, data=data, **kwargs) def patch(self, url, data=None, **kwargs): r"""Sends a PATCH request. Returns :class:`Response` object. :param url: URL for the new :class:`Request` object. :param data: (optional) Dictionary, list of tuples, bytes, or file-like object to send in the body of the :class:`Request`. :param \*\*kwargs: Optional arguments that ``request`` takes. :rtype: requests.Response """ return self.request('PATCH', url, data=data, **kwargs) def delete(self, url, **kwargs): r"""Sends a DELETE request. Returns :class:`Response` object. :param url: URL for the new :class:`Request` object. :param \*\*kwargs: Optional arguments that ``request`` takes. :rtype: requests.Response """ return self.request('DELETE', url, **kwargs) def send(self, request, **kwargs): """Send a given PreparedRequest. :rtype: requests.Response """ # Set defaults that the hooks can utilize to ensure they always have # the correct parameters to reproduce the previous request. kwargs.setdefault('stream', self.stream) kwargs.setdefault('verify', self.verify) kwargs.setdefault('cert', self.cert) kwargs.setdefault('proxies', self.rebuild_proxies(request, self.proxies)) # It's possible that users might accidentally send a Request object. # Guard against that specific failure case. if isinstance(request, Request): raise ValueError('You can only send PreparedRequests.') # Set up variables needed for resolve_redirects and dispatching of hooks allow_redirects = kwargs.pop('allow_redirects', True) stream = kwargs.get('stream') hooks = request.hooks # Get the appropriate adapter to use adapter = self.get_adapter(url=request.url) # Start time (approximately) of the request start = preferred_clock() # Send the request r = adapter.send(request, **kwargs) # Total elapsed time of the request (approximately) elapsed = preferred_clock() - start r.elapsed = timedelta(seconds=elapsed) # Response manipulation hooks r = dispatch_hook('response', hooks, r, **kwargs) # Persist cookies if r.history: # If the hooks create history then we want those cookies too for resp in r.history: extract_cookies_to_jar(self.cookies, resp.request, resp.raw) extract_cookies_to_jar(self.cookies, request, r.raw) # Resolve redirects if allowed. if allow_redirects: # Redirect resolving generator. gen = self.resolve_redirects(r, request, **kwargs) history = [resp for resp in gen] else: history = [] # Shuffle things around if there's history. if history: # Insert the first (original) request at the start history.insert(0, r) # Get the last request made r = history.pop() r.history = history # If redirects aren't being followed, store the response on the Request for Response.next(). if not allow_redirects: try: r._next = next(self.resolve_redirects(r, request, yield_requests=True, **kwargs)) except StopIteration: pass if not stream: r.content return r def merge_environment_settings(self, url, proxies, stream, verify, cert): """ Check the environment and merge it with some settings. :rtype: dict """ # Gather clues from the surrounding environment. if self.trust_env: # Set environment's proxies. no_proxy = proxies.get('no_proxy') if proxies is not None else None env_proxies = get_environ_proxies(url, no_proxy=no_proxy) for (k, v) in env_proxies.items(): proxies.setdefault(k, v) # Look for requests environment configuration and be compatible # with cURL. if verify is True or verify is None: verify = (os.environ.get('REQUESTS_CA_BUNDLE') or os.environ.get('CURL_CA_BUNDLE')) # Merge all the kwargs. proxies = merge_setting(proxies, self.proxies) stream = merge_setting(stream, self.stream) verify = merge_setting(verify, self.verify) cert = merge_setting(cert, self.cert) return {'verify': verify, 'proxies': proxies, 'stream': stream, 'cert': cert} def get_adapter(self, url): """ Returns the appropriate connection adapter for the given URL. :rtype: requests.adapters.BaseAdapter """ for (prefix, adapter) in self.adapters.items(): if url.lower().startswith(prefix.lower()): return adapter # Nothing matches :-/ raise InvalidSchema("No connection adapters were found for {!r}".format(url)) def close(self): """Closes all adapters and as such the session""" for v in self.adapters.values(): v.close() def mount(self, prefix, adapter): """Registers a connection adapter to a prefix. Adapters are sorted in descending order by prefix length. """ self.adapters[prefix] = adapter keys_to_move = [k for k in self.adapters if len(k) < len(prefix)] for key in keys_to_move: self.adapters[key] = self.adapters.pop(key) def __getstate__(self): state = {attr: getattr(self, attr, None) for attr in self.__attrs__} return state def __setstate__(self, state): for attr, value in state.items(): setattr(self, attr, value) def session(): """ Returns a :class:`Session` for context-management. .. deprecated:: 1.0.0 This method has been deprecated since version 1.0.0 and is only kept for backwards compatibility. New code should use :class:`~requests.sessions.Session` to create a session. This may be removed at a future date. :rtype: Session """ return Session() ================================================ FILE: modules/requests/status_codes.py ================================================ # -*- coding: utf-8 -*- r""" The ``codes`` object defines a mapping from common names for HTTP statuses to their numerical codes, accessible either as attributes or as dictionary items. Example:: >>> import requests >>> requests.codes['temporary_redirect'] 307 >>> requests.codes.teapot 418 >>> requests.codes['\o/'] 200 Some codes have multiple names, and both upper- and lower-case versions of the names are allowed. For example, ``codes.ok``, ``codes.OK``, and ``codes.okay`` all correspond to the HTTP status code 200. """ from .structures import LookupDict _codes = { # Informational. 100: ('continue',), 101: ('switching_protocols',), 102: ('processing',), 103: ('checkpoint',), 122: ('uri_too_long', 'request_uri_too_long'), 200: ('ok', 'okay', 'all_ok', 'all_okay', 'all_good', '\\o/', '✓'), 201: ('created',), 202: ('accepted',), 203: ('non_authoritative_info', 'non_authoritative_information'), 204: ('no_content',), 205: ('reset_content', 'reset'), 206: ('partial_content', 'partial'), 207: ('multi_status', 'multiple_status', 'multi_stati', 'multiple_stati'), 208: ('already_reported',), 226: ('im_used',), # Redirection. 300: ('multiple_choices',), 301: ('moved_permanently', 'moved', '\\o-'), 302: ('found',), 303: ('see_other', 'other'), 304: ('not_modified',), 305: ('use_proxy',), 306: ('switch_proxy',), 307: ('temporary_redirect', 'temporary_moved', 'temporary'), 308: ('permanent_redirect', 'resume_incomplete', 'resume',), # These 2 to be removed in 3.0 # Client Error. 400: ('bad_request', 'bad'), 401: ('unauthorized',), 402: ('payment_required', 'payment'), 403: ('forbidden',), 404: ('not_found', '-o-'), 405: ('method_not_allowed', 'not_allowed'), 406: ('not_acceptable',), 407: ('proxy_authentication_required', 'proxy_auth', 'proxy_authentication'), 408: ('request_timeout', 'timeout'), 409: ('conflict',), 410: ('gone',), 411: ('length_required',), 412: ('precondition_failed', 'precondition'), 413: ('request_entity_too_large',), 414: ('request_uri_too_large',), 415: ('unsupported_media_type', 'unsupported_media', 'media_type'), 416: ('requested_range_not_satisfiable', 'requested_range', 'range_not_satisfiable'), 417: ('expectation_failed',), 418: ('im_a_teapot', 'teapot', 'i_am_a_teapot'), 421: ('misdirected_request',), 422: ('unprocessable_entity', 'unprocessable'), 423: ('locked',), 424: ('failed_dependency', 'dependency'), 425: ('unordered_collection', 'unordered'), 426: ('upgrade_required', 'upgrade'), 428: ('precondition_required', 'precondition'), 429: ('too_many_requests', 'too_many'), 431: ('header_fields_too_large', 'fields_too_large'), 444: ('no_response', 'none'), 449: ('retry_with', 'retry'), 450: ('blocked_by_windows_parental_controls', 'parental_controls'), 451: ('unavailable_for_legal_reasons', 'legal_reasons'), 499: ('client_closed_request',), # Server Error. 500: ('internal_server_error', 'server_error', '/o\\', '✗'), 501: ('not_implemented',), 502: ('bad_gateway',), 503: ('service_unavailable', 'unavailable'), 504: ('gateway_timeout',), 505: ('http_version_not_supported', 'http_version'), 506: ('variant_also_negotiates',), 507: ('insufficient_storage',), 509: ('bandwidth_limit_exceeded', 'bandwidth'), 510: ('not_extended',), 511: ('network_authentication_required', 'network_auth', 'network_authentication'), } codes = LookupDict(name='status_codes') def _init(): for code, titles in _codes.items(): for title in titles: setattr(codes, title, code) if not title.startswith(('\\', '/')): setattr(codes, title.upper(), code) def doc(code): names = ', '.join('``%s``' % n for n in _codes[code]) return '* %d: %s' % (code, names) global __doc__ __doc__ = (__doc__ + '\n' + '\n'.join(doc(code) for code in sorted(_codes)) if __doc__ is not None else None) _init() ================================================ FILE: modules/requests/structures.py ================================================ # -*- coding: utf-8 -*- """ requests.structures ~~~~~~~~~~~~~~~~~~~ Data structures that power Requests. """ from collections import OrderedDict from .compat import Mapping, MutableMapping class CaseInsensitiveDict(MutableMapping): """A case-insensitive ``dict``-like object. Implements all methods and operations of ``MutableMapping`` as well as dict's ``copy``. Also provides ``lower_items``. All keys are expected to be strings. The structure remembers the case of the last key to be set, and ``iter(instance)``, ``keys()``, ``items()``, ``iterkeys()``, and ``iteritems()`` will contain case-sensitive keys. However, querying and contains testing is case insensitive:: cid = CaseInsensitiveDict() cid['Accept'] = 'application/json' cid['aCCEPT'] == 'application/json' # True list(cid) == ['Accept'] # True For example, ``headers['content-encoding']`` will return the value of a ``'Content-Encoding'`` response header, regardless of how the header name was originally stored. If the constructor, ``.update``, or equality comparison operations are given keys that have equal ``.lower()``s, the behavior is undefined. """ def __init__(self, data=None, **kwargs): self._store = OrderedDict() if data is None: data = {} self.update(data, **kwargs) def __setitem__(self, key, value): # Use the lowercased key for lookups, but store the actual # key alongside the value. self._store[key.lower()] = (key, value) def __getitem__(self, key): return self._store[key.lower()][1] def __delitem__(self, key): del self._store[key.lower()] def __iter__(self): return (casedkey for casedkey, mappedvalue in self._store.values()) def __len__(self): return len(self._store) def lower_items(self): """Like iteritems(), but with all lowercase keys.""" return ( (lowerkey, keyval[1]) for (lowerkey, keyval) in self._store.items() ) def __eq__(self, other): if isinstance(other, Mapping): other = CaseInsensitiveDict(other) else: return NotImplemented # Compare insensitively return dict(self.lower_items()) == dict(other.lower_items()) # Copy is required def copy(self): return CaseInsensitiveDict(self._store.values()) def __repr__(self): return str(dict(self.items())) class LookupDict(dict): """Dictionary lookup object.""" def __init__(self, name=None): self.name = name super(LookupDict, self).__init__() def __repr__(self): return '' % (self.name) def __getitem__(self, key): # We allow fall-through here, so values default to None return self.__dict__.get(key, None) def get(self, key, default=None): return self.__dict__.get(key, default) ================================================ FILE: modules/requests/utils.py ================================================ # -*- coding: utf-8 -*- """ requests.utils ~~~~~~~~~~~~~~ This module provides utility functions that are used within Requests that are also useful for external consumption. """ import codecs import contextlib import io import os import re import socket import struct import sys import tempfile import warnings import zipfile from collections import OrderedDict from urllib3.util import make_headers from .__version__ import __version__ from . import certs # to_native_string is unused here, but imported here for backwards compatibility from ._internal_utils import to_native_string from .compat import parse_http_list as _parse_list_header from .compat import ( quote, urlparse, bytes, str, unquote, getproxies, proxy_bypass, urlunparse, basestring, integer_types, is_py3, proxy_bypass_environment, getproxies_environment, Mapping) from .cookies import cookiejar_from_dict from .structures import CaseInsensitiveDict from .exceptions import ( InvalidURL, InvalidHeader, FileModeWarning, UnrewindableBodyError) NETRC_FILES = ('.netrc', '_netrc') DEFAULT_CA_BUNDLE_PATH = certs.where() DEFAULT_PORTS = {'http': 80, 'https': 443} # Ensure that ', ' is used to preserve previous delimiter behavior. DEFAULT_ACCEPT_ENCODING = ", ".join( re.split(r",\s*", make_headers(accept_encoding=True)["accept-encoding"]) ) if sys.platform == 'win32': # provide a proxy_bypass version on Windows without DNS lookups def proxy_bypass_registry(host): try: if is_py3: import winreg else: import _winreg as winreg except ImportError: return False try: internetSettings = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r'Software\Microsoft\Windows\CurrentVersion\Internet Settings') # ProxyEnable could be REG_SZ or REG_DWORD, normalizing it proxyEnable = int(winreg.QueryValueEx(internetSettings, 'ProxyEnable')[0]) # ProxyOverride is almost always a string proxyOverride = winreg.QueryValueEx(internetSettings, 'ProxyOverride')[0] except OSError: return False if not proxyEnable or not proxyOverride: return False # make a check value list from the registry entry: replace the # '' string by the localhost entry and the corresponding # canonical entry. proxyOverride = proxyOverride.split(';') # now check if we match one of the registry values. for test in proxyOverride: if test == '': if '.' not in host: return True test = test.replace(".", r"\.") # mask dots test = test.replace("*", r".*") # change glob sequence test = test.replace("?", r".") # change glob char if re.match(test, host, re.I): return True return False def proxy_bypass(host): # noqa """Return True, if the host should be bypassed. Checks proxy settings gathered from the environment, if specified, or the registry. """ if getproxies_environment(): return proxy_bypass_environment(host) else: return proxy_bypass_registry(host) def dict_to_sequence(d): """Returns an internal sequence dictionary update.""" if hasattr(d, 'items'): d = d.items() return d def super_len(o): total_length = None current_position = 0 if hasattr(o, '__len__'): total_length = len(o) elif hasattr(o, 'len'): total_length = o.len elif hasattr(o, 'fileno'): try: fileno = o.fileno() except io.UnsupportedOperation: pass else: total_length = os.fstat(fileno).st_size # Having used fstat to determine the file length, we need to # confirm that this file was opened up in binary mode. if 'b' not in o.mode: warnings.warn(( "Requests has determined the content-length for this " "request using the binary size of the file: however, the " "file has been opened in text mode (i.e. without the 'b' " "flag in the mode). This may lead to an incorrect " "content-length. In Requests 3.0, support will be removed " "for files in text mode."), FileModeWarning ) if hasattr(o, 'tell'): try: current_position = o.tell() except (OSError, IOError): # This can happen in some weird situations, such as when the file # is actually a special file descriptor like stdin. In this # instance, we don't know what the length is, so set it to zero and # let requests chunk it instead. if total_length is not None: current_position = total_length else: if hasattr(o, 'seek') and total_length is None: # StringIO and BytesIO have seek but no useable fileno try: # seek to end of file o.seek(0, 2) total_length = o.tell() # seek back to current position to support # partially read file-like objects o.seek(current_position or 0) except (OSError, IOError): total_length = 0 if total_length is None: total_length = 0 return max(0, total_length - current_position) def get_netrc_auth(url, raise_errors=False): """Returns the Requests tuple auth for a given url from netrc.""" netrc_file = os.environ.get('NETRC') if netrc_file is not None: netrc_locations = (netrc_file,) else: netrc_locations = ('~/{}'.format(f) for f in NETRC_FILES) try: from netrc import netrc, NetrcParseError netrc_path = None for f in netrc_locations: try: loc = os.path.expanduser(f) except KeyError: # os.path.expanduser can fail when $HOME is undefined and # getpwuid fails. See https://bugs.python.org/issue20164 & # https://github.com/psf/requests/issues/1846 return if os.path.exists(loc): netrc_path = loc break # Abort early if there isn't one. if netrc_path is None: return ri = urlparse(url) # Strip port numbers from netloc. This weird `if...encode`` dance is # used for Python 3.2, which doesn't support unicode literals. splitstr = b':' if isinstance(url, str): splitstr = splitstr.decode('ascii') host = ri.netloc.split(splitstr)[0] try: _netrc = netrc(netrc_path).authenticators(host) if _netrc: # Return with login / password login_i = (0 if _netrc[0] else 1) return (_netrc[login_i], _netrc[2]) except (NetrcParseError, IOError): # If there was a parsing error or a permissions issue reading the file, # we'll just skip netrc auth unless explicitly asked to raise errors. if raise_errors: raise # App Engine hackiness. except (ImportError, AttributeError): pass def guess_filename(obj): """Tries to guess the filename of the given object.""" name = getattr(obj, 'name', None) if (name and isinstance(name, basestring) and name[0] != '<' and name[-1] != '>'): return os.path.basename(name) def extract_zipped_paths(path): """Replace nonexistent paths that look like they refer to a member of a zip archive with the location of an extracted copy of the target, or else just return the provided path unchanged. """ if os.path.exists(path): # this is already a valid path, no need to do anything further return path # find the first valid part of the provided path and treat that as a zip archive # assume the rest of the path is the name of a member in the archive archive, member = os.path.split(path) while archive and not os.path.exists(archive): archive, prefix = os.path.split(archive) member = '/'.join([prefix, member]) if not zipfile.is_zipfile(archive): return path zip_file = zipfile.ZipFile(archive) if member not in zip_file.namelist(): return path # we have a valid zip archive and a valid member of that archive tmp = tempfile.gettempdir() extracted_path = os.path.join(tmp, member.split('/')[-1]) if not os.path.exists(extracted_path): # use read + write to avoid the creating nested folders, we only want the file, avoids mkdir racing condition with atomic_open(extracted_path) as file_handler: file_handler.write(zip_file.read(member)) return extracted_path @contextlib.contextmanager def atomic_open(filename): """Write a file to the disk in an atomic fashion""" replacer = os.rename if sys.version_info[0] == 2 else os.replace tmp_descriptor, tmp_name = tempfile.mkstemp(dir=os.path.dirname(filename)) try: with os.fdopen(tmp_descriptor, 'wb') as tmp_handler: yield tmp_handler replacer(tmp_name, filename) except BaseException: os.remove(tmp_name) raise def from_key_val_list(value): """Take an object and test to see if it can be represented as a dictionary. Unless it can not be represented as such, return an OrderedDict, e.g., :: >>> from_key_val_list([('key', 'val')]) OrderedDict([('key', 'val')]) >>> from_key_val_list('string') Traceback (most recent call last): ... ValueError: cannot encode objects that are not 2-tuples >>> from_key_val_list({'key': 'val'}) OrderedDict([('key', 'val')]) :rtype: OrderedDict """ if value is None: return None if isinstance(value, (str, bytes, bool, int)): raise ValueError('cannot encode objects that are not 2-tuples') return OrderedDict(value) def to_key_val_list(value): """Take an object and test to see if it can be represented as a dictionary. If it can be, return a list of tuples, e.g., :: >>> to_key_val_list([('key', 'val')]) [('key', 'val')] >>> to_key_val_list({'key': 'val'}) [('key', 'val')] >>> to_key_val_list('string') Traceback (most recent call last): ... ValueError: cannot encode objects that are not 2-tuples :rtype: list """ if value is None: return None if isinstance(value, (str, bytes, bool, int)): raise ValueError('cannot encode objects that are not 2-tuples') if isinstance(value, Mapping): value = value.items() return list(value) # From mitsuhiko/werkzeug (used with permission). def parse_list_header(value): """Parse lists as described by RFC 2068 Section 2. In particular, parse comma-separated lists where the elements of the list may include quoted-strings. A quoted-string could contain a comma. A non-quoted string could have quotes in the middle. Quotes are removed automatically after parsing. It basically works like :func:`parse_set_header` just that items may appear multiple times and case sensitivity is preserved. The return value is a standard :class:`list`: >>> parse_list_header('token, "quoted value"') ['token', 'quoted value'] To create a header from the :class:`list` again, use the :func:`dump_header` function. :param value: a string with a list header. :return: :class:`list` :rtype: list """ result = [] for item in _parse_list_header(value): if item[:1] == item[-1:] == '"': item = unquote_header_value(item[1:-1]) result.append(item) return result # From mitsuhiko/werkzeug (used with permission). def parse_dict_header(value): """Parse lists of key, value pairs as described by RFC 2068 Section 2 and convert them into a python dict: >>> d = parse_dict_header('foo="is a fish", bar="as well"') >>> type(d) is dict True >>> sorted(d.items()) [('bar', 'as well'), ('foo', 'is a fish')] If there is no value for a key it will be `None`: >>> parse_dict_header('key_without_value') {'key_without_value': None} To create a header from the :class:`dict` again, use the :func:`dump_header` function. :param value: a string with a dict header. :return: :class:`dict` :rtype: dict """ result = {} for item in _parse_list_header(value): if '=' not in item: result[item] = None continue name, value = item.split('=', 1) if value[:1] == value[-1:] == '"': value = unquote_header_value(value[1:-1]) result[name] = value return result # From mitsuhiko/werkzeug (used with permission). def unquote_header_value(value, is_filename=False): r"""Unquotes a header value. (Reversal of :func:`quote_header_value`). This does not use the real unquoting but what browsers are actually using for quoting. :param value: the header value to unquote. :rtype: str """ if value and value[0] == value[-1] == '"': # this is not the real unquoting, but fixing this so that the # RFC is met will result in bugs with internet explorer and # probably some other browsers as well. IE for example is # uploading files with "C:\foo\bar.txt" as filename value = value[1:-1] # if this is a filename and the starting characters look like # a UNC path, then just return the value without quotes. Using the # replace sequence below on a UNC path has the effect of turning # the leading double slash into a single slash and then # _fix_ie_filename() doesn't work correctly. See #458. if not is_filename or value[:2] != '\\\\': return value.replace('\\\\', '\\').replace('\\"', '"') return value def dict_from_cookiejar(cj): """Returns a key/value dictionary from a CookieJar. :param cj: CookieJar object to extract cookies from. :rtype: dict """ cookie_dict = {} for cookie in cj: cookie_dict[cookie.name] = cookie.value return cookie_dict def add_dict_to_cookiejar(cj, cookie_dict): """Returns a CookieJar from a key/value dictionary. :param cj: CookieJar to insert cookies into. :param cookie_dict: Dict of key/values to insert into CookieJar. :rtype: CookieJar """ return cookiejar_from_dict(cookie_dict, cj) def get_encodings_from_content(content): """Returns encodings from given content string. :param content: bytestring to extract encodings from. """ warnings.warn(( 'In requests 3.0, get_encodings_from_content will be removed. For ' 'more information, please see the discussion on issue #2266. (This' ' warning should only appear once.)'), DeprecationWarning) charset_re = re.compile(r']', flags=re.I) pragma_re = re.compile(r']', flags=re.I) xml_re = re.compile(r'^<\?xml.*?encoding=["\']*(.+?)["\'>]') return (charset_re.findall(content) + pragma_re.findall(content) + xml_re.findall(content)) def _parse_content_type_header(header): """Returns content type and parameters from given header :param header: string :return: tuple containing content type and dictionary of parameters """ tokens = header.split(';') content_type, params = tokens[0].strip(), tokens[1:] params_dict = {} items_to_strip = "\"' " for param in params: param = param.strip() if param: key, value = param, True index_of_equals = param.find("=") if index_of_equals != -1: key = param[:index_of_equals].strip(items_to_strip) value = param[index_of_equals + 1:].strip(items_to_strip) params_dict[key.lower()] = value return content_type, params_dict def get_encoding_from_headers(headers): """Returns encodings from given HTTP Header Dict. :param headers: dictionary to extract encoding from. :rtype: str """ content_type = headers.get('content-type') if not content_type: return None content_type, params = _parse_content_type_header(content_type) if 'charset' in params: return params['charset'].strip("'\"") if 'text' in content_type: return 'ISO-8859-1' if 'application/json' in content_type: # Assume UTF-8 based on RFC 4627: https://www.ietf.org/rfc/rfc4627.txt since the charset was unset return 'utf-8' def stream_decode_response_unicode(iterator, r): """Stream decodes a iterator.""" if r.encoding is None: for item in iterator: yield item return decoder = codecs.getincrementaldecoder(r.encoding)(errors='replace') for chunk in iterator: rv = decoder.decode(chunk) if rv: yield rv rv = decoder.decode(b'', final=True) if rv: yield rv def iter_slices(string, slice_length): """Iterate over slices of a string.""" pos = 0 if slice_length is None or slice_length <= 0: slice_length = len(string) while pos < len(string): yield string[pos:pos + slice_length] pos += slice_length def get_unicode_from_response(r): """Returns the requested content back in unicode. :param r: Response object to get unicode content from. Tried: 1. charset from content-type 2. fall back and replace all unicode characters :rtype: str """ warnings.warn(( 'In requests 3.0, get_unicode_from_response will be removed. For ' 'more information, please see the discussion on issue #2266. (This' ' warning should only appear once.)'), DeprecationWarning) tried_encodings = [] # Try charset from content-type encoding = get_encoding_from_headers(r.headers) if encoding: try: return str(r.content, encoding) except UnicodeError: tried_encodings.append(encoding) # Fall back: try: return str(r.content, encoding, errors='replace') except TypeError: return r.content # The unreserved URI characters (RFC 3986) UNRESERVED_SET = frozenset( "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + "0123456789-._~") def unquote_unreserved(uri): """Un-escape any percent-escape sequences in a URI that are unreserved characters. This leaves all reserved, illegal and non-ASCII bytes encoded. :rtype: str """ parts = uri.split('%') for i in range(1, len(parts)): h = parts[i][0:2] if len(h) == 2 and h.isalnum(): try: c = chr(int(h, 16)) except ValueError: raise InvalidURL("Invalid percent-escape sequence: '%s'" % h) if c in UNRESERVED_SET: parts[i] = c + parts[i][2:] else: parts[i] = '%' + parts[i] else: parts[i] = '%' + parts[i] return ''.join(parts) def requote_uri(uri): """Re-quote the given URI. This function passes the given URI through an unquote/quote cycle to ensure that it is fully and consistently quoted. :rtype: str """ safe_with_percent = "!#$%&'()*+,/:;=?@[]~" safe_without_percent = "!#$&'()*+,/:;=?@[]~" try: # Unquote only the unreserved characters # Then quote only illegal characters (do not quote reserved, # unreserved, or '%') return quote(unquote_unreserved(uri), safe=safe_with_percent) except InvalidURL: # We couldn't unquote the given URI, so let's try quoting it, but # there may be unquoted '%'s in the URI. We need to make sure they're # properly quoted so they do not cause issues elsewhere. return quote(uri, safe=safe_without_percent) def address_in_network(ip, net): """This function allows you to check if an IP belongs to a network subnet Example: returns True if ip = 192.168.1.1 and net = 192.168.1.0/24 returns False if ip = 192.168.1.1 and net = 192.168.100.0/24 :rtype: bool """ ipaddr = struct.unpack('=L', socket.inet_aton(ip))[0] netaddr, bits = net.split('/') netmask = struct.unpack('=L', socket.inet_aton(dotted_netmask(int(bits))))[0] network = struct.unpack('=L', socket.inet_aton(netaddr))[0] & netmask return (ipaddr & netmask) == (network & netmask) def dotted_netmask(mask): """Converts mask from /xx format to xxx.xxx.xxx.xxx Example: if mask is 24 function returns 255.255.255.0 :rtype: str """ bits = 0xffffffff ^ (1 << 32 - mask) - 1 return socket.inet_ntoa(struct.pack('>I', bits)) def is_ipv4_address(string_ip): """ :rtype: bool """ try: socket.inet_aton(string_ip) except socket.error: return False return True def is_valid_cidr(string_network): """ Very simple check of the cidr format in no_proxy variable. :rtype: bool """ if string_network.count('/') == 1: try: mask = int(string_network.split('/')[1]) except ValueError: return False if mask < 1 or mask > 32: return False try: socket.inet_aton(string_network.split('/')[0]) except socket.error: return False else: return False return True @contextlib.contextmanager def set_environ(env_name, value): """Set the environment variable 'env_name' to 'value' Save previous value, yield, and then restore the previous value stored in the environment variable 'env_name'. If 'value' is None, do nothing""" value_changed = value is not None if value_changed: old_value = os.environ.get(env_name) os.environ[env_name] = value try: yield finally: if value_changed: if old_value is None: del os.environ[env_name] else: os.environ[env_name] = old_value def should_bypass_proxies(url, no_proxy): """ Returns whether we should bypass proxies or not. :rtype: bool """ # Prioritize lowercase environment variables over uppercase # to keep a consistent behaviour with other http projects (curl, wget). get_proxy = lambda k: os.environ.get(k) or os.environ.get(k.upper()) # First check whether no_proxy is defined. If it is, check that the URL # we're getting isn't in the no_proxy list. no_proxy_arg = no_proxy if no_proxy is None: no_proxy = get_proxy('no_proxy') parsed = urlparse(url) if parsed.hostname is None: # URLs don't always have hostnames, e.g. file:/// urls. return True if no_proxy: # We need to check whether we match here. We need to see if we match # the end of the hostname, both with and without the port. no_proxy = ( host for host in no_proxy.replace(' ', '').split(',') if host ) if is_ipv4_address(parsed.hostname): for proxy_ip in no_proxy: if is_valid_cidr(proxy_ip): if address_in_network(parsed.hostname, proxy_ip): return True elif parsed.hostname == proxy_ip: # If no_proxy ip was defined in plain IP notation instead of cidr notation & # matches the IP of the index return True else: host_with_port = parsed.hostname if parsed.port: host_with_port += ':{}'.format(parsed.port) for host in no_proxy: if parsed.hostname.endswith(host) or host_with_port.endswith(host): # The URL does match something in no_proxy, so we don't want # to apply the proxies on this URL. return True with set_environ('no_proxy', no_proxy_arg): # parsed.hostname can be `None` in cases such as a file URI. try: bypass = proxy_bypass(parsed.hostname) except (TypeError, socket.gaierror): bypass = False if bypass: return True return False def get_environ_proxies(url, no_proxy=None): """ Return a dict of environment proxies. :rtype: dict """ if should_bypass_proxies(url, no_proxy=no_proxy): return {} else: return getproxies() def select_proxy(url, proxies): """Select a proxy for the url, if applicable. :param url: The url being for the request :param proxies: A dictionary of schemes or schemes and hosts to proxy URLs """ proxies = proxies or {} urlparts = urlparse(url) if urlparts.hostname is None: return proxies.get(urlparts.scheme, proxies.get('all')) proxy_keys = [ urlparts.scheme + '://' + urlparts.hostname, urlparts.scheme, 'all://' + urlparts.hostname, 'all', ] proxy = None for proxy_key in proxy_keys: if proxy_key in proxies: proxy = proxies[proxy_key] break return proxy def default_user_agent(name="python-requests"): """ Return a string representing the default user agent. :rtype: str """ return '%s/%s' % (name, __version__) def default_headers(): """ :rtype: requests.structures.CaseInsensitiveDict """ return CaseInsensitiveDict({ 'User-Agent': default_user_agent(), 'Accept-Encoding': DEFAULT_ACCEPT_ENCODING, 'Accept': '*/*', 'Connection': 'keep-alive', }) def parse_header_links(value): """Return a list of parsed link headers proxies. i.e. Link: ; rel=front; type="image/jpeg",; rel=back;type="image/jpeg" :rtype: list """ links = [] replace_chars = ' \'"' value = value.strip(replace_chars) if not value: return links for val in re.split(', *<', value): try: url, params = val.split(';', 1) except ValueError: url, params = val, '' link = {'url': url.strip('<> \'"')} for param in params.split(';'): try: key, value = param.split('=') except ValueError: break link[key.strip(replace_chars)] = value.strip(replace_chars) links.append(link) return links # Null bytes; no need to recreate these on each call to guess_json_utf _null = '\x00'.encode('ascii') # encoding to ASCII for Python 3 _null2 = _null * 2 _null3 = _null * 3 def guess_json_utf(data): """ :rtype: str """ # JSON always starts with two ASCII characters, so detection is as # easy as counting the nulls and from their location and count # determine the encoding. Also detect a BOM, if present. sample = data[:4] if sample in (codecs.BOM_UTF32_LE, codecs.BOM_UTF32_BE): return 'utf-32' # BOM included if sample[:3] == codecs.BOM_UTF8: return 'utf-8-sig' # BOM included, MS style (discouraged) if sample[:2] in (codecs.BOM_UTF16_LE, codecs.BOM_UTF16_BE): return 'utf-16' # BOM included nullcount = sample.count(_null) if nullcount == 0: return 'utf-8' if nullcount == 2: if sample[::2] == _null2: # 1st and 3rd are null return 'utf-16-be' if sample[1::2] == _null2: # 2nd and 4th are null return 'utf-16-le' # Did not detect 2 valid UTF-16 ascii-range characters if nullcount == 3: if sample[:3] == _null3: return 'utf-32-be' if sample[1:] == _null3: return 'utf-32-le' # Did not detect a valid UTF-32 ascii-range character return None def prepend_scheme_if_needed(url, new_scheme): """Given a URL that may or may not have a scheme, prepend the given scheme. Does not replace a present scheme with the one provided as an argument. :rtype: str """ scheme, netloc, path, params, query, fragment = urlparse(url, new_scheme) # urlparse is a finicky beast, and sometimes decides that there isn't a # netloc present. Assume that it's being over-cautious, and switch netloc # and path if urlparse decided there was no netloc. if not netloc: netloc, path = path, netloc return urlunparse((scheme, netloc, path, params, query, fragment)) def get_auth_from_url(url): """Given a url with authentication components, extract them into a tuple of username,password. :rtype: (str,str) """ parsed = urlparse(url) try: auth = (unquote(parsed.username), unquote(parsed.password)) except (AttributeError, TypeError): auth = ('', '') return auth # Moved outside of function to avoid recompile every call _CLEAN_HEADER_REGEX_BYTE = re.compile(b'^\\S[^\\r\\n]*$|^$') _CLEAN_HEADER_REGEX_STR = re.compile(r'^\S[^\r\n]*$|^$') def check_header_validity(header): """Verifies that header value is a string which doesn't contain leading whitespace or return characters. This prevents unintended header injection. :param header: tuple, in the format (name, value). """ name, value = header if isinstance(value, bytes): pat = _CLEAN_HEADER_REGEX_BYTE else: pat = _CLEAN_HEADER_REGEX_STR try: if not pat.match(value): raise InvalidHeader("Invalid return character or leading space in header: %s" % name) except TypeError: raise InvalidHeader("Value for header {%s: %s} must be of type str or " "bytes, not %s" % (name, value, type(value))) def urldefragauth(url): """ Given a url remove the fragment and the authentication part. :rtype: str """ scheme, netloc, path, params, query, fragment = urlparse(url) # see func:`prepend_scheme_if_needed` if not netloc: netloc, path = path, netloc netloc = netloc.rsplit('@', 1)[-1] return urlunparse((scheme, netloc, path, params, query, '')) def rewind_body(prepared_request): """Move file pointer back to its recorded starting position so it can be read again on redirect. """ body_seek = getattr(prepared_request.body, 'seek', None) if body_seek is not None and isinstance(prepared_request._body_position, integer_types): try: body_seek(prepared_request._body_position) except (IOError, OSError): raise UnrewindableBodyError("An error occurred when rewinding request " "body for redirect.") else: raise UnrewindableBodyError("Unable to rewind request body for redirect.") ================================================ FILE: modules/tkinter/colorchooser.py ================================================ # tk common color chooser dialogue # # this module provides an interface to the native color dialogue # available in Tk 4.2 and newer. # # written by Fredrik Lundh, May 1997 # # fixed initialcolor handling in August 1998 # from tkinter.commondialog import Dialog __all__ = ["Chooser", "askcolor"] class Chooser(Dialog): """Create a dialog for the tk_chooseColor command. Args: master: The master widget for this dialog. If not provided, defaults to options['parent'] (if defined). options: Dictionary of options for the tk_chooseColor call. initialcolor: Specifies the selected color when the dialog is first displayed. This can be a tk color string or a 3-tuple of ints in the range (0, 255) for an RGB triplet. parent: The parent window of the color dialog. The color dialog is displayed on top of this. title: A string for the title of the dialog box. """ command = "tk_chooseColor" def _fixoptions(self): """Ensure initialcolor is a tk color string. Convert initialcolor from a RGB triplet to a color string. """ try: color = self.options["initialcolor"] if isinstance(color, tuple): # Assume an RGB triplet. self.options["initialcolor"] = "#%02x%02x%02x" % color except KeyError: pass def _fixresult(self, widget, result): """Adjust result returned from call to tk_chooseColor. Return both an RGB tuple of ints in the range (0, 255) and the tk color string in the form #rrggbb. """ # Result can be many things: an empty tuple, an empty string, or # a _tkinter.Tcl_Obj, so this somewhat weird check handles that. if not result or not str(result): return None, None # canceled # To simplify application code, the color chooser returns # an RGB tuple together with the Tk color string. r, g, b = widget.winfo_rgb(result) return (r//256, g//256, b//256), str(result) # # convenience stuff def askcolor(color=None, **options): """Display dialog window for selection of a color. Convenience wrapper for the Chooser class. Displays the color chooser dialog with color as the initial value. """ if color: options = options.copy() options["initialcolor"] = color return Chooser(**options).show() # -------------------------------------------------------------------- # test stuff if __name__ == "__main__": print("color", askcolor()) ================================================ FILE: modules/tkinter/commondialog.py ================================================ # base class for tk common dialogues # # this module provides a base class for accessing the common # dialogues available in Tk 4.2 and newer. use filedialog, # colorchooser, and messagebox to access the individual # dialogs. # # written by Fredrik Lundh, May 1997 # __all__ = ["Dialog"] from tkinter import Frame class Dialog: command = None def __init__(self, master=None, **options): if not master: master = options.get('parent') self.master = master self.options = options def _fixoptions(self): pass # hook def _fixresult(self, widget, result): return result # hook def show(self, **options): # update instance options for k, v in options.items(): self.options[k] = v self._fixoptions() # we need a dummy widget to properly process the options # (at least as long as we use Tkinter 1.63) w = Frame(self.master) try: s = w.tk.call(self.command, *w._options(self.options)) s = self._fixresult(w, s) finally: try: # get rid of the widget w.destroy() except: pass return s ================================================ FILE: modules/tkinter/constants.py ================================================ # Symbolic constants for Tk # Booleans NO=FALSE=OFF=0 YES=TRUE=ON=1 # -anchor and -sticky N='n' S='s' W='w' E='e' NW='nw' SW='sw' NE='ne' SE='se' NS='ns' EW='ew' NSEW='nsew' CENTER='center' # -fill NONE='none' X='x' Y='y' BOTH='both' # -side LEFT='left' TOP='top' RIGHT='right' BOTTOM='bottom' # -relief RAISED='raised' SUNKEN='sunken' FLAT='flat' RIDGE='ridge' GROOVE='groove' SOLID = 'solid' # -orient HORIZONTAL='horizontal' VERTICAL='vertical' # -tabs NUMERIC='numeric' # -wrap CHAR='char' WORD='word' # -align BASELINE='baseline' # -bordermode INSIDE='inside' OUTSIDE='outside' # Special tags, marks and insert positions SEL='sel' SEL_FIRST='sel.first' SEL_LAST='sel.last' END='end' INSERT='insert' CURRENT='current' ANCHOR='anchor' ALL='all' # e.g. Canvas.delete(ALL) # Text widget and button states NORMAL='normal' DISABLED='disabled' ACTIVE='active' # Canvas state HIDDEN='hidden' # Menu item types CASCADE='cascade' CHECKBUTTON='checkbutton' COMMAND='command' RADIOBUTTON='radiobutton' SEPARATOR='separator' # Selection modes for list boxes SINGLE='single' BROWSE='browse' MULTIPLE='multiple' EXTENDED='extended' # Activestyle for list boxes # NONE='none' is also valid DOTBOX='dotbox' UNDERLINE='underline' # Various canvas styles PIESLICE='pieslice' CHORD='chord' ARC='arc' FIRST='first' LAST='last' BUTT='butt' PROJECTING='projecting' ROUND='round' BEVEL='bevel' MITER='miter' # Arguments to xview/yview MOVETO='moveto' SCROLL='scroll' UNITS='units' PAGES='pages' ================================================ FILE: modules/tkinter/dialog.py ================================================ # dialog.py -- Tkinter interface to the tk_dialog script. from tkinter import _cnfmerge, Widget, TclError, Button, Pack __all__ = ["Dialog"] DIALOG_ICON = 'questhead' class Dialog(Widget): def __init__(self, master=None, cnf={}, **kw): cnf = _cnfmerge((cnf, kw)) self.widgetName = '__dialog__' Widget._setup(self, master, cnf) self.num = self.tk.getint( self.tk.call( 'tk_dialog', self._w, cnf['title'], cnf['text'], cnf['bitmap'], cnf['default'], *cnf['strings'])) try: Widget.destroy(self) except TclError: pass def destroy(self): pass def _test(): d = Dialog(None, {'title': 'File Modified', 'text': 'File "Python.h" has been modified' ' since the last time it was saved.' ' Do you want to save it before' ' exiting the application.', 'bitmap': DIALOG_ICON, 'default': 0, 'strings': ('Save File', 'Discard Changes', 'Return to Editor')}) print(d.num) if __name__ == '__main__': t = Button(None, {'text': 'Test', 'command': _test, Pack: {}}) q = Button(None, {'text': 'Quit', 'command': t.quit, Pack: {}}) t.mainloop() ================================================ FILE: modules/tkinter/dnd.py ================================================ """Drag-and-drop support for Tkinter. This is very preliminary. I currently only support dnd *within* one application, between different windows (or within the same window). I am trying to make this as generic as possible -- not dependent on the use of a particular widget or icon type, etc. I also hope that this will work with Pmw. To enable an object to be dragged, you must create an event binding for it that starts the drag-and-drop process. Typically, you should bind to a callback function that you write. The function should call Tkdnd.dnd_start(source, event), where 'source' is the object to be dragged, and 'event' is the event that invoked the call (the argument to your callback function). Even though this is a class instantiation, the returned instance should not be stored -- it will be kept alive automatically for the duration of the drag-and-drop. When a drag-and-drop is already in process for the Tk interpreter, the call is *ignored*; this normally averts starting multiple simultaneous dnd processes, e.g. because different button callbacks all dnd_start(). The object is *not* necessarily a widget -- it can be any application-specific object that is meaningful to potential drag-and-drop targets. Potential drag-and-drop targets are discovered as follows. Whenever the mouse moves, and at the start and end of a drag-and-drop move, the Tk widget directly under the mouse is inspected. This is the target widget (not to be confused with the target object, yet to be determined). If there is no target widget, there is no dnd target object. If there is a target widget, and it has an attribute dnd_accept, this should be a function (or any callable object). The function is called as dnd_accept(source, event), where 'source' is the object being dragged (the object passed to dnd_start() above), and 'event' is the most recent event object (generally a event; it can also be or ). If the dnd_accept() function returns something other than None, this is the new dnd target object. If dnd_accept() returns None, or if the target widget has no dnd_accept attribute, the target widget's parent is considered as the target widget, and the search for a target object is repeated from there. If necessary, the search is repeated all the way up to the root widget. If none of the target widgets can produce a target object, there is no target object (the target object is None). The target object thus produced, if any, is called the new target object. It is compared with the old target object (or None, if there was no old target widget). There are several cases ('source' is the source object, and 'event' is the most recent event object): - Both the old and new target objects are None. Nothing happens. - The old and new target objects are the same object. Its method dnd_motion(source, event) is called. - The old target object was None, and the new target object is not None. The new target object's method dnd_enter(source, event) is called. - The new target object is None, and the old target object is not None. The old target object's method dnd_leave(source, event) is called. - The old and new target objects differ and neither is None. The old target object's method dnd_leave(source, event), and then the new target object's method dnd_enter(source, event) is called. Once this is done, the new target object replaces the old one, and the Tk mainloop proceeds. The return value of the methods mentioned above is ignored; if they raise an exception, the normal exception handling mechanisms take over. The drag-and-drop processes can end in two ways: a final target object is selected, or no final target object is selected. When a final target object is selected, it will always have been notified of the potential drop by a call to its dnd_enter() method, as described above, and possibly one or more calls to its dnd_motion() method; its dnd_leave() method has not been called since the last call to dnd_enter(). The target is notified of the drop by a call to its method dnd_commit(source, event). If no final target object is selected, and there was an old target object, its dnd_leave(source, event) method is called to complete the dnd sequence. Finally, the source object is notified that the drag-and-drop process is over, by a call to source.dnd_end(target, event), specifying either the selected target object, or None if no target object was selected. The source object can use this to implement the commit action; this is sometimes simpler than to do it in the target's dnd_commit(). The target's dnd_commit() method could then simply be aliased to dnd_leave(). At any time during a dnd sequence, the application can cancel the sequence by calling the cancel() method on the object returned by dnd_start(). This will call dnd_leave() if a target is currently active; it will never call dnd_commit(). """ import tkinter __all__ = ["dnd_start", "DndHandler"] # The factory function def dnd_start(source, event): h = DndHandler(source, event) if h.root: return h else: return None # The class that does the work class DndHandler: root = None def __init__(self, source, event): if event.num > 5: return root = event.widget._root() try: root.__dnd return # Don't start recursive dnd except AttributeError: root.__dnd = self self.root = root self.source = source self.target = None self.initial_button = button = event.num self.initial_widget = widget = event.widget self.release_pattern = "" % (button, button) self.save_cursor = widget['cursor'] or "" widget.bind(self.release_pattern, self.on_release) widget.bind("", self.on_motion) widget['cursor'] = "hand2" def __del__(self): root = self.root self.root = None if root: try: del root.__dnd except AttributeError: pass def on_motion(self, event): x, y = event.x_root, event.y_root target_widget = self.initial_widget.winfo_containing(x, y) source = self.source new_target = None while target_widget: try: attr = target_widget.dnd_accept except AttributeError: pass else: new_target = attr(source, event) if new_target: break target_widget = target_widget.master old_target = self.target if old_target is new_target: if old_target: old_target.dnd_motion(source, event) else: if old_target: self.target = None old_target.dnd_leave(source, event) if new_target: new_target.dnd_enter(source, event) self.target = new_target def on_release(self, event): self.finish(event, 1) def cancel(self, event=None): self.finish(event, 0) def finish(self, event, commit=0): target = self.target source = self.source widget = self.initial_widget root = self.root try: del root.__dnd self.initial_widget.unbind(self.release_pattern) self.initial_widget.unbind("") widget['cursor'] = self.save_cursor self.target = self.source = self.initial_widget = self.root = None if target: if commit: target.dnd_commit(source, event) else: target.dnd_leave(source, event) finally: source.dnd_end(target, event) # ---------------------------------------------------------------------- # The rest is here for testing and demonstration purposes only! class Icon: def __init__(self, name): self.name = name self.canvas = self.label = self.id = None def attach(self, canvas, x=10, y=10): if canvas is self.canvas: self.canvas.coords(self.id, x, y) return if self.canvas: self.detach() if not canvas: return label = tkinter.Label(canvas, text=self.name, borderwidth=2, relief="raised") id = canvas.create_window(x, y, window=label, anchor="nw") self.canvas = canvas self.label = label self.id = id label.bind("", self.press) def detach(self): canvas = self.canvas if not canvas: return id = self.id label = self.label self.canvas = self.label = self.id = None canvas.delete(id) label.destroy() def press(self, event): if dnd_start(self, event): # where the pointer is relative to the label widget: self.x_off = event.x self.y_off = event.y # where the widget is relative to the canvas: self.x_orig, self.y_orig = self.canvas.coords(self.id) def move(self, event): x, y = self.where(self.canvas, event) self.canvas.coords(self.id, x, y) def putback(self): self.canvas.coords(self.id, self.x_orig, self.y_orig) def where(self, canvas, event): # where the corner of the canvas is relative to the screen: x_org = canvas.winfo_rootx() y_org = canvas.winfo_rooty() # where the pointer is relative to the canvas widget: x = event.x_root - x_org y = event.y_root - y_org # compensate for initial pointer offset return x - self.x_off, y - self.y_off def dnd_end(self, target, event): pass class Tester: def __init__(self, root): self.top = tkinter.Toplevel(root) self.canvas = tkinter.Canvas(self.top, width=100, height=100) self.canvas.pack(fill="both", expand=1) self.canvas.dnd_accept = self.dnd_accept def dnd_accept(self, source, event): return self def dnd_enter(self, source, event): self.canvas.focus_set() # Show highlight border x, y = source.where(self.canvas, event) x1, y1, x2, y2 = source.canvas.bbox(source.id) dx, dy = x2-x1, y2-y1 self.dndid = self.canvas.create_rectangle(x, y, x+dx, y+dy) self.dnd_motion(source, event) def dnd_motion(self, source, event): x, y = source.where(self.canvas, event) x1, y1, x2, y2 = self.canvas.bbox(self.dndid) self.canvas.move(self.dndid, x-x1, y-y1) def dnd_leave(self, source, event): self.top.focus_set() # Hide highlight border self.canvas.delete(self.dndid) self.dndid = None def dnd_commit(self, source, event): self.dnd_leave(source, event) x, y = source.where(self.canvas, event) source.attach(self.canvas, x, y) def test(): root = tkinter.Tk() root.geometry("+1+1") tkinter.Button(command=root.quit, text="Quit").pack() t1 = Tester(root) t1.top.geometry("+1+60") t2 = Tester(root) t2.top.geometry("+120+60") t3 = Tester(root) t3.top.geometry("+240+60") i1 = Icon("ICON1") i2 = Icon("ICON2") i3 = Icon("ICON3") i1.attach(t1.canvas) i2.attach(t2.canvas) i3.attach(t3.canvas) root.mainloop() if __name__ == '__main__': test() ================================================ FILE: modules/tkinter/filedialog.py ================================================ """File selection dialog classes. Classes: - FileDialog - LoadFileDialog - SaveFileDialog This module also presents tk common file dialogues, it provides interfaces to the native file dialogues available in Tk 4.2 and newer, and the directory dialogue available in Tk 8.3 and newer. These interfaces were written by Fredrik Lundh, May 1997. """ __all__ = ["FileDialog", "LoadFileDialog", "SaveFileDialog", "Open", "SaveAs", "Directory", "askopenfilename", "asksaveasfilename", "askopenfilenames", "askopenfile", "askopenfiles", "asksaveasfile", "askdirectory"] import fnmatch import os from tkinter import ( Frame, LEFT, YES, BOTTOM, Entry, TOP, Button, Tk, X, Toplevel, RIGHT, Y, END, Listbox, BOTH, Scrollbar, ) from tkinter.dialog import Dialog from tkinter import commondialog from tkinter.simpledialog import _setup_dialog dialogstates = {} class FileDialog: """Standard file selection dialog -- no checks on selected file. Usage: d = FileDialog(master) fname = d.go(dir_or_file, pattern, default, key) if fname is None: ...canceled... else: ...open file... All arguments to go() are optional. The 'key' argument specifies a key in the global dictionary 'dialogstates', which keeps track of the values for the directory and pattern arguments, overriding the values passed in (it does not keep track of the default argument!). If no key is specified, the dialog keeps no memory of previous state. Note that memory is kept even when the dialog is canceled. (All this emulates the behavior of the Macintosh file selection dialogs.) """ title = "File Selection Dialog" def __init__(self, master, title=None): if title is None: title = self.title self.master = master self.directory = None self.top = Toplevel(master) self.top.title(title) self.top.iconname(title) _setup_dialog(self.top) self.botframe = Frame(self.top) self.botframe.pack(side=BOTTOM, fill=X) self.selection = Entry(self.top) self.selection.pack(side=BOTTOM, fill=X) self.selection.bind('', self.ok_event) self.filter = Entry(self.top) self.filter.pack(side=TOP, fill=X) self.filter.bind('', self.filter_command) self.midframe = Frame(self.top) self.midframe.pack(expand=YES, fill=BOTH) self.filesbar = Scrollbar(self.midframe) self.filesbar.pack(side=RIGHT, fill=Y) self.files = Listbox(self.midframe, exportselection=0, yscrollcommand=(self.filesbar, 'set')) self.files.pack(side=RIGHT, expand=YES, fill=BOTH) btags = self.files.bindtags() self.files.bindtags(btags[1:] + btags[:1]) self.files.bind('', self.files_select_event) self.files.bind('', self.files_double_event) self.filesbar.config(command=(self.files, 'yview')) self.dirsbar = Scrollbar(self.midframe) self.dirsbar.pack(side=LEFT, fill=Y) self.dirs = Listbox(self.midframe, exportselection=0, yscrollcommand=(self.dirsbar, 'set')) self.dirs.pack(side=LEFT, expand=YES, fill=BOTH) self.dirsbar.config(command=(self.dirs, 'yview')) btags = self.dirs.bindtags() self.dirs.bindtags(btags[1:] + btags[:1]) self.dirs.bind('', self.dirs_select_event) self.dirs.bind('', self.dirs_double_event) self.ok_button = Button(self.botframe, text="OK", command=self.ok_command) self.ok_button.pack(side=LEFT) self.filter_button = Button(self.botframe, text="Filter", command=self.filter_command) self.filter_button.pack(side=LEFT, expand=YES) self.cancel_button = Button(self.botframe, text="Cancel", command=self.cancel_command) self.cancel_button.pack(side=RIGHT) self.top.protocol('WM_DELETE_WINDOW', self.cancel_command) # XXX Are the following okay for a general audience? self.top.bind('', self.cancel_command) self.top.bind('', self.cancel_command) def go(self, dir_or_file=os.curdir, pattern="*", default="", key=None): if key and key in dialogstates: self.directory, pattern = dialogstates[key] else: dir_or_file = os.path.expanduser(dir_or_file) if os.path.isdir(dir_or_file): self.directory = dir_or_file else: self.directory, default = os.path.split(dir_or_file) self.set_filter(self.directory, pattern) self.set_selection(default) self.filter_command() self.selection.focus_set() self.top.wait_visibility() # window needs to be visible for the grab self.top.grab_set() self.how = None self.master.mainloop() # Exited by self.quit(how) if key: directory, pattern = self.get_filter() if self.how: directory = os.path.dirname(self.how) dialogstates[key] = directory, pattern self.top.destroy() return self.how def quit(self, how=None): self.how = how self.master.quit() # Exit mainloop() def dirs_double_event(self, event): self.filter_command() def dirs_select_event(self, event): dir, pat = self.get_filter() subdir = self.dirs.get('active') dir = os.path.normpath(os.path.join(self.directory, subdir)) self.set_filter(dir, pat) def files_double_event(self, event): self.ok_command() def files_select_event(self, event): file = self.files.get('active') self.set_selection(file) def ok_event(self, event): self.ok_command() def ok_command(self): self.quit(self.get_selection()) def filter_command(self, event=None): dir, pat = self.get_filter() try: names = os.listdir(dir) except OSError: self.master.bell() return self.directory = dir self.set_filter(dir, pat) names.sort() subdirs = [os.pardir] matchingfiles = [] for name in names: fullname = os.path.join(dir, name) if os.path.isdir(fullname): subdirs.append(name) elif fnmatch.fnmatch(name, pat): matchingfiles.append(name) self.dirs.delete(0, END) for name in subdirs: self.dirs.insert(END, name) self.files.delete(0, END) for name in matchingfiles: self.files.insert(END, name) head, tail = os.path.split(self.get_selection()) if tail == os.curdir: tail = '' self.set_selection(tail) def get_filter(self): filter = self.filter.get() filter = os.path.expanduser(filter) if filter[-1:] == os.sep or os.path.isdir(filter): filter = os.path.join(filter, "*") return os.path.split(filter) def get_selection(self): file = self.selection.get() file = os.path.expanduser(file) return file def cancel_command(self, event=None): self.quit() def set_filter(self, dir, pat): if not os.path.isabs(dir): try: pwd = os.getcwd() except OSError: pwd = None if pwd: dir = os.path.join(pwd, dir) dir = os.path.normpath(dir) self.filter.delete(0, END) self.filter.insert(END, os.path.join(dir or os.curdir, pat or "*")) def set_selection(self, file): self.selection.delete(0, END) self.selection.insert(END, os.path.join(self.directory, file)) class LoadFileDialog(FileDialog): """File selection dialog which checks that the file exists.""" title = "Load File Selection Dialog" def ok_command(self): file = self.get_selection() if not os.path.isfile(file): self.master.bell() else: self.quit(file) class SaveFileDialog(FileDialog): """File selection dialog which checks that the file may be created.""" title = "Save File Selection Dialog" def ok_command(self): file = self.get_selection() if os.path.exists(file): if os.path.isdir(file): self.master.bell() return d = Dialog(self.top, title="Overwrite Existing File Question", text="Overwrite existing file %r?" % (file,), bitmap='questhead', default=1, strings=("Yes", "Cancel")) if d.num != 0: return else: head, tail = os.path.split(file) if not os.path.isdir(head): self.master.bell() return self.quit(file) # For the following classes and modules: # # options (all have default values): # # - defaultextension: added to filename if not explicitly given # # - filetypes: sequence of (label, pattern) tuples. the same pattern # may occur with several patterns. use "*" as pattern to indicate # all files. # # - initialdir: initial directory. preserved by dialog instance. # # - initialfile: initial file (ignored by the open dialog). preserved # by dialog instance. # # - parent: which window to place the dialog on top of # # - title: dialog title # # - multiple: if true user may select more than one file # # options for the directory chooser: # # - initialdir, parent, title: see above # # - mustexist: if true, user must pick an existing directory # class _Dialog(commondialog.Dialog): def _fixoptions(self): try: # make sure "filetypes" is a tuple self.options["filetypes"] = tuple(self.options["filetypes"]) except KeyError: pass def _fixresult(self, widget, result): if result: # keep directory and filename until next time # convert Tcl path objects to strings try: result = result.string except AttributeError: # it already is a string pass path, file = os.path.split(result) self.options["initialdir"] = path self.options["initialfile"] = file self.filename = result # compatibility return result # # file dialogs class Open(_Dialog): "Ask for a filename to open" command = "tk_getOpenFile" def _fixresult(self, widget, result): if isinstance(result, tuple): # multiple results: result = tuple([getattr(r, "string", r) for r in result]) if result: path, file = os.path.split(result[0]) self.options["initialdir"] = path # don't set initialfile or filename, as we have multiple of these return result if not widget.tk.wantobjects() and "multiple" in self.options: # Need to split result explicitly return self._fixresult(widget, widget.tk.splitlist(result)) return _Dialog._fixresult(self, widget, result) class SaveAs(_Dialog): "Ask for a filename to save as" command = "tk_getSaveFile" # the directory dialog has its own _fix routines. class Directory(commondialog.Dialog): "Ask for a directory" command = "tk_chooseDirectory" def _fixresult(self, widget, result): if result: # convert Tcl path objects to strings try: result = result.string except AttributeError: # it already is a string pass # keep directory until next time self.options["initialdir"] = result self.directory = result # compatibility return result # # convenience stuff def askopenfilename(**options): "Ask for a filename to open" return Open(**options).show() def asksaveasfilename(**options): "Ask for a filename to save as" return SaveAs(**options).show() def askopenfilenames(**options): """Ask for multiple filenames to open Returns a list of filenames or empty list if cancel button selected """ options["multiple"]=1 return Open(**options).show() # FIXME: are the following perhaps a bit too convenient? def askopenfile(mode = "r", **options): "Ask for a filename to open, and returned the opened file" filename = Open(**options).show() if filename: return open(filename, mode) return None def askopenfiles(mode = "r", **options): """Ask for multiple filenames and return the open file objects returns a list of open file objects or an empty list if cancel selected """ files = askopenfilenames(**options) if files: ofiles=[] for filename in files: ofiles.append(open(filename, mode)) files=ofiles return files def asksaveasfile(mode = "w", **options): "Ask for a filename to save as, and returned the opened file" filename = SaveAs(**options).show() if filename: return open(filename, mode) return None def askdirectory (**options): "Ask for a directory, and return the file name" return Directory(**options).show() # -------------------------------------------------------------------- # test stuff def test(): """Simple test program.""" root = Tk() root.withdraw() fd = LoadFileDialog(root) loadfile = fd.go(key="test") fd = SaveFileDialog(root) savefile = fd.go(key="test") print(loadfile, savefile) # Since the file name may contain non-ASCII characters, we need # to find an encoding that likely supports the file name, and # displays correctly on the terminal. # Start off with UTF-8 enc = "utf-8" import sys # See whether CODESET is defined try: import locale locale.setlocale(locale.LC_ALL,'') enc = locale.nl_langinfo(locale.CODESET) except (ImportError, AttributeError): pass # dialog for opening files openfilename=askopenfilename(filetypes=[("all files", "*")]) try: fp=open(openfilename,"r") fp.close() except: print("Could not open File: ") print(sys.exc_info()[1]) print("open", openfilename.encode(enc)) # dialog for saving files saveasfilename=asksaveasfilename() print("saveas", saveasfilename.encode(enc)) if __name__ == '__main__': test() ================================================ FILE: modules/tkinter/font.py ================================================ # Tkinter font wrapper # # written by Fredrik Lundh, February 1998 # import itertools import tkinter __version__ = "0.9" __all__ = ["NORMAL", "ROMAN", "BOLD", "ITALIC", "nametofont", "Font", "families", "names"] # weight/slant NORMAL = "normal" ROMAN = "roman" BOLD = "bold" ITALIC = "italic" def nametofont(name): """Given the name of a tk named font, returns a Font representation. """ return Font(name=name, exists=True) class Font: """Represents a named font. Constructor options are: font -- font specifier (name, system font, or (family, size, style)-tuple) name -- name to use for this font configuration (defaults to a unique name) exists -- does a named font by this name already exist? Creates a new named font if False, points to the existing font if True. Raises _tkinter.TclError if the assertion is false. the following are ignored if font is specified: family -- font 'family', e.g. Courier, Times, Helvetica size -- font size in points weight -- font thickness: NORMAL, BOLD slant -- font slant: ROMAN, ITALIC underline -- font underlining: false (0), true (1) overstrike -- font strikeout: false (0), true (1) """ counter = itertools.count(1) def _set(self, kw): options = [] for k, v in kw.items(): options.append("-"+k) options.append(str(v)) return tuple(options) def _get(self, args): options = [] for k in args: options.append("-"+k) return tuple(options) def _mkdict(self, args): options = {} for i in range(0, len(args), 2): options[args[i][1:]] = args[i+1] return options def __init__(self, root=None, font=None, name=None, exists=False, **options): if not root: root = tkinter._get_default_root('use font') tk = getattr(root, 'tk', root) if font: # get actual settings corresponding to the given font font = tk.splitlist(tk.call("font", "actual", font)) else: font = self._set(options) if not name: name = "font" + str(next(self.counter)) self.name = name if exists: self.delete_font = False # confirm font exists if self.name not in tk.splitlist(tk.call("font", "names")): raise tkinter._tkinter.TclError( "named font %s does not already exist" % (self.name,)) # if font config info supplied, apply it if font: tk.call("font", "configure", self.name, *font) else: # create new font (raises TclError if the font exists) tk.call("font", "create", self.name, *font) self.delete_font = True self._tk = tk self._split = tk.splitlist self._call = tk.call def __str__(self): return self.name def __eq__(self, other): if not isinstance(other, Font): return NotImplemented return self.name == other.name and self._tk == other._tk def __getitem__(self, key): return self.cget(key) def __setitem__(self, key, value): self.configure(**{key: value}) def __del__(self): try: if self.delete_font: self._call("font", "delete", self.name) except Exception: pass def copy(self): "Return a distinct copy of the current font" return Font(self._tk, **self.actual()) def actual(self, option=None, displayof=None): "Return actual font attributes" args = () if displayof: args = ('-displayof', displayof) if option: args = args + ('-' + option, ) return self._call("font", "actual", self.name, *args) else: return self._mkdict( self._split(self._call("font", "actual", self.name, *args))) def cget(self, option): "Get font attribute" return self._call("font", "config", self.name, "-"+option) def config(self, **options): "Modify font attributes" if options: self._call("font", "config", self.name, *self._set(options)) else: return self._mkdict( self._split(self._call("font", "config", self.name))) configure = config def measure(self, text, displayof=None): "Return text width" args = (text,) if displayof: args = ('-displayof', displayof, text) return self._tk.getint(self._call("font", "measure", self.name, *args)) def metrics(self, *options, **kw): """Return font metrics. For best performance, create a dummy widget using this font before calling this method.""" args = () displayof = kw.pop('displayof', None) if displayof: args = ('-displayof', displayof) if options: args = args + self._get(options) return self._tk.getint( self._call("font", "metrics", self.name, *args)) else: res = self._split(self._call("font", "metrics", self.name, *args)) options = {} for i in range(0, len(res), 2): options[res[i][1:]] = self._tk.getint(res[i+1]) return options def families(root=None, displayof=None): "Get font families (as a tuple)" if not root: root = tkinter._get_default_root('use font.families()') args = () if displayof: args = ('-displayof', displayof) return root.tk.splitlist(root.tk.call("font", "families", *args)) def names(root=None): "Get names of defined fonts (as a tuple)" if not root: root = tkinter._get_default_root('use font.names()') return root.tk.splitlist(root.tk.call("font", "names")) # -------------------------------------------------------------------- # test stuff if __name__ == "__main__": root = tkinter.Tk() # create a font f = Font(family="times", size=30, weight=NORMAL) print(f.actual()) print(f.actual("family")) print(f.actual("weight")) print(f.config()) print(f.cget("family")) print(f.cget("weight")) print(names()) print(f.measure("hello"), f.metrics("linespace")) print(f.metrics(displayof=root)) f = Font(font=("Courier", 20, "bold")) print(f.measure("hello"), f.metrics("linespace", displayof=root)) w = tkinter.Label(root, text="Hello, world", font=f) w.pack() w = tkinter.Button(root, text="Quit!", command=root.destroy) w.pack() fb = Font(font=w["font"]).copy() fb.config(weight=BOLD) w.config(font=fb) tkinter.mainloop() ================================================ FILE: modules/tkinter/messagebox.py ================================================ # tk common message boxes # # this module provides an interface to the native message boxes # available in Tk 4.2 and newer. # # written by Fredrik Lundh, May 1997 # # # options (all have default values): # # - default: which button to make default (one of the reply codes) # # - icon: which icon to display (see below) # # - message: the message to display # # - parent: which window to place the dialog on top of # # - title: dialog title # # - type: dialog type; that is, which buttons to display (see below) # print("from tkinter import messagebox") from tkinter.commondialog import Dialog __all__ = ["showinfo", "showwarning", "showerror", "askquestion", "askokcancel", "askyesno", "askyesnocancel", "askretrycancel"] # # constants # icons ERROR = "error" INFO = "info" QUESTION = "question" WARNING = "warning" # types ABORTRETRYIGNORE = "abortretryignore" OK = "ok" OKCANCEL = "okcancel" RETRYCANCEL = "retrycancel" YESNO = "yesno" YESNOCANCEL = "yesnocancel" # replies ABORT = "abort" RETRY = "retry" IGNORE = "ignore" OK = "ok" CANCEL = "cancel" YES = "yes" NO = "no" # # message dialog class class Message(Dialog): "A message box" command = "tk_messageBox" # # convenience stuff # Rename _icon and _type options to allow overriding them in options def _show(title=None, message=None, _icon=None, _type=None, **options): if _icon and "icon" not in options: options["icon"] = _icon if _type and "type" not in options: options["type"] = _type if title: options["title"] = title if message: options["message"] = message res = Message(**options).show() # In some Tcl installations, yes/no is converted into a boolean. if isinstance(res, bool): if res: return YES return NO # In others we get a Tcl_Obj. return str(res) def showinfo(title=None, message=None, **options): "Show an info message" print(f'tkinter.messagebox.showinfo(title="{title}", message="{message}")') return _show(title, message, INFO, OK, **options) def showwarning(title=None, message=None, **options): "Show a warning message" print(f'tkinter.messagebox.showwarning(title="{title}", message="{message}")') return _show(title, message, WARNING, OK, **options) def showerror(title=None, message=None, **options): "Show an error message" print(f'tkinter.messagebox.showerror(title="{title}", message="{message}")') return _show(title, message, ERROR, OK, **options) def askquestion(title=None, message=None, **options): "Ask a question" print(f'tkinter.messagebox.askquestion(title="{title}", message="{message}")') return _show(title, message, QUESTION, YESNO, **options) def askokcancel(title=None, message=None, **options): "Ask if operation should proceed; return true if the answer is ok" print(f'tkinter.messagebox.askokcancel(title="{title}", message="{message}")') s = _show(title, message, QUESTION, OKCANCEL, **options) return s == OK def askyesno(title=None, message=None, **options): "Ask a question; return true if the answer is yes" print(f'tkinter.messagebox.askyesno(title="{title}", message="{message}")') s = _show(title, message, QUESTION, YESNO, **options) return s == YES def askyesnocancel(title=None, message=None, **options): "Ask a question; return true if the answer is yes, None if cancelled." print(f'tkinter.messagebox.askyesnocancel(title="{title}", message="{message}")') s = _show(title, message, QUESTION, YESNOCANCEL, **options) # s might be a Tcl index object, so convert it to a string s = str(s) if s == CANCEL: return None return s == YES def askretrycancel(title=None, message=None, **options): "Ask if operation should be retried; return true if the answer is yes" print(f'tkinter.messagebox.askretrycancel(title="{title}", message="{message}")') s = _show(title, message, WARNING, RETRYCANCEL, **options) return s == RETRY # -------------------------------------------------------------------- # test stuff if __name__ == "__main__": print("info", showinfo("Spam", "Egg Information")) print("warning", showwarning("Spam", "Egg Warning")) print("error", showerror("Spam", "Egg Alert")) print("question", askquestion("Spam", "Question?")) print("proceed", askokcancel("Spam", "Proceed?")) print("yes/no", askyesno("Spam", "Got it?")) print("yes/no/cancel", askyesnocancel("Spam", "Want it?")) print("try again", askretrycancel("Spam", "Try again?")) ================================================ FILE: modules/tkinter/scrolledtext.py ================================================ """A ScrolledText widget feels like a text widget but also has a vertical scroll bar on its right. (Later, options may be added to add a horizontal bar as well, to make the bars disappear automatically when not needed, to move them to the other side of the window, etc.) Configuration options are passed to the Text widget. A Frame widget is inserted between the master and the text, to hold the Scrollbar widget. Most methods calls are inherited from the Text widget; Pack, Grid and Place methods are redirected to the Frame widget however. """ from tkinter import Frame, Text, Scrollbar, Pack, Grid, Place from tkinter.constants import RIGHT, LEFT, Y, BOTH __all__ = ['ScrolledText'] class ScrolledText(Text): def __init__(self, master=None, **kw): self.frame = Frame(master) self.vbar = Scrollbar(self.frame) self.vbar.pack(side=RIGHT, fill=Y) kw.update({'yscrollcommand': self.vbar.set}) Text.__init__(self, self.frame, **kw) self.pack(side=LEFT, fill=BOTH, expand=True) self.vbar['command'] = self.yview # Copy geometry methods of self.frame without overriding Text # methods -- hack! text_meths = vars(Text).keys() methods = vars(Pack).keys() | vars(Grid).keys() | vars(Place).keys() methods = methods.difference(text_meths) for m in methods: if m[0] != '_' and m != 'config' and m != 'configure': setattr(self, m, getattr(self.frame, m)) def __str__(self): return str(self.frame) def example(): from tkinter.constants import END stext = ScrolledText(bg='white', height=10) stext.insert(END, __doc__) stext.pack(fill=BOTH, side=LEFT, expand=True) stext.focus_set() stext.mainloop() if __name__ == "__main__": example() ================================================ FILE: modules/tkinter/simpledialog.py ================================================ # # An Introduction to Tkinter # # Copyright (c) 1997 by Fredrik Lundh # # This copyright applies to Dialog, askinteger, askfloat and asktring # # fredrik@pythonware.com # http://www.pythonware.com # """This modules handles dialog boxes. It contains the following public symbols: SimpleDialog -- A simple but flexible modal dialog box Dialog -- a base class for dialogs askinteger -- get an integer from the user askfloat -- get a float from the user askstring -- get a string from the user """ from tkinter import * from tkinter import messagebox, _get_default_root class SimpleDialog: def __init__(self, master, text='', buttons=[], default=None, cancel=None, title=None, class_=None): if class_: self.root = Toplevel(master, class_=class_) else: self.root = Toplevel(master) if title: self.root.title(title) self.root.iconname(title) _setup_dialog(self.root) self.message = Message(self.root, text=text, aspect=400) self.message.pack(expand=1, fill=BOTH) self.frame = Frame(self.root) self.frame.pack() self.num = default self.cancel = cancel self.default = default self.root.bind('', self.return_event) for num in range(len(buttons)): s = buttons[num] b = Button(self.frame, text=s, command=(lambda self=self, num=num: self.done(num))) if num == default: b.config(relief=RIDGE, borderwidth=8) b.pack(side=LEFT, fill=BOTH, expand=1) self.root.protocol('WM_DELETE_WINDOW', self.wm_delete_window) self._set_transient(master) def _set_transient(self, master, relx=0.5, rely=0.3): widget = self.root widget.withdraw() # Remain invisible while we figure out the geometry widget.transient(master) widget.update_idletasks() # Actualize geometry information if master.winfo_ismapped(): m_width = master.winfo_width() m_height = master.winfo_height() m_x = master.winfo_rootx() m_y = master.winfo_rooty() else: m_width = master.winfo_screenwidth() m_height = master.winfo_screenheight() m_x = m_y = 0 w_width = widget.winfo_reqwidth() w_height = widget.winfo_reqheight() x = m_x + (m_width - w_width) * relx y = m_y + (m_height - w_height) * rely if x+w_width > master.winfo_screenwidth(): x = master.winfo_screenwidth() - w_width elif x < 0: x = 0 if y+w_height > master.winfo_screenheight(): y = master.winfo_screenheight() - w_height elif y < 0: y = 0 widget.geometry("+%d+%d" % (x, y)) widget.deiconify() # Become visible at the desired location def go(self): self.root.wait_visibility() self.root.grab_set() self.root.mainloop() self.root.destroy() return self.num def return_event(self, event): if self.default is None: self.root.bell() else: self.done(self.default) def wm_delete_window(self): if self.cancel is None: self.root.bell() else: self.done(self.cancel) def done(self, num): self.num = num self.root.quit() class Dialog(Toplevel): '''Class to open dialogs. This class is intended as a base class for custom dialogs ''' def __init__(self, parent, title = None): '''Initialize a dialog. Arguments: parent -- a parent window (the application window) title -- the dialog title ''' master = parent if not master: master = _get_default_root('create dialog window') Toplevel.__init__(self, master) self.withdraw() # remain invisible for now # If the parent is not viewable, don't # make the child transient, or else it # would be opened withdrawn if parent is not None and parent.winfo_viewable(): self.transient(parent) if title: self.title(title) _setup_dialog(self) self.parent = parent self.result = None body = Frame(self) self.initial_focus = self.body(body) body.pack(padx=5, pady=5) self.buttonbox() if not self.initial_focus: self.initial_focus = self self.protocol("WM_DELETE_WINDOW", self.cancel) if parent is not None: self.geometry("+%d+%d" % (parent.winfo_rootx()+50, parent.winfo_rooty()+50)) self.deiconify() # become visible now self.initial_focus.focus_set() # wait for window to appear on screen before calling grab_set self.wait_visibility() self.grab_set() self.wait_window(self) def destroy(self): '''Destroy the window''' self.initial_focus = None Toplevel.destroy(self) # # construction hooks def body(self, master): '''create dialog body. return widget that should have initial focus. This method should be overridden, and is called by the __init__ method. ''' pass def buttonbox(self): '''add standard button box. override if you do not want the standard buttons ''' box = Frame(self) w = Button(box, text="OK", width=10, command=self.ok, default=ACTIVE) w.pack(side=LEFT, padx=5, pady=5) w = Button(box, text="Cancel", width=10, command=self.cancel) w.pack(side=LEFT, padx=5, pady=5) self.bind("", self.ok) self.bind("", self.cancel) box.pack() # # standard button semantics def ok(self, event=None): if not self.validate(): self.initial_focus.focus_set() # put focus back return self.withdraw() self.update_idletasks() try: self.apply() finally: self.cancel() def cancel(self, event=None): # put focus back to the parent window if self.parent is not None: self.parent.focus_set() self.destroy() # # command hooks def validate(self): '''validate the data This method is called automatically to validate the data before the dialog is destroyed. By default, it always validates OK. ''' return 1 # override def apply(self): '''process the data This method is called automatically to process the data, *after* the dialog is destroyed. By default, it does nothing. ''' pass # override def _setup_dialog(w): if w._windowingsystem == "aqua": w.tk.call("::tk::unsupported::MacWindowStyle", "style", w, "moveableModal", "") elif w._windowingsystem == "x11": w.wm_attributes("-type", "dialog") # -------------------------------------------------------------------- # convenience dialogues class _QueryDialog(Dialog): def __init__(self, title, prompt, initialvalue=None, minvalue = None, maxvalue = None, parent = None): self.prompt = prompt self.minvalue = minvalue self.maxvalue = maxvalue self.initialvalue = initialvalue Dialog.__init__(self, parent, title) def destroy(self): self.entry = None Dialog.destroy(self) def body(self, master): w = Label(master, text=self.prompt, justify=LEFT) w.grid(row=0, padx=5, sticky=W) self.entry = Entry(master, name="entry") self.entry.grid(row=1, padx=5, sticky=W+E) if self.initialvalue is not None: self.entry.insert(0, self.initialvalue) self.entry.select_range(0, END) return self.entry def validate(self): try: result = self.getresult() except ValueError: messagebox.showwarning( "Illegal value", self.errormessage + "\nPlease try again", parent = self ) return 0 if self.minvalue is not None and result < self.minvalue: messagebox.showwarning( "Too small", "The allowed minimum value is %s. " "Please try again." % self.minvalue, parent = self ) return 0 if self.maxvalue is not None and result > self.maxvalue: messagebox.showwarning( "Too large", "The allowed maximum value is %s. " "Please try again." % self.maxvalue, parent = self ) return 0 self.result = result return 1 class _QueryInteger(_QueryDialog): errormessage = "Not an integer." def getresult(self): return self.getint(self.entry.get()) def askinteger(title, prompt, **kw): '''get an integer from the user Arguments: title -- the dialog title prompt -- the label text **kw -- see SimpleDialog class Return value is an integer ''' d = _QueryInteger(title, prompt, **kw) return d.result class _QueryFloat(_QueryDialog): errormessage = "Not a floating point value." def getresult(self): return self.getdouble(self.entry.get()) def askfloat(title, prompt, **kw): '''get a float from the user Arguments: title -- the dialog title prompt -- the label text **kw -- see SimpleDialog class Return value is a float ''' d = _QueryFloat(title, prompt, **kw) return d.result class _QueryString(_QueryDialog): def __init__(self, *args, **kw): if "show" in kw: self.__show = kw["show"] del kw["show"] else: self.__show = None _QueryDialog.__init__(self, *args, **kw) def body(self, master): entry = _QueryDialog.body(self, master) if self.__show is not None: entry.configure(show=self.__show) return entry def getresult(self): return self.entry.get() def askstring(title, prompt, **kw): '''get a string from the user Arguments: title -- the dialog title prompt -- the label text **kw -- see SimpleDialog class Return value is a string ''' d = _QueryString(title, prompt, **kw) return d.result if __name__ == '__main__': def test(): root = Tk() def doit(root=root): d = SimpleDialog(root, text="This is a test dialog. " "Would this have been an actual dialog, " "the buttons below would have been glowing " "in soft pink light.\n" "Do you believe this?", buttons=["Yes", "No", "Cancel"], default=0, cancel=2, title="Test Dialog") print(d.go()) print(askinteger("Spam", "Egg count", initialvalue=12*12)) print(askfloat("Spam", "Egg weight\n(in tons)", minvalue=1, maxvalue=100)) print(askstring("Spam", "Egg label")) t = Button(root, text='Test', command=doit) t.pack() q = Button(root, text='Quit', command=t.quit) q.pack() t.mainloop() test() ================================================ FILE: modules/tkinter/test/README ================================================ Writing new tests ================= Precaution ---------- New tests should always use only one Tk window at once, like all the current tests do. This means that you have to destroy the current window before creating another one, and clean up after the test. The motivation behind this is that some tests may depend on having its window focused while it is running to work properly, and it may be hard to force focus on your window across platforms (right now only test_traversal at test_ttk.test_widgets.NotebookTest depends on this). ================================================ FILE: modules/tkinter/test/runtktests.py ================================================ """ Use this module to get and run all tk tests. tkinter tests should live in a package inside the directory where this file lives, like test_tkinter. Extensions also should live in packages following the same rule as above. """ import os import importlib import test.support this_dir_path = os.path.abspath(os.path.dirname(__file__)) def is_package(path): for name in os.listdir(path): if name in ('__init__.py', '__init__.pyc'): return True return False def get_tests_modules(basepath=this_dir_path, gui=True, packages=None): """This will import and yield modules whose names start with test_ and are inside packages found in the path starting at basepath. If packages is specified it should contain package names that want their tests collected. """ py_ext = '.py' for dirpath, dirnames, filenames in os.walk(basepath): for dirname in list(dirnames): if dirname[0] == '.': dirnames.remove(dirname) if is_package(dirpath) and filenames: pkg_name = dirpath[len(basepath) + len(os.sep):].replace('/', '.') if packages and pkg_name not in packages: continue filenames = filter( lambda x: x.startswith('test_') and x.endswith(py_ext), filenames) for name in filenames: try: yield importlib.import_module( ".%s.%s" % (pkg_name, name[:-len(py_ext)]), "tkinter.test") except test.support.ResourceDenied: if gui: raise def get_tests(text=True, gui=True, packages=None): """Yield all the tests in the modules found by get_tests_modules. If nogui is True, only tests that do not require a GUI will be returned.""" attrs = [] if text: attrs.append('tests_nogui') if gui: attrs.append('tests_gui') for module in get_tests_modules(gui=gui, packages=packages): for attr in attrs: for test in getattr(module, attr, ()): yield test if __name__ == "__main__": test.support.run_unittest(*get_tests()) ================================================ FILE: modules/tkinter/test/support.py ================================================ import functools import re import tkinter import unittest class AbstractTkTest: @classmethod def setUpClass(cls): cls._old_support_default_root = tkinter._support_default_root destroy_default_root() tkinter.NoDefaultRoot() cls.root = tkinter.Tk() cls.wantobjects = cls.root.wantobjects() # De-maximize main window. # Some window managers can maximize new windows. cls.root.wm_state('normal') try: cls.root.wm_attributes('-zoomed', False) except tkinter.TclError: pass @classmethod def tearDownClass(cls): cls.root.update_idletasks() cls.root.destroy() del cls.root tkinter._default_root = None tkinter._support_default_root = cls._old_support_default_root def setUp(self): self.root.deiconify() def tearDown(self): for w in self.root.winfo_children(): w.destroy() self.root.withdraw() class AbstractDefaultRootTest: def setUp(self): self._old_support_default_root = tkinter._support_default_root destroy_default_root() tkinter._support_default_root = True self.wantobjects = tkinter.wantobjects def tearDown(self): destroy_default_root() tkinter._default_root = None tkinter._support_default_root = self._old_support_default_root def _test_widget(self, constructor): # no master passing x = constructor() self.assertIsNotNone(tkinter._default_root) self.assertIs(x.master, tkinter._default_root) self.assertIs(x.tk, tkinter._default_root.tk) x.destroy() destroy_default_root() tkinter.NoDefaultRoot() self.assertRaises(RuntimeError, constructor) self.assertFalse(hasattr(tkinter, '_default_root')) def destroy_default_root(): if getattr(tkinter, '_default_root', None): tkinter._default_root.update_idletasks() tkinter._default_root.destroy() tkinter._default_root = None def simulate_mouse_click(widget, x, y): """Generate proper events to click at the x, y position (tries to act like an X server).""" widget.event_generate('', x=0, y=0) widget.event_generate('', x=x, y=y) widget.event_generate('', x=x, y=y) widget.event_generate('', x=x, y=y) import _tkinter tcl_version = tuple(map(int, _tkinter.TCL_VERSION.split('.'))) def requires_tcl(*version): if len(version) <= 2: return unittest.skipUnless(tcl_version >= version, 'requires Tcl version >= ' + '.'.join(map(str, version))) def deco(test): @functools.wraps(test) def newtest(self): if get_tk_patchlevel() < version: self.skipTest('requires Tcl version >= ' + '.'.join(map(str, version))) test(self) return newtest return deco _tk_patchlevel = None def get_tk_patchlevel(): global _tk_patchlevel if _tk_patchlevel is None: tcl = tkinter.Tcl() patchlevel = tcl.call('info', 'patchlevel') m = re.fullmatch(r'(\d+)\.(\d+)([ab.])(\d+)', patchlevel) major, minor, releaselevel, serial = m.groups() major, minor, serial = int(major), int(minor), int(serial) releaselevel = {'a': 'alpha', 'b': 'beta', '.': 'final'}[releaselevel] if releaselevel == 'final': _tk_patchlevel = major, minor, serial, releaselevel, 0 else: _tk_patchlevel = major, minor, 0, releaselevel, serial return _tk_patchlevel units = { 'c': 72 / 2.54, # centimeters 'i': 72, # inches 'm': 72 / 25.4, # millimeters 'p': 1, # points } def pixels_conv(value): return float(value[:-1]) * units[value[-1:]] def tcl_obj_eq(actual, expected): if actual == expected: return True if isinstance(actual, _tkinter.Tcl_Obj): if isinstance(expected, str): return str(actual) == expected if isinstance(actual, tuple): if isinstance(expected, tuple): return (len(actual) == len(expected) and all(tcl_obj_eq(act, exp) for act, exp in zip(actual, expected))) return False def widget_eq(actual, expected): if actual == expected: return True if isinstance(actual, (str, tkinter.Widget)): if isinstance(expected, (str, tkinter.Widget)): return str(actual) == str(expected) return False ================================================ FILE: modules/tkinter/test/test_tkinter/test_colorchooser.py ================================================ import unittest import tkinter from test.support import requires, run_unittest, swap_attr from tkinter.test.support import AbstractTkTest from tkinter import colorchooser requires('gui') class ChooserTest(AbstractTkTest, unittest.TestCase): @classmethod def setUpClass(cls): AbstractTkTest.setUpClass.__func__(cls) cls.cc = colorchooser.Chooser(initialcolor='dark blue slate') def test_fixoptions(self): cc = self.cc cc._fixoptions() self.assertEqual(cc.options['initialcolor'], 'dark blue slate') cc.options['initialcolor'] = '#D2D269691E1E' cc._fixoptions() self.assertEqual(cc.options['initialcolor'], '#D2D269691E1E') cc.options['initialcolor'] = (210, 105, 30) cc._fixoptions() self.assertEqual(cc.options['initialcolor'], '#d2691e') def test_fixresult(self): cc = self.cc self.assertEqual(cc._fixresult(self.root, ()), (None, None)) self.assertEqual(cc._fixresult(self.root, ''), (None, None)) self.assertEqual(cc._fixresult(self.root, 'chocolate'), ((210, 105, 30), 'chocolate')) self.assertEqual(cc._fixresult(self.root, '#4a3c8c'), ((74, 60, 140), '#4a3c8c')) tests_gui = (ChooserTest,) if __name__ == "__main__": run_unittest(*tests_gui) ================================================ FILE: modules/tkinter/test/test_tkinter/test_font.py ================================================ import unittest import tkinter from tkinter import font from test.support import requires, run_unittest, gc_collect, ALWAYS_EQ from tkinter.test.support import AbstractTkTest, AbstractDefaultRootTest requires('gui') fontname = "TkDefaultFont" class FontTest(AbstractTkTest, unittest.TestCase): @classmethod def setUpClass(cls): AbstractTkTest.setUpClass.__func__(cls) try: cls.font = font.Font(root=cls.root, name=fontname, exists=True) except tkinter.TclError: cls.font = font.Font(root=cls.root, name=fontname, exists=False) def test_configure(self): options = self.font.configure() self.assertGreaterEqual(set(options), {'family', 'size', 'weight', 'slant', 'underline', 'overstrike'}) for key in options: self.assertEqual(self.font.cget(key), options[key]) self.assertEqual(self.font[key], options[key]) for key in 'family', 'weight', 'slant': self.assertIsInstance(options[key], str) self.assertIsInstance(self.font.cget(key), str) self.assertIsInstance(self.font[key], str) sizetype = int if self.wantobjects else str for key in 'size', 'underline', 'overstrike': self.assertIsInstance(options[key], sizetype) self.assertIsInstance(self.font.cget(key), sizetype) self.assertIsInstance(self.font[key], sizetype) def test_unicode_family(self): family = 'MS \u30b4\u30b7\u30c3\u30af' try: f = font.Font(root=self.root, family=family, exists=True) except tkinter.TclError: f = font.Font(root=self.root, family=family, exists=False) self.assertEqual(f.cget('family'), family) del f gc_collect() def test_actual(self): options = self.font.actual() self.assertGreaterEqual(set(options), {'family', 'size', 'weight', 'slant', 'underline', 'overstrike'}) for key in options: self.assertEqual(self.font.actual(key), options[key]) for key in 'family', 'weight', 'slant': self.assertIsInstance(options[key], str) self.assertIsInstance(self.font.actual(key), str) sizetype = int if self.wantobjects else str for key in 'size', 'underline', 'overstrike': self.assertIsInstance(options[key], sizetype) self.assertIsInstance(self.font.actual(key), sizetype) def test_name(self): self.assertEqual(self.font.name, fontname) self.assertEqual(str(self.font), fontname) def test_equality(self): font1 = font.Font(root=self.root, name=fontname, exists=True) font2 = font.Font(root=self.root, name=fontname, exists=True) self.assertIsNot(font1, font2) self.assertEqual(font1, font2) self.assertNotEqual(font1, font1.copy()) self.assertNotEqual(font1, 0) self.assertEqual(font1, ALWAYS_EQ) root2 = tkinter.Tk() self.addCleanup(root2.destroy) font3 = font.Font(root=root2, name=fontname, exists=True) self.assertEqual(str(font1), str(font3)) self.assertNotEqual(font1, font3) def test_measure(self): self.assertIsInstance(self.font.measure('abc'), int) def test_metrics(self): metrics = self.font.metrics() self.assertGreaterEqual(set(metrics), {'ascent', 'descent', 'linespace', 'fixed'}) for key in metrics: self.assertEqual(self.font.metrics(key), metrics[key]) self.assertIsInstance(metrics[key], int) self.assertIsInstance(self.font.metrics(key), int) def test_families(self): families = font.families(self.root) self.assertIsInstance(families, tuple) self.assertTrue(families) for family in families: self.assertIsInstance(family, str) self.assertTrue(family) def test_names(self): names = font.names(self.root) self.assertIsInstance(names, tuple) self.assertTrue(names) for name in names: self.assertIsInstance(name, str) self.assertTrue(name) self.assertIn(fontname, names) class DefaultRootTest(AbstractDefaultRootTest, unittest.TestCase): def test_families(self): self.assertRaises(RuntimeError, font.families) root = tkinter.Tk() families = font.families() self.assertIsInstance(families, tuple) self.assertTrue(families) for family in families: self.assertIsInstance(family, str) self.assertTrue(family) root.destroy() tkinter.NoDefaultRoot() self.assertRaises(RuntimeError, font.families) def test_names(self): self.assertRaises(RuntimeError, font.names) root = tkinter.Tk() names = font.names() self.assertIsInstance(names, tuple) self.assertTrue(names) for name in names: self.assertIsInstance(name, str) self.assertTrue(name) self.assertIn(fontname, names) root.destroy() tkinter.NoDefaultRoot() self.assertRaises(RuntimeError, font.names) tests_gui = (FontTest, DefaultRootTest) if __name__ == "__main__": run_unittest(*tests_gui) ================================================ FILE: modules/tkinter/test/test_tkinter/test_geometry_managers.py ================================================ import unittest import re import tkinter from tkinter import TclError from test.support import requires from tkinter.test.support import pixels_conv, tcl_version, requires_tcl from tkinter.test.widget_tests import AbstractWidgetTest requires('gui') class PackTest(AbstractWidgetTest, unittest.TestCase): test_keys = None def create2(self): pack = tkinter.Toplevel(self.root, name='pack') pack.wm_geometry('300x200+0+0') pack.wm_minsize(1, 1) a = tkinter.Frame(pack, name='a', width=20, height=40, bg='red') b = tkinter.Frame(pack, name='b', width=50, height=30, bg='blue') c = tkinter.Frame(pack, name='c', width=80, height=80, bg='green') d = tkinter.Frame(pack, name='d', width=40, height=30, bg='yellow') return pack, a, b, c, d def test_pack_configure_after(self): pack, a, b, c, d = self.create2() with self.assertRaisesRegex(TclError, 'window "%s" isn\'t packed' % b): a.pack_configure(after=b) with self.assertRaisesRegex(TclError, 'bad window path name ".foo"'): a.pack_configure(after='.foo') a.pack_configure(side='top') b.pack_configure(side='top') c.pack_configure(side='top') d.pack_configure(side='top') self.assertEqual(pack.pack_slaves(), [a, b, c, d]) a.pack_configure(after=b) self.assertEqual(pack.pack_slaves(), [b, a, c, d]) a.pack_configure(after=a) self.assertEqual(pack.pack_slaves(), [b, a, c, d]) def test_pack_configure_anchor(self): pack, a, b, c, d = self.create2() def check(anchor, geom): a.pack_configure(side='top', ipadx=5, padx=10, ipady=15, pady=20, expand=True, anchor=anchor) self.root.update() self.assertEqual(a.winfo_geometry(), geom) check('n', '30x70+135+20') check('ne', '30x70+260+20') check('e', '30x70+260+65') check('se', '30x70+260+110') check('s', '30x70+135+110') check('sw', '30x70+10+110') check('w', '30x70+10+65') check('nw', '30x70+10+20') check('center', '30x70+135+65') def test_pack_configure_before(self): pack, a, b, c, d = self.create2() with self.assertRaisesRegex(TclError, 'window "%s" isn\'t packed' % b): a.pack_configure(before=b) with self.assertRaisesRegex(TclError, 'bad window path name ".foo"'): a.pack_configure(before='.foo') a.pack_configure(side='top') b.pack_configure(side='top') c.pack_configure(side='top') d.pack_configure(side='top') self.assertEqual(pack.pack_slaves(), [a, b, c, d]) a.pack_configure(before=d) self.assertEqual(pack.pack_slaves(), [b, c, a, d]) a.pack_configure(before=a) self.assertEqual(pack.pack_slaves(), [b, c, a, d]) def test_pack_configure_expand(self): pack, a, b, c, d = self.create2() def check(*geoms): self.root.update() self.assertEqual(a.winfo_geometry(), geoms[0]) self.assertEqual(b.winfo_geometry(), geoms[1]) self.assertEqual(c.winfo_geometry(), geoms[2]) self.assertEqual(d.winfo_geometry(), geoms[3]) a.pack_configure(side='left') b.pack_configure(side='top') c.pack_configure(side='right') d.pack_configure(side='bottom') check('20x40+0+80', '50x30+135+0', '80x80+220+75', '40x30+100+170') a.pack_configure(side='left', expand='yes') b.pack_configure(side='top', expand='on') c.pack_configure(side='right', expand=True) d.pack_configure(side='bottom', expand=1) check('20x40+40+80', '50x30+175+35', '80x80+180+110', '40x30+100+135') a.pack_configure(side='left', expand='yes', fill='both') b.pack_configure(side='top', expand='on', fill='both') c.pack_configure(side='right', expand=True, fill='both') d.pack_configure(side='bottom', expand=1, fill='both') check('100x200+0+0', '200x100+100+0', '160x100+140+100', '40x100+100+100') def test_pack_configure_in(self): pack, a, b, c, d = self.create2() a.pack_configure(side='top') b.pack_configure(side='top') c.pack_configure(side='top') d.pack_configure(side='top') a.pack_configure(in_=pack) self.assertEqual(pack.pack_slaves(), [b, c, d, a]) a.pack_configure(in_=c) self.assertEqual(pack.pack_slaves(), [b, c, d]) self.assertEqual(c.pack_slaves(), [a]) with self.assertRaisesRegex(TclError, 'can\'t pack %s inside itself' % (a,)): a.pack_configure(in_=a) with self.assertRaisesRegex(TclError, 'bad window path name ".foo"'): a.pack_configure(in_='.foo') def test_pack_configure_padx_ipadx_fill(self): pack, a, b, c, d = self.create2() def check(geom1, geom2, **kwargs): a.pack_forget() b.pack_forget() a.pack_configure(**kwargs) b.pack_configure(expand=True, fill='both') self.root.update() self.assertEqual(a.winfo_geometry(), geom1) self.assertEqual(b.winfo_geometry(), geom2) check('20x40+260+80', '240x200+0+0', side='right', padx=20) check('20x40+250+80', '240x200+0+0', side='right', padx=(10, 30)) check('60x40+240+80', '240x200+0+0', side='right', ipadx=20) check('30x40+260+80', '250x200+0+0', side='right', ipadx=5, padx=10) check('20x40+260+80', '240x200+0+0', side='right', padx=20, fill='x') check('20x40+249+80', '240x200+0+0', side='right', padx=(9, 31), fill='x') check('60x40+240+80', '240x200+0+0', side='right', ipadx=20, fill='x') check('30x40+260+80', '250x200+0+0', side='right', ipadx=5, padx=10, fill='x') check('30x40+255+80', '250x200+0+0', side='right', ipadx=5, padx=(5, 15), fill='x') check('20x40+140+0', '300x160+0+40', side='top', padx=20) check('20x40+120+0', '300x160+0+40', side='top', padx=(0, 40)) check('60x40+120+0', '300x160+0+40', side='top', ipadx=20) check('30x40+135+0', '300x160+0+40', side='top', ipadx=5, padx=10) check('30x40+130+0', '300x160+0+40', side='top', ipadx=5, padx=(5, 15)) check('260x40+20+0', '300x160+0+40', side='top', padx=20, fill='x') check('260x40+25+0', '300x160+0+40', side='top', padx=(25, 15), fill='x') check('300x40+0+0', '300x160+0+40', side='top', ipadx=20, fill='x') check('280x40+10+0', '300x160+0+40', side='top', ipadx=5, padx=10, fill='x') check('280x40+5+0', '300x160+0+40', side='top', ipadx=5, padx=(5, 15), fill='x') a.pack_configure(padx='1c') self.assertEqual(a.pack_info()['padx'], self._str(pack.winfo_pixels('1c'))) a.pack_configure(ipadx='1c') self.assertEqual(a.pack_info()['ipadx'], self._str(pack.winfo_pixels('1c'))) def test_pack_configure_pady_ipady_fill(self): pack, a, b, c, d = self.create2() def check(geom1, geom2, **kwargs): a.pack_forget() b.pack_forget() a.pack_configure(**kwargs) b.pack_configure(expand=True, fill='both') self.root.update() self.assertEqual(a.winfo_geometry(), geom1) self.assertEqual(b.winfo_geometry(), geom2) check('20x40+280+80', '280x200+0+0', side='right', pady=20) check('20x40+280+70', '280x200+0+0', side='right', pady=(10, 30)) check('20x80+280+60', '280x200+0+0', side='right', ipady=20) check('20x50+280+75', '280x200+0+0', side='right', ipady=5, pady=10) check('20x40+280+80', '280x200+0+0', side='right', pady=20, fill='x') check('20x40+280+69', '280x200+0+0', side='right', pady=(9, 31), fill='x') check('20x80+280+60', '280x200+0+0', side='right', ipady=20, fill='x') check('20x50+280+75', '280x200+0+0', side='right', ipady=5, pady=10, fill='x') check('20x50+280+70', '280x200+0+0', side='right', ipady=5, pady=(5, 15), fill='x') check('20x40+140+20', '300x120+0+80', side='top', pady=20) check('20x40+140+0', '300x120+0+80', side='top', pady=(0, 40)) check('20x80+140+0', '300x120+0+80', side='top', ipady=20) check('20x50+140+10', '300x130+0+70', side='top', ipady=5, pady=10) check('20x50+140+5', '300x130+0+70', side='top', ipady=5, pady=(5, 15)) check('300x40+0+20', '300x120+0+80', side='top', pady=20, fill='x') check('300x40+0+25', '300x120+0+80', side='top', pady=(25, 15), fill='x') check('300x80+0+0', '300x120+0+80', side='top', ipady=20, fill='x') check('300x50+0+10', '300x130+0+70', side='top', ipady=5, pady=10, fill='x') check('300x50+0+5', '300x130+0+70', side='top', ipady=5, pady=(5, 15), fill='x') a.pack_configure(pady='1c') self.assertEqual(a.pack_info()['pady'], self._str(pack.winfo_pixels('1c'))) a.pack_configure(ipady='1c') self.assertEqual(a.pack_info()['ipady'], self._str(pack.winfo_pixels('1c'))) def test_pack_configure_side(self): pack, a, b, c, d = self.create2() def check(side, geom1, geom2): a.pack_configure(side=side) self.assertEqual(a.pack_info()['side'], side) b.pack_configure(expand=True, fill='both') self.root.update() self.assertEqual(a.winfo_geometry(), geom1) self.assertEqual(b.winfo_geometry(), geom2) check('top', '20x40+140+0', '300x160+0+40') check('bottom', '20x40+140+160', '300x160+0+0') check('left', '20x40+0+80', '280x200+20+0') check('right', '20x40+280+80', '280x200+0+0') def test_pack_forget(self): pack, a, b, c, d = self.create2() a.pack_configure() b.pack_configure() c.pack_configure() self.assertEqual(pack.pack_slaves(), [a, b, c]) b.pack_forget() self.assertEqual(pack.pack_slaves(), [a, c]) b.pack_forget() self.assertEqual(pack.pack_slaves(), [a, c]) d.pack_forget() def test_pack_info(self): pack, a, b, c, d = self.create2() with self.assertRaisesRegex(TclError, 'window "%s" isn\'t packed' % a): a.pack_info() a.pack_configure() b.pack_configure(side='right', in_=a, anchor='s', expand=True, fill='x', ipadx=5, padx=10, ipady=2, pady=(5, 15)) info = a.pack_info() self.assertIsInstance(info, dict) self.assertEqual(info['anchor'], 'center') self.assertEqual(info['expand'], self._str(0)) self.assertEqual(info['fill'], 'none') self.assertEqual(info['in'], pack) self.assertEqual(info['ipadx'], self._str(0)) self.assertEqual(info['ipady'], self._str(0)) self.assertEqual(info['padx'], self._str(0)) self.assertEqual(info['pady'], self._str(0)) self.assertEqual(info['side'], 'top') info = b.pack_info() self.assertIsInstance(info, dict) self.assertEqual(info['anchor'], 's') self.assertEqual(info['expand'], self._str(1)) self.assertEqual(info['fill'], 'x') self.assertEqual(info['in'], a) self.assertEqual(info['ipadx'], self._str(5)) self.assertEqual(info['ipady'], self._str(2)) self.assertEqual(info['padx'], self._str(10)) self.assertEqual(info['pady'], self._str((5, 15))) self.assertEqual(info['side'], 'right') def test_pack_propagate(self): pack, a, b, c, d = self.create2() pack.configure(width=300, height=200) a.pack_configure() pack.pack_propagate(False) self.root.update() self.assertEqual(pack.winfo_reqwidth(), 300) self.assertEqual(pack.winfo_reqheight(), 200) pack.pack_propagate(True) self.root.update() self.assertEqual(pack.winfo_reqwidth(), 20) self.assertEqual(pack.winfo_reqheight(), 40) def test_pack_slaves(self): pack, a, b, c, d = self.create2() self.assertEqual(pack.pack_slaves(), []) a.pack_configure() self.assertEqual(pack.pack_slaves(), [a]) b.pack_configure() self.assertEqual(pack.pack_slaves(), [a, b]) class PlaceTest(AbstractWidgetTest, unittest.TestCase): test_keys = None def create2(self): t = tkinter.Toplevel(self.root, width=300, height=200, bd=0) t.wm_geometry('300x200+0+0') f = tkinter.Frame(t, width=154, height=84, bd=2, relief='raised') f.place_configure(x=48, y=38) f2 = tkinter.Frame(t, width=30, height=60, bd=2, relief='raised') self.root.update() return t, f, f2 def test_place_configure_in(self): t, f, f2 = self.create2() self.assertEqual(f2.winfo_manager(), '') with self.assertRaisesRegex(TclError, "can't place %s relative to " "itself" % re.escape(str(f2))): f2.place_configure(in_=f2) if tcl_version >= (8, 5): self.assertEqual(f2.winfo_manager(), '') with self.assertRaisesRegex(TclError, 'bad window path name'): f2.place_configure(in_='spam') f2.place_configure(in_=f) self.assertEqual(f2.winfo_manager(), 'place') def test_place_configure_x(self): t, f, f2 = self.create2() f2.place_configure(in_=f) self.assertEqual(f2.place_info()['x'], '0') self.root.update() self.assertEqual(f2.winfo_x(), 50) f2.place_configure(x=100) self.assertEqual(f2.place_info()['x'], '100') self.root.update() self.assertEqual(f2.winfo_x(), 150) f2.place_configure(x=-10, relx=1) self.assertEqual(f2.place_info()['x'], '-10') self.root.update() self.assertEqual(f2.winfo_x(), 190) with self.assertRaisesRegex(TclError, 'bad screen distance "spam"'): f2.place_configure(in_=f, x='spam') def test_place_configure_y(self): t, f, f2 = self.create2() f2.place_configure(in_=f) self.assertEqual(f2.place_info()['y'], '0') self.root.update() self.assertEqual(f2.winfo_y(), 40) f2.place_configure(y=50) self.assertEqual(f2.place_info()['y'], '50') self.root.update() self.assertEqual(f2.winfo_y(), 90) f2.place_configure(y=-10, rely=1) self.assertEqual(f2.place_info()['y'], '-10') self.root.update() self.assertEqual(f2.winfo_y(), 110) with self.assertRaisesRegex(TclError, 'bad screen distance "spam"'): f2.place_configure(in_=f, y='spam') def test_place_configure_relx(self): t, f, f2 = self.create2() f2.place_configure(in_=f) self.assertEqual(f2.place_info()['relx'], '0') self.root.update() self.assertEqual(f2.winfo_x(), 50) f2.place_configure(relx=0.5) self.assertEqual(f2.place_info()['relx'], '0.5') self.root.update() self.assertEqual(f2.winfo_x(), 125) f2.place_configure(relx=1) self.assertEqual(f2.place_info()['relx'], '1') self.root.update() self.assertEqual(f2.winfo_x(), 200) with self.assertRaisesRegex(TclError, 'expected floating-point number ' 'but got "spam"'): f2.place_configure(in_=f, relx='spam') def test_place_configure_rely(self): t, f, f2 = self.create2() f2.place_configure(in_=f) self.assertEqual(f2.place_info()['rely'], '0') self.root.update() self.assertEqual(f2.winfo_y(), 40) f2.place_configure(rely=0.5) self.assertEqual(f2.place_info()['rely'], '0.5') self.root.update() self.assertEqual(f2.winfo_y(), 80) f2.place_configure(rely=1) self.assertEqual(f2.place_info()['rely'], '1') self.root.update() self.assertEqual(f2.winfo_y(), 120) with self.assertRaisesRegex(TclError, 'expected floating-point number ' 'but got "spam"'): f2.place_configure(in_=f, rely='spam') def test_place_configure_anchor(self): f = tkinter.Frame(self.root) with self.assertRaisesRegex(TclError, 'bad anchor "j"'): f.place_configure(anchor='j') with self.assertRaisesRegex(TclError, 'ambiguous anchor ""'): f.place_configure(anchor='') for value in 'n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw', 'center': f.place_configure(anchor=value) self.assertEqual(f.place_info()['anchor'], value) def test_place_configure_width(self): t, f, f2 = self.create2() f2.place_configure(in_=f, width=120) self.root.update() self.assertEqual(f2.winfo_width(), 120) f2.place_configure(width='') self.root.update() self.assertEqual(f2.winfo_width(), 30) with self.assertRaisesRegex(TclError, 'bad screen distance "abcd"'): f2.place_configure(width='abcd') def test_place_configure_height(self): t, f, f2 = self.create2() f2.place_configure(in_=f, height=120) self.root.update() self.assertEqual(f2.winfo_height(), 120) f2.place_configure(height='') self.root.update() self.assertEqual(f2.winfo_height(), 60) with self.assertRaisesRegex(TclError, 'bad screen distance "abcd"'): f2.place_configure(height='abcd') def test_place_configure_relwidth(self): t, f, f2 = self.create2() f2.place_configure(in_=f, relwidth=0.5) self.root.update() self.assertEqual(f2.winfo_width(), 75) f2.place_configure(relwidth='') self.root.update() self.assertEqual(f2.winfo_width(), 30) with self.assertRaisesRegex(TclError, 'expected floating-point number ' 'but got "abcd"'): f2.place_configure(relwidth='abcd') def test_place_configure_relheight(self): t, f, f2 = self.create2() f2.place_configure(in_=f, relheight=0.5) self.root.update() self.assertEqual(f2.winfo_height(), 40) f2.place_configure(relheight='') self.root.update() self.assertEqual(f2.winfo_height(), 60) with self.assertRaisesRegex(TclError, 'expected floating-point number ' 'but got "abcd"'): f2.place_configure(relheight='abcd') def test_place_configure_bordermode(self): f = tkinter.Frame(self.root) with self.assertRaisesRegex(TclError, 'bad bordermode "j"'): f.place_configure(bordermode='j') with self.assertRaisesRegex(TclError, 'ambiguous bordermode ""'): f.place_configure(bordermode='') for value in 'inside', 'outside', 'ignore': f.place_configure(bordermode=value) self.assertEqual(f.place_info()['bordermode'], value) def test_place_forget(self): foo = tkinter.Frame(self.root) foo.place_configure(width=50, height=50) self.root.update() foo.place_forget() self.root.update() self.assertFalse(foo.winfo_ismapped()) with self.assertRaises(TypeError): foo.place_forget(0) def test_place_info(self): t, f, f2 = self.create2() f2.place_configure(in_=f, x=1, y=2, width=3, height=4, relx=0.1, rely=0.2, relwidth=0.3, relheight=0.4, anchor='se', bordermode='outside') info = f2.place_info() self.assertIsInstance(info, dict) self.assertEqual(info['x'], '1') self.assertEqual(info['y'], '2') self.assertEqual(info['width'], '3') self.assertEqual(info['height'], '4') self.assertEqual(info['relx'], '0.1') self.assertEqual(info['rely'], '0.2') self.assertEqual(info['relwidth'], '0.3') self.assertEqual(info['relheight'], '0.4') self.assertEqual(info['anchor'], 'se') self.assertEqual(info['bordermode'], 'outside') self.assertEqual(info['x'], '1') self.assertEqual(info['x'], '1') with self.assertRaises(TypeError): f2.place_info(0) def test_place_slaves(self): foo = tkinter.Frame(self.root) bar = tkinter.Frame(self.root) self.assertEqual(foo.place_slaves(), []) bar.place_configure(in_=foo) self.assertEqual(foo.place_slaves(), [bar]) with self.assertRaises(TypeError): foo.place_slaves(0) class GridTest(AbstractWidgetTest, unittest.TestCase): test_keys = None def tearDown(self): cols, rows = self.root.grid_size() for i in range(cols + 1): self.root.grid_columnconfigure(i, weight=0, minsize=0, pad=0, uniform='') for i in range(rows + 1): self.root.grid_rowconfigure(i, weight=0, minsize=0, pad=0, uniform='') self.root.grid_propagate(1) if tcl_version >= (8, 5): self.root.grid_anchor('nw') super().tearDown() def test_grid_configure(self): b = tkinter.Button(self.root) self.assertEqual(b.grid_info(), {}) b.grid_configure() self.assertEqual(b.grid_info()['in'], self.root) self.assertEqual(b.grid_info()['column'], self._str(0)) self.assertEqual(b.grid_info()['row'], self._str(0)) b.grid_configure({'column': 1}, row=2) self.assertEqual(b.grid_info()['column'], self._str(1)) self.assertEqual(b.grid_info()['row'], self._str(2)) def test_grid_configure_column(self): b = tkinter.Button(self.root) with self.assertRaisesRegex(TclError, 'bad column value "-1": ' 'must be a non-negative integer'): b.grid_configure(column=-1) b.grid_configure(column=2) self.assertEqual(b.grid_info()['column'], self._str(2)) def test_grid_configure_columnspan(self): b = tkinter.Button(self.root) with self.assertRaisesRegex(TclError, 'bad columnspan value "0": ' 'must be a positive integer'): b.grid_configure(columnspan=0) b.grid_configure(columnspan=2) self.assertEqual(b.grid_info()['columnspan'], self._str(2)) def test_grid_configure_in(self): f = tkinter.Frame(self.root) b = tkinter.Button(self.root) self.assertEqual(b.grid_info(), {}) b.grid_configure() self.assertEqual(b.grid_info()['in'], self.root) b.grid_configure(in_=f) self.assertEqual(b.grid_info()['in'], f) b.grid_configure({'in': self.root}) self.assertEqual(b.grid_info()['in'], self.root) def test_grid_configure_ipadx(self): b = tkinter.Button(self.root) with self.assertRaisesRegex(TclError, 'bad ipadx value "-1": ' 'must be positive screen distance'): b.grid_configure(ipadx=-1) b.grid_configure(ipadx=1) self.assertEqual(b.grid_info()['ipadx'], self._str(1)) b.grid_configure(ipadx='.5c') self.assertEqual(b.grid_info()['ipadx'], self._str(round(pixels_conv('.5c') * self.scaling))) def test_grid_configure_ipady(self): b = tkinter.Button(self.root) with self.assertRaisesRegex(TclError, 'bad ipady value "-1": ' 'must be positive screen distance'): b.grid_configure(ipady=-1) b.grid_configure(ipady=1) self.assertEqual(b.grid_info()['ipady'], self._str(1)) b.grid_configure(ipady='.5c') self.assertEqual(b.grid_info()['ipady'], self._str(round(pixels_conv('.5c') * self.scaling))) def test_grid_configure_padx(self): b = tkinter.Button(self.root) with self.assertRaisesRegex(TclError, 'bad pad value "-1": ' 'must be positive screen distance'): b.grid_configure(padx=-1) b.grid_configure(padx=1) self.assertEqual(b.grid_info()['padx'], self._str(1)) b.grid_configure(padx=(10, 5)) self.assertEqual(b.grid_info()['padx'], self._str((10, 5))) b.grid_configure(padx='.5c') self.assertEqual(b.grid_info()['padx'], self._str(round(pixels_conv('.5c') * self.scaling))) def test_grid_configure_pady(self): b = tkinter.Button(self.root) with self.assertRaisesRegex(TclError, 'bad pad value "-1": ' 'must be positive screen distance'): b.grid_configure(pady=-1) b.grid_configure(pady=1) self.assertEqual(b.grid_info()['pady'], self._str(1)) b.grid_configure(pady=(10, 5)) self.assertEqual(b.grid_info()['pady'], self._str((10, 5))) b.grid_configure(pady='.5c') self.assertEqual(b.grid_info()['pady'], self._str(round(pixels_conv('.5c') * self.scaling))) def test_grid_configure_row(self): b = tkinter.Button(self.root) with self.assertRaisesRegex(TclError, 'bad (row|grid) value "-1": ' 'must be a non-negative integer'): b.grid_configure(row=-1) b.grid_configure(row=2) self.assertEqual(b.grid_info()['row'], self._str(2)) def test_grid_configure_rownspan(self): b = tkinter.Button(self.root) with self.assertRaisesRegex(TclError, 'bad rowspan value "0": ' 'must be a positive integer'): b.grid_configure(rowspan=0) b.grid_configure(rowspan=2) self.assertEqual(b.grid_info()['rowspan'], self._str(2)) def test_grid_configure_sticky(self): f = tkinter.Frame(self.root, bg='red') with self.assertRaisesRegex(TclError, 'bad stickyness value "glue"'): f.grid_configure(sticky='glue') f.grid_configure(sticky='ne') self.assertEqual(f.grid_info()['sticky'], 'ne') f.grid_configure(sticky='n,s,e,w') self.assertEqual(f.grid_info()['sticky'], 'nesw') def test_grid_columnconfigure(self): with self.assertRaises(TypeError): self.root.grid_columnconfigure() self.assertEqual(self.root.grid_columnconfigure(0), {'minsize': 0, 'pad': 0, 'uniform': None, 'weight': 0}) with self.assertRaisesRegex(TclError, 'bad option "-foo"'): self.root.grid_columnconfigure(0, 'foo') self.root.grid_columnconfigure((0, 3), weight=2) with self.assertRaisesRegex(TclError, 'must specify a single element on retrieval'): self.root.grid_columnconfigure((0, 3)) b = tkinter.Button(self.root) b.grid_configure(column=0, row=0) if tcl_version >= (8, 5): self.root.grid_columnconfigure('all', weight=3) with self.assertRaisesRegex(TclError, 'expected integer but got "all"'): self.root.grid_columnconfigure('all') self.assertEqual(self.root.grid_columnconfigure(0, 'weight'), 3) self.assertEqual(self.root.grid_columnconfigure(3, 'weight'), 2) self.assertEqual(self.root.grid_columnconfigure(265, 'weight'), 0) if tcl_version >= (8, 5): self.root.grid_columnconfigure(b, weight=4) self.assertEqual(self.root.grid_columnconfigure(0, 'weight'), 4) def test_grid_columnconfigure_minsize(self): with self.assertRaisesRegex(TclError, 'bad screen distance "foo"'): self.root.grid_columnconfigure(0, minsize='foo') self.root.grid_columnconfigure(0, minsize=10) self.assertEqual(self.root.grid_columnconfigure(0, 'minsize'), 10) self.assertEqual(self.root.grid_columnconfigure(0)['minsize'], 10) def test_grid_columnconfigure_weight(self): with self.assertRaisesRegex(TclError, 'expected integer but got "bad"'): self.root.grid_columnconfigure(0, weight='bad') with self.assertRaisesRegex(TclError, 'invalid arg "-weight": ' 'should be non-negative'): self.root.grid_columnconfigure(0, weight=-3) self.root.grid_columnconfigure(0, weight=3) self.assertEqual(self.root.grid_columnconfigure(0, 'weight'), 3) self.assertEqual(self.root.grid_columnconfigure(0)['weight'], 3) def test_grid_columnconfigure_pad(self): with self.assertRaisesRegex(TclError, 'bad screen distance "foo"'): self.root.grid_columnconfigure(0, pad='foo') with self.assertRaisesRegex(TclError, 'invalid arg "-pad": ' 'should be non-negative'): self.root.grid_columnconfigure(0, pad=-3) self.root.grid_columnconfigure(0, pad=3) self.assertEqual(self.root.grid_columnconfigure(0, 'pad'), 3) self.assertEqual(self.root.grid_columnconfigure(0)['pad'], 3) def test_grid_columnconfigure_uniform(self): self.root.grid_columnconfigure(0, uniform='foo') self.assertEqual(self.root.grid_columnconfigure(0, 'uniform'), 'foo') self.assertEqual(self.root.grid_columnconfigure(0)['uniform'], 'foo') def test_grid_rowconfigure(self): with self.assertRaises(TypeError): self.root.grid_rowconfigure() self.assertEqual(self.root.grid_rowconfigure(0), {'minsize': 0, 'pad': 0, 'uniform': None, 'weight': 0}) with self.assertRaisesRegex(TclError, 'bad option "-foo"'): self.root.grid_rowconfigure(0, 'foo') self.root.grid_rowconfigure((0, 3), weight=2) with self.assertRaisesRegex(TclError, 'must specify a single element on retrieval'): self.root.grid_rowconfigure((0, 3)) b = tkinter.Button(self.root) b.grid_configure(column=0, row=0) if tcl_version >= (8, 5): self.root.grid_rowconfigure('all', weight=3) with self.assertRaisesRegex(TclError, 'expected integer but got "all"'): self.root.grid_rowconfigure('all') self.assertEqual(self.root.grid_rowconfigure(0, 'weight'), 3) self.assertEqual(self.root.grid_rowconfigure(3, 'weight'), 2) self.assertEqual(self.root.grid_rowconfigure(265, 'weight'), 0) if tcl_version >= (8, 5): self.root.grid_rowconfigure(b, weight=4) self.assertEqual(self.root.grid_rowconfigure(0, 'weight'), 4) def test_grid_rowconfigure_minsize(self): with self.assertRaisesRegex(TclError, 'bad screen distance "foo"'): self.root.grid_rowconfigure(0, minsize='foo') self.root.grid_rowconfigure(0, minsize=10) self.assertEqual(self.root.grid_rowconfigure(0, 'minsize'), 10) self.assertEqual(self.root.grid_rowconfigure(0)['minsize'], 10) def test_grid_rowconfigure_weight(self): with self.assertRaisesRegex(TclError, 'expected integer but got "bad"'): self.root.grid_rowconfigure(0, weight='bad') with self.assertRaisesRegex(TclError, 'invalid arg "-weight": ' 'should be non-negative'): self.root.grid_rowconfigure(0, weight=-3) self.root.grid_rowconfigure(0, weight=3) self.assertEqual(self.root.grid_rowconfigure(0, 'weight'), 3) self.assertEqual(self.root.grid_rowconfigure(0)['weight'], 3) def test_grid_rowconfigure_pad(self): with self.assertRaisesRegex(TclError, 'bad screen distance "foo"'): self.root.grid_rowconfigure(0, pad='foo') with self.assertRaisesRegex(TclError, 'invalid arg "-pad": ' 'should be non-negative'): self.root.grid_rowconfigure(0, pad=-3) self.root.grid_rowconfigure(0, pad=3) self.assertEqual(self.root.grid_rowconfigure(0, 'pad'), 3) self.assertEqual(self.root.grid_rowconfigure(0)['pad'], 3) def test_grid_rowconfigure_uniform(self): self.root.grid_rowconfigure(0, uniform='foo') self.assertEqual(self.root.grid_rowconfigure(0, 'uniform'), 'foo') self.assertEqual(self.root.grid_rowconfigure(0)['uniform'], 'foo') def test_grid_forget(self): b = tkinter.Button(self.root) c = tkinter.Button(self.root) b.grid_configure(row=2, column=2, rowspan=2, columnspan=2, padx=3, pady=4, sticky='ns') self.assertEqual(self.root.grid_slaves(), [b]) b.grid_forget() c.grid_forget() self.assertEqual(self.root.grid_slaves(), []) self.assertEqual(b.grid_info(), {}) b.grid_configure(row=0, column=0) info = b.grid_info() self.assertEqual(info['row'], self._str(0)) self.assertEqual(info['column'], self._str(0)) self.assertEqual(info['rowspan'], self._str(1)) self.assertEqual(info['columnspan'], self._str(1)) self.assertEqual(info['padx'], self._str(0)) self.assertEqual(info['pady'], self._str(0)) self.assertEqual(info['sticky'], '') def test_grid_remove(self): b = tkinter.Button(self.root) c = tkinter.Button(self.root) b.grid_configure(row=2, column=2, rowspan=2, columnspan=2, padx=3, pady=4, sticky='ns') self.assertEqual(self.root.grid_slaves(), [b]) b.grid_remove() c.grid_remove() self.assertEqual(self.root.grid_slaves(), []) self.assertEqual(b.grid_info(), {}) b.grid_configure(row=0, column=0) info = b.grid_info() self.assertEqual(info['row'], self._str(0)) self.assertEqual(info['column'], self._str(0)) self.assertEqual(info['rowspan'], self._str(2)) self.assertEqual(info['columnspan'], self._str(2)) self.assertEqual(info['padx'], self._str(3)) self.assertEqual(info['pady'], self._str(4)) self.assertEqual(info['sticky'], 'ns') def test_grid_info(self): b = tkinter.Button(self.root) self.assertEqual(b.grid_info(), {}) b.grid_configure(row=2, column=2, rowspan=2, columnspan=2, padx=3, pady=4, sticky='ns') info = b.grid_info() self.assertIsInstance(info, dict) self.assertEqual(info['in'], self.root) self.assertEqual(info['row'], self._str(2)) self.assertEqual(info['column'], self._str(2)) self.assertEqual(info['rowspan'], self._str(2)) self.assertEqual(info['columnspan'], self._str(2)) self.assertEqual(info['padx'], self._str(3)) self.assertEqual(info['pady'], self._str(4)) self.assertEqual(info['sticky'], 'ns') @requires_tcl(8, 5) def test_grid_anchor(self): with self.assertRaisesRegex(TclError, 'bad anchor "x"'): self.root.grid_anchor('x') with self.assertRaisesRegex(TclError, 'ambiguous anchor ""'): self.root.grid_anchor('') with self.assertRaises(TypeError): self.root.grid_anchor('se', 'nw') self.root.grid_anchor('se') self.assertEqual(self.root.tk.call('grid', 'anchor', self.root), 'se') def test_grid_bbox(self): self.assertEqual(self.root.grid_bbox(), (0, 0, 0, 0)) self.assertEqual(self.root.grid_bbox(0, 0), (0, 0, 0, 0)) self.assertEqual(self.root.grid_bbox(0, 0, 1, 1), (0, 0, 0, 0)) with self.assertRaisesRegex(TclError, 'expected integer but got "x"'): self.root.grid_bbox('x', 0) with self.assertRaisesRegex(TclError, 'expected integer but got "x"'): self.root.grid_bbox(0, 'x') with self.assertRaisesRegex(TclError, 'expected integer but got "x"'): self.root.grid_bbox(0, 0, 'x', 0) with self.assertRaisesRegex(TclError, 'expected integer but got "x"'): self.root.grid_bbox(0, 0, 0, 'x') with self.assertRaises(TypeError): self.root.grid_bbox(0, 0, 0, 0, 0) t = self.root # de-maximize t.wm_geometry('1x1+0+0') t.wm_geometry('') f1 = tkinter.Frame(t, width=75, height=75, bg='red') f2 = tkinter.Frame(t, width=90, height=90, bg='blue') f1.grid_configure(row=0, column=0) f2.grid_configure(row=1, column=1) self.root.update() self.assertEqual(t.grid_bbox(), (0, 0, 165, 165)) self.assertEqual(t.grid_bbox(0, 0), (0, 0, 75, 75)) self.assertEqual(t.grid_bbox(0, 0, 1, 1), (0, 0, 165, 165)) self.assertEqual(t.grid_bbox(1, 1), (75, 75, 90, 90)) self.assertEqual(t.grid_bbox(10, 10, 0, 0), (0, 0, 165, 165)) self.assertEqual(t.grid_bbox(-2, -2, -1, -1), (0, 0, 0, 0)) self.assertEqual(t.grid_bbox(10, 10, 12, 12), (165, 165, 0, 0)) def test_grid_location(self): with self.assertRaises(TypeError): self.root.grid_location() with self.assertRaises(TypeError): self.root.grid_location(0) with self.assertRaises(TypeError): self.root.grid_location(0, 0, 0) with self.assertRaisesRegex(TclError, 'bad screen distance "x"'): self.root.grid_location('x', 'y') with self.assertRaisesRegex(TclError, 'bad screen distance "y"'): self.root.grid_location('1c', 'y') t = self.root # de-maximize t.wm_geometry('1x1+0+0') t.wm_geometry('') f = tkinter.Frame(t, width=200, height=100, highlightthickness=0, bg='red') self.assertEqual(f.grid_location(10, 10), (-1, -1)) f.grid_configure() self.root.update() self.assertEqual(t.grid_location(-10, -10), (-1, -1)) self.assertEqual(t.grid_location(-10, 0), (-1, 0)) self.assertEqual(t.grid_location(-1, 0), (-1, 0)) self.assertEqual(t.grid_location(0, -10), (0, -1)) self.assertEqual(t.grid_location(0, -1), (0, -1)) self.assertEqual(t.grid_location(0, 0), (0, 0)) self.assertEqual(t.grid_location(200, 0), (0, 0)) self.assertEqual(t.grid_location(201, 0), (1, 0)) self.assertEqual(t.grid_location(0, 100), (0, 0)) self.assertEqual(t.grid_location(0, 101), (0, 1)) self.assertEqual(t.grid_location(201, 101), (1, 1)) def test_grid_propagate(self): self.assertEqual(self.root.grid_propagate(), True) with self.assertRaises(TypeError): self.root.grid_propagate(False, False) self.root.grid_propagate(False) self.assertFalse(self.root.grid_propagate()) f = tkinter.Frame(self.root, width=100, height=100, bg='red') f.grid_configure(row=0, column=0) self.root.update() self.assertEqual(f.winfo_width(), 100) self.assertEqual(f.winfo_height(), 100) f.grid_propagate(False) g = tkinter.Frame(self.root, width=75, height=85, bg='green') g.grid_configure(in_=f, row=0, column=0) self.root.update() self.assertEqual(f.winfo_width(), 100) self.assertEqual(f.winfo_height(), 100) f.grid_propagate(True) self.root.update() self.assertEqual(f.winfo_width(), 75) self.assertEqual(f.winfo_height(), 85) def test_grid_size(self): with self.assertRaises(TypeError): self.root.grid_size(0) self.assertEqual(self.root.grid_size(), (0, 0)) f = tkinter.Scale(self.root) f.grid_configure(row=0, column=0) self.assertEqual(self.root.grid_size(), (1, 1)) f.grid_configure(row=4, column=5) self.assertEqual(self.root.grid_size(), (6, 5)) def test_grid_slaves(self): self.assertEqual(self.root.grid_slaves(), []) a = tkinter.Label(self.root) a.grid_configure(row=0, column=1) b = tkinter.Label(self.root) b.grid_configure(row=1, column=0) c = tkinter.Label(self.root) c.grid_configure(row=1, column=1) d = tkinter.Label(self.root) d.grid_configure(row=1, column=1) self.assertEqual(self.root.grid_slaves(), [d, c, b, a]) self.assertEqual(self.root.grid_slaves(row=0), [a]) self.assertEqual(self.root.grid_slaves(row=1), [d, c, b]) self.assertEqual(self.root.grid_slaves(column=0), [b]) self.assertEqual(self.root.grid_slaves(column=1), [d, c, a]) self.assertEqual(self.root.grid_slaves(row=1, column=1), [d, c]) tests_gui = ( PackTest, PlaceTest, GridTest, ) if __name__ == '__main__': unittest.main() ================================================ FILE: modules/tkinter/test/test_tkinter/test_images.py ================================================ import unittest import tkinter from test import support from tkinter.test.support import AbstractTkTest, AbstractDefaultRootTest, requires_tcl support.requires('gui') class MiscTest(AbstractTkTest, unittest.TestCase): def test_image_types(self): image_types = self.root.image_types() self.assertIsInstance(image_types, tuple) self.assertIn('photo', image_types) self.assertIn('bitmap', image_types) def test_image_names(self): image_names = self.root.image_names() self.assertIsInstance(image_names, tuple) class DefaultRootTest(AbstractDefaultRootTest, unittest.TestCase): def test_image_types(self): self.assertRaises(RuntimeError, tkinter.image_types) root = tkinter.Tk() image_types = tkinter.image_types() self.assertIsInstance(image_types, tuple) self.assertIn('photo', image_types) self.assertIn('bitmap', image_types) root.destroy() tkinter.NoDefaultRoot() self.assertRaises(RuntimeError, tkinter.image_types) def test_image_names(self): self.assertRaises(RuntimeError, tkinter.image_names) root = tkinter.Tk() image_names = tkinter.image_names() self.assertIsInstance(image_names, tuple) root.destroy() tkinter.NoDefaultRoot() self.assertRaises(RuntimeError, tkinter.image_names) def test_image_create_bitmap(self): self.assertRaises(RuntimeError, tkinter.BitmapImage) root = tkinter.Tk() image = tkinter.BitmapImage() self.assertIn(image.name, tkinter.image_names()) root.destroy() tkinter.NoDefaultRoot() self.assertRaises(RuntimeError, tkinter.BitmapImage) def test_image_create_photo(self): self.assertRaises(RuntimeError, tkinter.PhotoImage) root = tkinter.Tk() image = tkinter.PhotoImage() self.assertIn(image.name, tkinter.image_names()) root.destroy() tkinter.NoDefaultRoot() self.assertRaises(RuntimeError, tkinter.PhotoImage) class BitmapImageTest(AbstractTkTest, unittest.TestCase): @classmethod def setUpClass(cls): AbstractTkTest.setUpClass.__func__(cls) cls.testfile = support.findfile('python.xbm', subdir='imghdrdata') def test_create_from_file(self): image = tkinter.BitmapImage('::img::test', master=self.root, foreground='yellow', background='blue', file=self.testfile) self.assertEqual(str(image), '::img::test') self.assertEqual(image.type(), 'bitmap') self.assertEqual(image.width(), 16) self.assertEqual(image.height(), 16) self.assertIn('::img::test', self.root.image_names()) del image support.gc_collect() # For PyPy or other GCs. self.assertNotIn('::img::test', self.root.image_names()) def test_create_from_data(self): with open(self.testfile, 'rb') as f: data = f.read() image = tkinter.BitmapImage('::img::test', master=self.root, foreground='yellow', background='blue', data=data) self.assertEqual(str(image), '::img::test') self.assertEqual(image.type(), 'bitmap') self.assertEqual(image.width(), 16) self.assertEqual(image.height(), 16) self.assertIn('::img::test', self.root.image_names()) del image support.gc_collect() # For PyPy or other GCs. self.assertNotIn('::img::test', self.root.image_names()) def assertEqualStrList(self, actual, expected): self.assertIsInstance(actual, str) self.assertEqual(self.root.splitlist(actual), expected) def test_configure_data(self): image = tkinter.BitmapImage('::img::test', master=self.root) self.assertEqual(image['data'], '-data {} {} {} {}') with open(self.testfile, 'rb') as f: data = f.read() image.configure(data=data) self.assertEqualStrList(image['data'], ('-data', '', '', '', data.decode('ascii'))) self.assertEqual(image.width(), 16) self.assertEqual(image.height(), 16) self.assertEqual(image['maskdata'], '-maskdata {} {} {} {}') image.configure(maskdata=data) self.assertEqualStrList(image['maskdata'], ('-maskdata', '', '', '', data.decode('ascii'))) def test_configure_file(self): image = tkinter.BitmapImage('::img::test', master=self.root) self.assertEqual(image['file'], '-file {} {} {} {}') image.configure(file=self.testfile) self.assertEqualStrList(image['file'], ('-file', '', '', '',self.testfile)) self.assertEqual(image.width(), 16) self.assertEqual(image.height(), 16) self.assertEqual(image['maskfile'], '-maskfile {} {} {} {}') image.configure(maskfile=self.testfile) self.assertEqualStrList(image['maskfile'], ('-maskfile', '', '', '', self.testfile)) def test_configure_background(self): image = tkinter.BitmapImage('::img::test', master=self.root) self.assertEqual(image['background'], '-background {} {} {} {}') image.configure(background='blue') self.assertEqual(image['background'], '-background {} {} {} blue') def test_configure_foreground(self): image = tkinter.BitmapImage('::img::test', master=self.root) self.assertEqual(image['foreground'], '-foreground {} {} #000000 #000000') image.configure(foreground='yellow') self.assertEqual(image['foreground'], '-foreground {} {} #000000 yellow') class PhotoImageTest(AbstractTkTest, unittest.TestCase): @classmethod def setUpClass(cls): AbstractTkTest.setUpClass.__func__(cls) cls.testfile = support.findfile('python.gif', subdir='imghdrdata') def create(self): return tkinter.PhotoImage('::img::test', master=self.root, file=self.testfile) def colorlist(self, *args): if tkinter.TkVersion >= 8.6 and self.wantobjects: return args else: return tkinter._join(args) def check_create_from_file(self, ext): testfile = support.findfile('python.' + ext, subdir='imghdrdata') image = tkinter.PhotoImage('::img::test', master=self.root, file=testfile) self.assertEqual(str(image), '::img::test') self.assertEqual(image.type(), 'photo') self.assertEqual(image.width(), 16) self.assertEqual(image.height(), 16) self.assertEqual(image['data'], '') self.assertEqual(image['file'], testfile) self.assertIn('::img::test', self.root.image_names()) del image support.gc_collect() # For PyPy or other GCs. self.assertNotIn('::img::test', self.root.image_names()) def check_create_from_data(self, ext): testfile = support.findfile('python.' + ext, subdir='imghdrdata') with open(testfile, 'rb') as f: data = f.read() image = tkinter.PhotoImage('::img::test', master=self.root, data=data) self.assertEqual(str(image), '::img::test') self.assertEqual(image.type(), 'photo') self.assertEqual(image.width(), 16) self.assertEqual(image.height(), 16) self.assertEqual(image['data'], data if self.wantobjects else data.decode('latin1')) self.assertEqual(image['file'], '') self.assertIn('::img::test', self.root.image_names()) del image support.gc_collect() # For PyPy or other GCs. self.assertNotIn('::img::test', self.root.image_names()) def test_create_from_ppm_file(self): self.check_create_from_file('ppm') def test_create_from_ppm_data(self): self.check_create_from_data('ppm') def test_create_from_pgm_file(self): self.check_create_from_file('pgm') def test_create_from_pgm_data(self): self.check_create_from_data('pgm') def test_create_from_gif_file(self): self.check_create_from_file('gif') def test_create_from_gif_data(self): self.check_create_from_data('gif') @requires_tcl(8, 6) def test_create_from_png_file(self): self.check_create_from_file('png') @requires_tcl(8, 6) def test_create_from_png_data(self): self.check_create_from_data('png') def test_configure_data(self): image = tkinter.PhotoImage('::img::test', master=self.root) self.assertEqual(image['data'], '') with open(self.testfile, 'rb') as f: data = f.read() image.configure(data=data) self.assertEqual(image['data'], data if self.wantobjects else data.decode('latin1')) self.assertEqual(image.width(), 16) self.assertEqual(image.height(), 16) def test_configure_format(self): image = tkinter.PhotoImage('::img::test', master=self.root) self.assertEqual(image['format'], '') image.configure(file=self.testfile, format='gif') self.assertEqual(image['format'], ('gif',) if self.wantobjects else 'gif') self.assertEqual(image.width(), 16) self.assertEqual(image.height(), 16) def test_configure_file(self): image = tkinter.PhotoImage('::img::test', master=self.root) self.assertEqual(image['file'], '') image.configure(file=self.testfile) self.assertEqual(image['file'], self.testfile) self.assertEqual(image.width(), 16) self.assertEqual(image.height(), 16) def test_configure_gamma(self): image = tkinter.PhotoImage('::img::test', master=self.root) self.assertEqual(image['gamma'], '1.0') image.configure(gamma=2.0) self.assertEqual(image['gamma'], '2.0') def test_configure_width_height(self): image = tkinter.PhotoImage('::img::test', master=self.root) self.assertEqual(image['width'], '0') self.assertEqual(image['height'], '0') image.configure(width=20) image.configure(height=10) self.assertEqual(image['width'], '20') self.assertEqual(image['height'], '10') self.assertEqual(image.width(), 20) self.assertEqual(image.height(), 10) def test_configure_palette(self): image = tkinter.PhotoImage('::img::test', master=self.root) self.assertEqual(image['palette'], '') image.configure(palette=256) self.assertEqual(image['palette'], '256') image.configure(palette='3/4/2') self.assertEqual(image['palette'], '3/4/2') def test_blank(self): image = self.create() image.blank() self.assertEqual(image.width(), 16) self.assertEqual(image.height(), 16) self.assertEqual(image.get(4, 6), self.colorlist(0, 0, 0)) def test_copy(self): image = self.create() image2 = image.copy() self.assertEqual(image2.width(), 16) self.assertEqual(image2.height(), 16) self.assertEqual(image.get(4, 6), image.get(4, 6)) def test_subsample(self): image = self.create() image2 = image.subsample(2, 3) self.assertEqual(image2.width(), 8) self.assertEqual(image2.height(), 6) self.assertEqual(image2.get(2, 2), image.get(4, 6)) image2 = image.subsample(2) self.assertEqual(image2.width(), 8) self.assertEqual(image2.height(), 8) self.assertEqual(image2.get(2, 3), image.get(4, 6)) def test_zoom(self): image = self.create() image2 = image.zoom(2, 3) self.assertEqual(image2.width(), 32) self.assertEqual(image2.height(), 48) self.assertEqual(image2.get(8, 18), image.get(4, 6)) self.assertEqual(image2.get(9, 20), image.get(4, 6)) image2 = image.zoom(2) self.assertEqual(image2.width(), 32) self.assertEqual(image2.height(), 32) self.assertEqual(image2.get(8, 12), image.get(4, 6)) self.assertEqual(image2.get(9, 13), image.get(4, 6)) def test_put(self): image = self.create() image.put('{red green} {blue yellow}', to=(4, 6)) self.assertEqual(image.get(4, 6), self.colorlist(255, 0, 0)) self.assertEqual(image.get(5, 6), self.colorlist(0, 128 if tkinter.TkVersion >= 8.6 else 255, 0)) self.assertEqual(image.get(4, 7), self.colorlist(0, 0, 255)) self.assertEqual(image.get(5, 7), self.colorlist(255, 255, 0)) image.put((('#f00', '#00ff00'), ('#000000fff', '#ffffffff0000'))) self.assertEqual(image.get(0, 0), self.colorlist(255, 0, 0)) self.assertEqual(image.get(1, 0), self.colorlist(0, 255, 0)) self.assertEqual(image.get(0, 1), self.colorlist(0, 0, 255)) self.assertEqual(image.get(1, 1), self.colorlist(255, 255, 0)) def test_get(self): image = self.create() self.assertEqual(image.get(4, 6), self.colorlist(62, 116, 162)) self.assertEqual(image.get(0, 0), self.colorlist(0, 0, 0)) self.assertEqual(image.get(15, 15), self.colorlist(0, 0, 0)) self.assertRaises(tkinter.TclError, image.get, -1, 0) self.assertRaises(tkinter.TclError, image.get, 0, -1) self.assertRaises(tkinter.TclError, image.get, 16, 15) self.assertRaises(tkinter.TclError, image.get, 15, 16) def test_write(self): image = self.create() self.addCleanup(support.unlink, support.TESTFN) image.write(support.TESTFN) image2 = tkinter.PhotoImage('::img::test2', master=self.root, format='ppm', file=support.TESTFN) self.assertEqual(str(image2), '::img::test2') self.assertEqual(image2.type(), 'photo') self.assertEqual(image2.width(), 16) self.assertEqual(image2.height(), 16) self.assertEqual(image2.get(0, 0), image.get(0, 0)) self.assertEqual(image2.get(15, 8), image.get(15, 8)) image.write(support.TESTFN, format='gif', from_coords=(4, 6, 6, 9)) image3 = tkinter.PhotoImage('::img::test3', master=self.root, format='gif', file=support.TESTFN) self.assertEqual(str(image3), '::img::test3') self.assertEqual(image3.type(), 'photo') self.assertEqual(image3.width(), 2) self.assertEqual(image3.height(), 3) self.assertEqual(image3.get(0, 0), image.get(4, 6)) self.assertEqual(image3.get(1, 2), image.get(5, 8)) def test_transparency(self): image = self.create() self.assertEqual(image.transparency_get(0, 0), True) self.assertEqual(image.transparency_get(4, 6), False) image.transparency_set(4, 6, True) self.assertEqual(image.transparency_get(4, 6), True) image.transparency_set(4, 6, False) self.assertEqual(image.transparency_get(4, 6), False) tests_gui = (MiscTest, DefaultRootTest, BitmapImageTest, PhotoImageTest,) if __name__ == "__main__": support.run_unittest(*tests_gui) ================================================ FILE: modules/tkinter/test/test_tkinter/test_loadtk.py ================================================ import os import sys import unittest import test.support as test_support from tkinter import Tcl, TclError test_support.requires('gui') class TkLoadTest(unittest.TestCase): @unittest.skipIf('DISPLAY' not in os.environ, 'No $DISPLAY set.') def testLoadTk(self): tcl = Tcl() self.assertRaises(TclError,tcl.winfo_geometry) tcl.loadtk() self.assertEqual('1x1+0+0', tcl.winfo_geometry()) tcl.destroy() def testLoadTkFailure(self): old_display = None if sys.platform.startswith(('win', 'darwin', 'cygwin')): # no failure possible on windows? # XXX Maybe on tk older than 8.4.13 it would be possible, # see tkinter.h. return with test_support.EnvironmentVarGuard() as env: if 'DISPLAY' in os.environ: del env['DISPLAY'] # on some platforms, deleting environment variables # doesn't actually carry through to the process level # because they don't support unsetenv # If that's the case, abort. with os.popen('echo $DISPLAY') as pipe: display = pipe.read().strip() if display: return tcl = Tcl() self.assertRaises(TclError, tcl.winfo_geometry) self.assertRaises(TclError, tcl.loadtk) tests_gui = (TkLoadTest, ) if __name__ == "__main__": test_support.run_unittest(*tests_gui) ================================================ FILE: modules/tkinter/test/test_tkinter/test_misc.py ================================================ import unittest import tkinter from test import support from tkinter.test.support import AbstractTkTest, AbstractDefaultRootTest support.requires('gui') class MiscTest(AbstractTkTest, unittest.TestCase): def test_all(self): self.assertIn("Widget", tkinter.__all__) # Check that variables from tkinter.constants are also in tkinter.__all__ self.assertIn("CASCADE", tkinter.__all__) self.assertIsNotNone(tkinter.CASCADE) # Check that sys, re, and constants are not in tkinter.__all__ self.assertNotIn("re", tkinter.__all__) self.assertNotIn("sys", tkinter.__all__) self.assertNotIn("constants", tkinter.__all__) # Check that an underscored functions is not in tkinter.__all__ self.assertNotIn("_tkerror", tkinter.__all__) # Check that wantobjects is not in tkinter.__all__ self.assertNotIn("wantobjects", tkinter.__all__) def test_repr(self): t = tkinter.Toplevel(self.root, name='top') f = tkinter.Frame(t, name='child') self.assertEqual(repr(f), '') def test_generated_names(self): t = tkinter.Toplevel(self.root) f = tkinter.Frame(t) f2 = tkinter.Frame(t) b = tkinter.Button(f2) for name in str(b).split('.'): self.assertFalse(name.isidentifier(), msg=repr(name)) def test_tk_setPalette(self): root = self.root root.tk_setPalette('black') self.assertEqual(root['background'], 'black') root.tk_setPalette('white') self.assertEqual(root['background'], 'white') self.assertRaisesRegex(tkinter.TclError, '^unknown color name "spam"$', root.tk_setPalette, 'spam') root.tk_setPalette(background='black') self.assertEqual(root['background'], 'black') root.tk_setPalette(background='blue', highlightColor='yellow') self.assertEqual(root['background'], 'blue') self.assertEqual(root['highlightcolor'], 'yellow') root.tk_setPalette(background='yellow', highlightColor='blue') self.assertEqual(root['background'], 'yellow') self.assertEqual(root['highlightcolor'], 'blue') self.assertRaisesRegex(tkinter.TclError, '^unknown color name "spam"$', root.tk_setPalette, background='spam') self.assertRaisesRegex(tkinter.TclError, '^must specify a background color$', root.tk_setPalette, spam='white') self.assertRaisesRegex(tkinter.TclError, '^must specify a background color$', root.tk_setPalette, highlightColor='blue') def test_after(self): root = self.root def callback(start=0, step=1): nonlocal count count = start + step # Without function, sleeps for ms. self.assertIsNone(root.after(1)) # Set up with callback with no args. count = 0 timer1 = root.after(0, callback) self.assertIn(timer1, root.tk.call('after', 'info')) (script, _) = root.tk.splitlist(root.tk.call('after', 'info', timer1)) root.update() # Process all pending events. self.assertEqual(count, 1) with self.assertRaises(tkinter.TclError): root.tk.call(script) # Set up with callback with args. count = 0 timer1 = root.after(0, callback, 42, 11) root.update() # Process all pending events. self.assertEqual(count, 53) # Cancel before called. timer1 = root.after(1000, callback) self.assertIn(timer1, root.tk.call('after', 'info')) (script, _) = root.tk.splitlist(root.tk.call('after', 'info', timer1)) root.after_cancel(timer1) # Cancel this event. self.assertEqual(count, 53) with self.assertRaises(tkinter.TclError): root.tk.call(script) def test_after_idle(self): root = self.root def callback(start=0, step=1): nonlocal count count = start + step # Set up with callback with no args. count = 0 idle1 = root.after_idle(callback) self.assertIn(idle1, root.tk.call('after', 'info')) (script, _) = root.tk.splitlist(root.tk.call('after', 'info', idle1)) root.update_idletasks() # Process all pending events. self.assertEqual(count, 1) with self.assertRaises(tkinter.TclError): root.tk.call(script) # Set up with callback with args. count = 0 idle1 = root.after_idle(callback, 42, 11) root.update_idletasks() # Process all pending events. self.assertEqual(count, 53) # Cancel before called. idle1 = root.after_idle(callback) self.assertIn(idle1, root.tk.call('after', 'info')) (script, _) = root.tk.splitlist(root.tk.call('after', 'info', idle1)) root.after_cancel(idle1) # Cancel this event. self.assertEqual(count, 53) with self.assertRaises(tkinter.TclError): root.tk.call(script) def test_after_cancel(self): root = self.root def callback(): nonlocal count count += 1 timer1 = root.after(5000, callback) idle1 = root.after_idle(callback) # No value for id raises a ValueError. with self.assertRaises(ValueError): root.after_cancel(None) # Cancel timer event. count = 0 (script, _) = root.tk.splitlist(root.tk.call('after', 'info', timer1)) root.tk.call(script) self.assertEqual(count, 1) root.after_cancel(timer1) with self.assertRaises(tkinter.TclError): root.tk.call(script) self.assertEqual(count, 1) with self.assertRaises(tkinter.TclError): root.tk.call('after', 'info', timer1) # Cancel same event - nothing happens. root.after_cancel(timer1) # Cancel idle event. count = 0 (script, _) = root.tk.splitlist(root.tk.call('after', 'info', idle1)) root.tk.call(script) self.assertEqual(count, 1) root.after_cancel(idle1) with self.assertRaises(tkinter.TclError): root.tk.call(script) self.assertEqual(count, 1) with self.assertRaises(tkinter.TclError): root.tk.call('after', 'info', idle1) def test_clipboard(self): root = self.root root.clipboard_clear() root.clipboard_append('Ùñî') self.assertEqual(root.clipboard_get(), 'Ùñî') root.clipboard_append('çōđě') self.assertEqual(root.clipboard_get(), 'Ùñîçōđě') root.clipboard_clear() with self.assertRaises(tkinter.TclError): root.clipboard_get() def test_clipboard_astral(self): root = self.root root.clipboard_clear() root.clipboard_append('𝔘𝔫𝔦') self.assertEqual(root.clipboard_get(), '𝔘𝔫𝔦') root.clipboard_append('𝔠𝔬𝔡𝔢') self.assertEqual(root.clipboard_get(), '𝔘𝔫𝔦𝔠𝔬𝔡𝔢') root.clipboard_clear() with self.assertRaises(tkinter.TclError): root.clipboard_get() def test_winfo_rgb(self): root = self.root rgb = root.winfo_rgb # Color name. self.assertEqual(rgb('red'), (65535, 0, 0)) self.assertEqual(rgb('dark slate blue'), (18504, 15677, 35723)) # #RGB - extends each 4-bit hex value to be 16-bit. self.assertEqual(rgb('#F0F'), (0xFFFF, 0x0000, 0xFFFF)) # #RRGGBB - extends each 8-bit hex value to be 16-bit. self.assertEqual(rgb('#4a3c8c'), (0x4a4a, 0x3c3c, 0x8c8c)) # #RRRRGGGGBBBB self.assertEqual(rgb('#dede14143939'), (0xdede, 0x1414, 0x3939)) # Invalid string. with self.assertRaises(tkinter.TclError): rgb('#123456789a') # RGB triplet is invalid input. with self.assertRaises(tkinter.TclError): rgb((111, 78, 55)) def test_event_repr_defaults(self): e = tkinter.Event() e.serial = 12345 e.num = '??' e.height = '??' e.keycode = '??' e.state = 0 e.time = 123456789 e.width = '??' e.x = '??' e.y = '??' e.char = '' e.keysym = '??' e.keysym_num = '??' e.type = '100' e.widget = '??' e.x_root = '??' e.y_root = '??' e.delta = 0 self.assertEqual(repr(e), '<100 event>') def test_event_repr(self): e = tkinter.Event() e.serial = 12345 e.num = 3 e.focus = True e.height = 200 e.keycode = 65 e.state = 0x30405 e.time = 123456789 e.width = 300 e.x = 10 e.y = 20 e.char = 'A' e.send_event = True e.keysym = 'Key-A' e.keysym_num = ord('A') e.type = tkinter.EventType.Configure e.widget = '.text' e.x_root = 1010 e.y_root = 1020 e.delta = -1 self.assertEqual(repr(e), "") def test_getboolean(self): for v in 'true', 'yes', 'on', '1', 't', 'y', 1, True: self.assertIs(self.root.getboolean(v), True) for v in 'false', 'no', 'off', '0', 'f', 'n', 0, False: self.assertIs(self.root.getboolean(v), False) self.assertRaises(ValueError, self.root.getboolean, 'yea') self.assertRaises(ValueError, self.root.getboolean, '') self.assertRaises(TypeError, self.root.getboolean, None) self.assertRaises(TypeError, self.root.getboolean, ()) def test_mainloop(self): log = [] def callback(): log.append(1) self.root.after(100, self.root.quit) self.root.after(100, callback) self.root.mainloop(1) self.assertEqual(log, []) self.root.mainloop(0) self.assertEqual(log, [1]) self.assertTrue(self.root.winfo_exists()) class DefaultRootTest(AbstractDefaultRootTest, unittest.TestCase): def test_default_root(self): self.assertIs(tkinter._support_default_root, True) self.assertIsNone(tkinter._default_root) root = tkinter.Tk() root2 = tkinter.Tk() root3 = tkinter.Tk() self.assertIs(tkinter._default_root, root) root2.destroy() self.assertIs(tkinter._default_root, root) root.destroy() self.assertIsNone(tkinter._default_root) root3.destroy() self.assertIsNone(tkinter._default_root) def test_no_default_root(self): self.assertIs(tkinter._support_default_root, True) self.assertIsNone(tkinter._default_root) root = tkinter.Tk() self.assertIs(tkinter._default_root, root) tkinter.NoDefaultRoot() self.assertIs(tkinter._support_default_root, False) self.assertFalse(hasattr(tkinter, '_default_root')) # repeated call is no-op tkinter.NoDefaultRoot() self.assertIs(tkinter._support_default_root, False) self.assertFalse(hasattr(tkinter, '_default_root')) root.destroy() self.assertIs(tkinter._support_default_root, False) self.assertFalse(hasattr(tkinter, '_default_root')) root = tkinter.Tk() self.assertIs(tkinter._support_default_root, False) self.assertFalse(hasattr(tkinter, '_default_root')) root.destroy() def test_getboolean(self): self.assertRaises(RuntimeError, tkinter.getboolean, '1') root = tkinter.Tk() self.assertIs(tkinter.getboolean('1'), True) self.assertRaises(ValueError, tkinter.getboolean, 'yea') root.destroy() tkinter.NoDefaultRoot() self.assertRaises(RuntimeError, tkinter.getboolean, '1') def test_mainloop(self): self.assertRaises(RuntimeError, tkinter.mainloop) root = tkinter.Tk() root.after_idle(root.quit) tkinter.mainloop() root.destroy() tkinter.NoDefaultRoot() self.assertRaises(RuntimeError, tkinter.mainloop) tests_gui = (MiscTest, DefaultRootTest) if __name__ == "__main__": support.run_unittest(*tests_gui) ================================================ FILE: modules/tkinter/test/test_tkinter/test_simpledialog.py ================================================ import unittest import tkinter from test.support import requires, run_unittest, swap_attr from tkinter.test.support import AbstractDefaultRootTest from tkinter.simpledialog import Dialog, askinteger requires('gui') class DefaultRootTest(AbstractDefaultRootTest, unittest.TestCase): def test_askinteger(self): self.assertRaises(RuntimeError, askinteger, "Go To Line", "Line number") root = tkinter.Tk() with swap_attr(Dialog, 'wait_window', lambda self, w: w.destroy()): askinteger("Go To Line", "Line number") root.destroy() tkinter.NoDefaultRoot() self.assertRaises(RuntimeError, askinteger, "Go To Line", "Line number") tests_gui = (DefaultRootTest,) if __name__ == "__main__": run_unittest(*tests_gui) ================================================ FILE: modules/tkinter/test/test_tkinter/test_text.py ================================================ import unittest import tkinter from test.support import requires, run_unittest from tkinter.test.support import AbstractTkTest requires('gui') class TextTest(AbstractTkTest, unittest.TestCase): def setUp(self): super().setUp() self.text = tkinter.Text(self.root) def test_debug(self): text = self.text olddebug = text.debug() try: text.debug(0) self.assertEqual(text.debug(), 0) text.debug(1) self.assertEqual(text.debug(), 1) finally: text.debug(olddebug) self.assertEqual(text.debug(), olddebug) def test_search(self): text = self.text # pattern and index are obligatory arguments. self.assertRaises(tkinter.TclError, text.search, None, '1.0') self.assertRaises(tkinter.TclError, text.search, 'a', None) self.assertRaises(tkinter.TclError, text.search, None, None) # Invalid text index. self.assertRaises(tkinter.TclError, text.search, '', 0) # Check if we are getting the indices as strings -- you are likely # to get Tcl_Obj under Tk 8.5 if Tkinter doesn't convert it. text.insert('1.0', 'hi-test') self.assertEqual(text.search('-test', '1.0', 'end'), '1.2') self.assertEqual(text.search('test', '1.0', 'end'), '1.3') tests_gui = (TextTest, ) if __name__ == "__main__": run_unittest(*tests_gui) ================================================ FILE: modules/tkinter/test/test_tkinter/test_variables.py ================================================ import unittest from test import support import gc import tkinter from tkinter import (Variable, StringVar, IntVar, DoubleVar, BooleanVar, Tcl, TclError) from test.support import ALWAYS_EQ from tkinter.test.support import AbstractDefaultRootTest class Var(Variable): _default = "default" side_effect = False def set(self, value): self.side_effect = True super().set(value) class TestBase(unittest.TestCase): def setUp(self): self.root = Tcl() def tearDown(self): del self.root class TestVariable(TestBase): def info_exists(self, *args): return self.root.getboolean(self.root.call("info", "exists", *args)) def test_default(self): v = Variable(self.root) self.assertEqual("", v.get()) self.assertRegex(str(v), r"^PY_VAR(\d+)$") def test_name_and_value(self): v = Variable(self.root, "sample string", "varname") self.assertEqual("sample string", v.get()) self.assertEqual("varname", str(v)) def test___del__(self): self.assertFalse(self.info_exists("varname")) v = Variable(self.root, "sample string", "varname") self.assertTrue(self.info_exists("varname")) del v support.gc_collect() # For PyPy or other GCs. self.assertFalse(self.info_exists("varname")) def test_dont_unset_not_existing(self): self.assertFalse(self.info_exists("varname")) v1 = Variable(self.root, name="name") v2 = Variable(self.root, name="name") del v1 support.gc_collect() # For PyPy or other GCs. self.assertFalse(self.info_exists("name")) # shouldn't raise exception del v2 support.gc_collect() # For PyPy or other GCs. self.assertFalse(self.info_exists("name")) def test_equality(self): # values doesn't matter, only class and name are checked v1 = Variable(self.root, name="abc") v2 = Variable(self.root, name="abc") self.assertIsNot(v1, v2) self.assertEqual(v1, v2) v3 = Variable(self.root, name="cba") self.assertNotEqual(v1, v3) v4 = StringVar(self.root, name="abc") self.assertEqual(str(v1), str(v4)) self.assertNotEqual(v1, v4) V = type('Variable', (), {}) self.assertNotEqual(v1, V()) self.assertNotEqual(v1, object()) self.assertEqual(v1, ALWAYS_EQ) root2 = tkinter.Tk() self.addCleanup(root2.destroy) v5 = Variable(root2, name="abc") self.assertEqual(str(v1), str(v5)) self.assertNotEqual(v1, v5) def test_invalid_name(self): with self.assertRaises(TypeError): Variable(self.root, name=123) def test_null_in_name(self): with self.assertRaises(ValueError): Variable(self.root, name='var\x00name') with self.assertRaises(ValueError): self.root.globalsetvar('var\x00name', "value") with self.assertRaises(ValueError): self.root.globalsetvar(b'var\x00name', "value") with self.assertRaises(ValueError): self.root.setvar('var\x00name', "value") with self.assertRaises(ValueError): self.root.setvar(b'var\x00name', "value") def test_initialize(self): v = Var(self.root) self.assertFalse(v.side_effect) v.set("value") self.assertTrue(v.side_effect) def test_trace_old(self): # Old interface v = Variable(self.root) vname = str(v) trace = [] def read_tracer(*args): trace.append(('read',) + args) def write_tracer(*args): trace.append(('write',) + args) cb1 = v.trace_variable('r', read_tracer) cb2 = v.trace_variable('wu', write_tracer) self.assertEqual(sorted(v.trace_vinfo()), [('r', cb1), ('wu', cb2)]) self.assertEqual(trace, []) v.set('spam') self.assertEqual(trace, [('write', vname, '', 'w')]) trace = [] v.get() self.assertEqual(trace, [('read', vname, '', 'r')]) trace = [] info = sorted(v.trace_vinfo()) v.trace_vdelete('w', cb1) # Wrong mode self.assertEqual(sorted(v.trace_vinfo()), info) with self.assertRaises(TclError): v.trace_vdelete('r', 'spam') # Wrong command name self.assertEqual(sorted(v.trace_vinfo()), info) v.trace_vdelete('r', (cb1, 43)) # Wrong arguments self.assertEqual(sorted(v.trace_vinfo()), info) v.get() self.assertEqual(trace, [('read', vname, '', 'r')]) trace = [] v.trace_vdelete('r', cb1) self.assertEqual(v.trace_vinfo(), [('wu', cb2)]) v.get() self.assertEqual(trace, []) trace = [] del write_tracer gc.collect() v.set('eggs') self.assertEqual(trace, [('write', vname, '', 'w')]) trace = [] del v gc.collect() self.assertEqual(trace, [('write', vname, '', 'u')]) def test_trace(self): v = Variable(self.root) vname = str(v) trace = [] def read_tracer(*args): trace.append(('read',) + args) def write_tracer(*args): trace.append(('write',) + args) tr1 = v.trace_add('read', read_tracer) tr2 = v.trace_add(['write', 'unset'], write_tracer) self.assertEqual(sorted(v.trace_info()), [ (('read',), tr1), (('write', 'unset'), tr2)]) self.assertEqual(trace, []) v.set('spam') self.assertEqual(trace, [('write', vname, '', 'write')]) trace = [] v.get() self.assertEqual(trace, [('read', vname, '', 'read')]) trace = [] info = sorted(v.trace_info()) v.trace_remove('write', tr1) # Wrong mode self.assertEqual(sorted(v.trace_info()), info) with self.assertRaises(TclError): v.trace_remove('read', 'spam') # Wrong command name self.assertEqual(sorted(v.trace_info()), info) v.get() self.assertEqual(trace, [('read', vname, '', 'read')]) trace = [] v.trace_remove('read', tr1) self.assertEqual(v.trace_info(), [(('write', 'unset'), tr2)]) v.get() self.assertEqual(trace, []) trace = [] del write_tracer gc.collect() v.set('eggs') self.assertEqual(trace, [('write', vname, '', 'write')]) trace = [] del v gc.collect() self.assertEqual(trace, [('write', vname, '', 'unset')]) class TestStringVar(TestBase): def test_default(self): v = StringVar(self.root) self.assertEqual("", v.get()) def test_get(self): v = StringVar(self.root, "abc", "name") self.assertEqual("abc", v.get()) self.root.globalsetvar("name", "value") self.assertEqual("value", v.get()) def test_get_null(self): v = StringVar(self.root, "abc\x00def", "name") self.assertEqual("abc\x00def", v.get()) self.root.globalsetvar("name", "val\x00ue") self.assertEqual("val\x00ue", v.get()) class TestIntVar(TestBase): def test_default(self): v = IntVar(self.root) self.assertEqual(0, v.get()) def test_get(self): v = IntVar(self.root, 123, "name") self.assertEqual(123, v.get()) self.root.globalsetvar("name", "345") self.assertEqual(345, v.get()) self.root.globalsetvar("name", "876.5") self.assertEqual(876, v.get()) def test_invalid_value(self): v = IntVar(self.root, name="name") self.root.globalsetvar("name", "value") with self.assertRaises((ValueError, TclError)): v.get() class TestDoubleVar(TestBase): def test_default(self): v = DoubleVar(self.root) self.assertEqual(0.0, v.get()) def test_get(self): v = DoubleVar(self.root, 1.23, "name") self.assertAlmostEqual(1.23, v.get()) self.root.globalsetvar("name", "3.45") self.assertAlmostEqual(3.45, v.get()) def test_get_from_int(self): v = DoubleVar(self.root, 1.23, "name") self.assertAlmostEqual(1.23, v.get()) self.root.globalsetvar("name", "3.45") self.assertAlmostEqual(3.45, v.get()) self.root.globalsetvar("name", "456") self.assertAlmostEqual(456, v.get()) def test_invalid_value(self): v = DoubleVar(self.root, name="name") self.root.globalsetvar("name", "value") with self.assertRaises((ValueError, TclError)): v.get() class TestBooleanVar(TestBase): def test_default(self): v = BooleanVar(self.root) self.assertIs(v.get(), False) def test_get(self): v = BooleanVar(self.root, True, "name") self.assertIs(v.get(), True) self.root.globalsetvar("name", "0") self.assertIs(v.get(), False) self.root.globalsetvar("name", 42 if self.root.wantobjects() else 1) self.assertIs(v.get(), True) self.root.globalsetvar("name", 0) self.assertIs(v.get(), False) self.root.globalsetvar("name", "on") self.assertIs(v.get(), True) def test_set(self): true = 1 if self.root.wantobjects() else "1" false = 0 if self.root.wantobjects() else "0" v = BooleanVar(self.root, name="name") v.set(True) self.assertEqual(self.root.globalgetvar("name"), true) v.set("0") self.assertEqual(self.root.globalgetvar("name"), false) v.set(42) self.assertEqual(self.root.globalgetvar("name"), true) v.set(0) self.assertEqual(self.root.globalgetvar("name"), false) v.set("on") self.assertEqual(self.root.globalgetvar("name"), true) def test_invalid_value_domain(self): false = 0 if self.root.wantobjects() else "0" v = BooleanVar(self.root, name="name") with self.assertRaises(TclError): v.set("value") self.assertEqual(self.root.globalgetvar("name"), false) self.root.globalsetvar("name", "value") with self.assertRaises(ValueError): v.get() self.root.globalsetvar("name", "1.0") with self.assertRaises(ValueError): v.get() class DefaultRootTest(AbstractDefaultRootTest, unittest.TestCase): def test_variable(self): self.assertRaises(RuntimeError, Variable) root = tkinter.Tk() v = Variable() v.set("value") self.assertEqual(v.get(), "value") root.destroy() tkinter.NoDefaultRoot() self.assertRaises(RuntimeError, Variable) tests_gui = (TestVariable, TestStringVar, TestIntVar, TestDoubleVar, TestBooleanVar, DefaultRootTest) if __name__ == "__main__": from test.support import run_unittest run_unittest(*tests_gui) ================================================ FILE: modules/tkinter/test/test_tkinter/test_widgets.py ================================================ import unittest import tkinter from tkinter import TclError import os from test.support import requires from tkinter.test.support import (tcl_version, requires_tcl, get_tk_patchlevel, widget_eq, AbstractDefaultRootTest) from tkinter.test.widget_tests import ( add_standard_options, noconv, pixels_round, AbstractWidgetTest, StandardOptionsTests, IntegerSizeTests, PixelSizeTests, setUpModule) requires('gui') def float_round(x): return float(round(x)) class AbstractToplevelTest(AbstractWidgetTest, PixelSizeTests): _conv_pad_pixels = noconv def test_configure_class(self): widget = self.create() self.assertEqual(widget['class'], widget.__class__.__name__.title()) self.checkInvalidParam(widget, 'class', 'Foo', errmsg="can't modify -class option after widget is created") widget2 = self.create(class_='Foo') self.assertEqual(widget2['class'], 'Foo') def test_configure_colormap(self): widget = self.create() self.assertEqual(widget['colormap'], '') self.checkInvalidParam(widget, 'colormap', 'new', errmsg="can't modify -colormap option after widget is created") widget2 = self.create(colormap='new') self.assertEqual(widget2['colormap'], 'new') def test_configure_container(self): widget = self.create() self.assertEqual(widget['container'], 0 if self.wantobjects else '0') self.checkInvalidParam(widget, 'container', 1, errmsg="can't modify -container option after widget is created") widget2 = self.create(container=True) self.assertEqual(widget2['container'], 1 if self.wantobjects else '1') def test_configure_visual(self): widget = self.create() self.assertEqual(widget['visual'], '') self.checkInvalidParam(widget, 'visual', 'default', errmsg="can't modify -visual option after widget is created") widget2 = self.create(visual='default') self.assertEqual(widget2['visual'], 'default') @add_standard_options(StandardOptionsTests) class ToplevelTest(AbstractToplevelTest, unittest.TestCase): OPTIONS = ( 'background', 'borderwidth', 'class', 'colormap', 'container', 'cursor', 'height', 'highlightbackground', 'highlightcolor', 'highlightthickness', 'menu', 'padx', 'pady', 'relief', 'screen', 'takefocus', 'use', 'visual', 'width', ) def create(self, **kwargs): return tkinter.Toplevel(self.root, **kwargs) def test_configure_menu(self): widget = self.create() menu = tkinter.Menu(self.root) self.checkParam(widget, 'menu', menu, eq=widget_eq) self.checkParam(widget, 'menu', '') def test_configure_screen(self): widget = self.create() self.assertEqual(widget['screen'], '') try: display = os.environ['DISPLAY'] except KeyError: self.skipTest('No $DISPLAY set.') self.checkInvalidParam(widget, 'screen', display, errmsg="can't modify -screen option after widget is created") widget2 = self.create(screen=display) self.assertEqual(widget2['screen'], display) def test_configure_use(self): widget = self.create() self.assertEqual(widget['use'], '') parent = self.create(container=True) wid = hex(parent.winfo_id()) with self.subTest(wid=wid): widget2 = self.create(use=wid) self.assertEqual(widget2['use'], wid) @add_standard_options(StandardOptionsTests) class FrameTest(AbstractToplevelTest, unittest.TestCase): OPTIONS = ( 'background', 'borderwidth', 'class', 'colormap', 'container', 'cursor', 'height', 'highlightbackground', 'highlightcolor', 'highlightthickness', 'padx', 'pady', 'relief', 'takefocus', 'visual', 'width', ) def create(self, **kwargs): return tkinter.Frame(self.root, **kwargs) @add_standard_options(StandardOptionsTests) class LabelFrameTest(AbstractToplevelTest, unittest.TestCase): OPTIONS = ( 'background', 'borderwidth', 'class', 'colormap', 'container', 'cursor', 'font', 'foreground', 'height', 'highlightbackground', 'highlightcolor', 'highlightthickness', 'labelanchor', 'labelwidget', 'padx', 'pady', 'relief', 'takefocus', 'text', 'visual', 'width', ) def create(self, **kwargs): return tkinter.LabelFrame(self.root, **kwargs) def test_configure_labelanchor(self): widget = self.create() self.checkEnumParam(widget, 'labelanchor', 'e', 'en', 'es', 'n', 'ne', 'nw', 's', 'se', 'sw', 'w', 'wn', 'ws') self.checkInvalidParam(widget, 'labelanchor', 'center') def test_configure_labelwidget(self): widget = self.create() label = tkinter.Label(self.root, text='Mupp', name='foo') self.checkParam(widget, 'labelwidget', label, expected='.foo') label.destroy() class AbstractLabelTest(AbstractWidgetTest, IntegerSizeTests): _conv_pixels = noconv def test_configure_highlightthickness(self): widget = self.create() self.checkPixelsParam(widget, 'highlightthickness', 0, 1.3, 2.6, 6, -2, '10p') @add_standard_options(StandardOptionsTests) class LabelTest(AbstractLabelTest, unittest.TestCase): OPTIONS = ( 'activebackground', 'activeforeground', 'anchor', 'background', 'bitmap', 'borderwidth', 'compound', 'cursor', 'disabledforeground', 'font', 'foreground', 'height', 'highlightbackground', 'highlightcolor', 'highlightthickness', 'image', 'justify', 'padx', 'pady', 'relief', 'state', 'takefocus', 'text', 'textvariable', 'underline', 'width', 'wraplength', ) def create(self, **kwargs): return tkinter.Label(self.root, **kwargs) @add_standard_options(StandardOptionsTests) class ButtonTest(AbstractLabelTest, unittest.TestCase): OPTIONS = ( 'activebackground', 'activeforeground', 'anchor', 'background', 'bitmap', 'borderwidth', 'command', 'compound', 'cursor', 'default', 'disabledforeground', 'font', 'foreground', 'height', 'highlightbackground', 'highlightcolor', 'highlightthickness', 'image', 'justify', 'overrelief', 'padx', 'pady', 'relief', 'repeatdelay', 'repeatinterval', 'state', 'takefocus', 'text', 'textvariable', 'underline', 'width', 'wraplength') def create(self, **kwargs): return tkinter.Button(self.root, **kwargs) def test_configure_default(self): widget = self.create() self.checkEnumParam(widget, 'default', 'active', 'disabled', 'normal') @add_standard_options(StandardOptionsTests) class CheckbuttonTest(AbstractLabelTest, unittest.TestCase): OPTIONS = ( 'activebackground', 'activeforeground', 'anchor', 'background', 'bitmap', 'borderwidth', 'command', 'compound', 'cursor', 'disabledforeground', 'font', 'foreground', 'height', 'highlightbackground', 'highlightcolor', 'highlightthickness', 'image', 'indicatoron', 'justify', 'offrelief', 'offvalue', 'onvalue', 'overrelief', 'padx', 'pady', 'relief', 'selectcolor', 'selectimage', 'state', 'takefocus', 'text', 'textvariable', 'tristateimage', 'tristatevalue', 'underline', 'variable', 'width', 'wraplength', ) def create(self, **kwargs): return tkinter.Checkbutton(self.root, **kwargs) def test_configure_offvalue(self): widget = self.create() self.checkParams(widget, 'offvalue', 1, 2.3, '', 'any string') def test_configure_onvalue(self): widget = self.create() self.checkParams(widget, 'onvalue', 1, 2.3, '', 'any string') @add_standard_options(StandardOptionsTests) class RadiobuttonTest(AbstractLabelTest, unittest.TestCase): OPTIONS = ( 'activebackground', 'activeforeground', 'anchor', 'background', 'bitmap', 'borderwidth', 'command', 'compound', 'cursor', 'disabledforeground', 'font', 'foreground', 'height', 'highlightbackground', 'highlightcolor', 'highlightthickness', 'image', 'indicatoron', 'justify', 'offrelief', 'overrelief', 'padx', 'pady', 'relief', 'selectcolor', 'selectimage', 'state', 'takefocus', 'text', 'textvariable', 'tristateimage', 'tristatevalue', 'underline', 'value', 'variable', 'width', 'wraplength', ) def create(self, **kwargs): return tkinter.Radiobutton(self.root, **kwargs) def test_configure_value(self): widget = self.create() self.checkParams(widget, 'value', 1, 2.3, '', 'any string') @add_standard_options(StandardOptionsTests) class MenubuttonTest(AbstractLabelTest, unittest.TestCase): OPTIONS = ( 'activebackground', 'activeforeground', 'anchor', 'background', 'bitmap', 'borderwidth', 'compound', 'cursor', 'direction', 'disabledforeground', 'font', 'foreground', 'height', 'highlightbackground', 'highlightcolor', 'highlightthickness', 'image', 'indicatoron', 'justify', 'menu', 'padx', 'pady', 'relief', 'state', 'takefocus', 'text', 'textvariable', 'underline', 'width', 'wraplength', ) _conv_pixels = staticmethod(pixels_round) def create(self, **kwargs): return tkinter.Menubutton(self.root, **kwargs) def test_configure_direction(self): widget = self.create() self.checkEnumParam(widget, 'direction', 'above', 'below', 'flush', 'left', 'right') def test_configure_height(self): widget = self.create() self.checkIntegerParam(widget, 'height', 100, -100, 0, conv=str) test_configure_highlightthickness = \ StandardOptionsTests.test_configure_highlightthickness def test_configure_image(self): widget = self.create() image = tkinter.PhotoImage(master=self.root, name='image1') self.checkParam(widget, 'image', image, conv=str) errmsg = 'image "spam" doesn\'t exist' with self.assertRaises(tkinter.TclError) as cm: widget['image'] = 'spam' if errmsg is not None: self.assertEqual(str(cm.exception), errmsg) with self.assertRaises(tkinter.TclError) as cm: widget.configure({'image': 'spam'}) if errmsg is not None: self.assertEqual(str(cm.exception), errmsg) def test_configure_menu(self): widget = self.create() menu = tkinter.Menu(widget, name='menu') self.checkParam(widget, 'menu', menu, eq=widget_eq) menu.destroy() def test_configure_padx(self): widget = self.create() self.checkPixelsParam(widget, 'padx', 3, 4.4, 5.6, '12m') self.checkParam(widget, 'padx', -2, expected=0) def test_configure_pady(self): widget = self.create() self.checkPixelsParam(widget, 'pady', 3, 4.4, 5.6, '12m') self.checkParam(widget, 'pady', -2, expected=0) def test_configure_width(self): widget = self.create() self.checkIntegerParam(widget, 'width', 402, -402, 0, conv=str) class OptionMenuTest(MenubuttonTest, unittest.TestCase): def create(self, default='b', values=('a', 'b', 'c'), **kwargs): return tkinter.OptionMenu(self.root, None, default, *values, **kwargs) def test_bad_kwarg(self): with self.assertRaisesRegex(TclError, r"^unknown option -image$"): tkinter.OptionMenu(self.root, None, 'b', image='') @add_standard_options(IntegerSizeTests, StandardOptionsTests) class EntryTest(AbstractWidgetTest, unittest.TestCase): OPTIONS = ( 'background', 'borderwidth', 'cursor', 'disabledbackground', 'disabledforeground', 'exportselection', 'font', 'foreground', 'highlightbackground', 'highlightcolor', 'highlightthickness', 'insertbackground', 'insertborderwidth', 'insertofftime', 'insertontime', 'insertwidth', 'invalidcommand', 'justify', 'readonlybackground', 'relief', 'selectbackground', 'selectborderwidth', 'selectforeground', 'show', 'state', 'takefocus', 'textvariable', 'validate', 'validatecommand', 'width', 'xscrollcommand', ) def create(self, **kwargs): return tkinter.Entry(self.root, **kwargs) def test_configure_disabledbackground(self): widget = self.create() self.checkColorParam(widget, 'disabledbackground') def test_configure_insertborderwidth(self): widget = self.create(insertwidth=100) self.checkPixelsParam(widget, 'insertborderwidth', 0, 1.3, 2.6, 6, -2, '10p') # insertborderwidth is bounded above by a half of insertwidth. self.checkParam(widget, 'insertborderwidth', 60, expected=100//2) def test_configure_insertwidth(self): widget = self.create() self.checkPixelsParam(widget, 'insertwidth', 1.3, 3.6, '10p') self.checkParam(widget, 'insertwidth', 0.1, expected=2) self.checkParam(widget, 'insertwidth', -2, expected=2) if pixels_round(0.9) <= 0: self.checkParam(widget, 'insertwidth', 0.9, expected=2) else: self.checkParam(widget, 'insertwidth', 0.9, expected=1) def test_configure_invalidcommand(self): widget = self.create() self.checkCommandParam(widget, 'invalidcommand') self.checkCommandParam(widget, 'invcmd') def test_configure_readonlybackground(self): widget = self.create() self.checkColorParam(widget, 'readonlybackground') def test_configure_show(self): widget = self.create() self.checkParam(widget, 'show', '*') self.checkParam(widget, 'show', '') self.checkParam(widget, 'show', ' ') def test_configure_state(self): widget = self.create() self.checkEnumParam(widget, 'state', 'disabled', 'normal', 'readonly') def test_configure_validate(self): widget = self.create() self.checkEnumParam(widget, 'validate', 'all', 'key', 'focus', 'focusin', 'focusout', 'none') def test_configure_validatecommand(self): widget = self.create() self.checkCommandParam(widget, 'validatecommand') self.checkCommandParam(widget, 'vcmd') def test_selection_methods(self): widget = self.create() widget.insert(0, '12345') self.assertFalse(widget.selection_present()) widget.selection_range(0, 'end') self.assertEqual(widget.selection_get(), '12345') self.assertTrue(widget.selection_present()) widget.selection_from(1) widget.selection_to(2) self.assertEqual(widget.selection_get(), '2') widget.selection_range(3, 4) self.assertEqual(widget.selection_get(), '4') widget.selection_clear() self.assertFalse(widget.selection_present()) widget.selection_range(0, 'end') widget.selection_adjust(4) self.assertEqual(widget.selection_get(), '1234') widget.selection_adjust(1) self.assertEqual(widget.selection_get(), '234') widget.selection_adjust(5) self.assertEqual(widget.selection_get(), '2345') widget.selection_adjust(0) self.assertEqual(widget.selection_get(), '12345') widget.selection_adjust(0) @add_standard_options(StandardOptionsTests) class SpinboxTest(EntryTest, unittest.TestCase): OPTIONS = ( 'activebackground', 'background', 'borderwidth', 'buttonbackground', 'buttoncursor', 'buttondownrelief', 'buttonuprelief', 'command', 'cursor', 'disabledbackground', 'disabledforeground', 'exportselection', 'font', 'foreground', 'format', 'from', 'highlightbackground', 'highlightcolor', 'highlightthickness', 'increment', 'insertbackground', 'insertborderwidth', 'insertofftime', 'insertontime', 'insertwidth', 'invalidcommand', 'justify', 'relief', 'readonlybackground', 'repeatdelay', 'repeatinterval', 'selectbackground', 'selectborderwidth', 'selectforeground', 'state', 'takefocus', 'textvariable', 'to', 'validate', 'validatecommand', 'values', 'width', 'wrap', 'xscrollcommand', ) def create(self, **kwargs): return tkinter.Spinbox(self.root, **kwargs) test_configure_show = None def test_configure_buttonbackground(self): widget = self.create() self.checkColorParam(widget, 'buttonbackground') def test_configure_buttoncursor(self): widget = self.create() self.checkCursorParam(widget, 'buttoncursor') def test_configure_buttondownrelief(self): widget = self.create() self.checkReliefParam(widget, 'buttondownrelief') def test_configure_buttonuprelief(self): widget = self.create() self.checkReliefParam(widget, 'buttonuprelief') def test_configure_format(self): widget = self.create() self.checkParam(widget, 'format', '%2f') self.checkParam(widget, 'format', '%2.2f') self.checkParam(widget, 'format', '%.2f') self.checkParam(widget, 'format', '%2.f') self.checkInvalidParam(widget, 'format', '%2e-1f') self.checkInvalidParam(widget, 'format', '2.2') self.checkInvalidParam(widget, 'format', '%2.-2f') self.checkParam(widget, 'format', '%-2.02f') self.checkParam(widget, 'format', '% 2.02f') self.checkParam(widget, 'format', '% -2.200f') self.checkParam(widget, 'format', '%09.200f') self.checkInvalidParam(widget, 'format', '%d') def test_configure_from(self): widget = self.create() self.checkParam(widget, 'to', 100.0) self.checkFloatParam(widget, 'from', -10, 10.2, 11.7) self.checkInvalidParam(widget, 'from', 200, errmsg='-to value must be greater than -from value') def test_configure_increment(self): widget = self.create() self.checkFloatParam(widget, 'increment', -1, 1, 10.2, 12.8, 0) def test_configure_to(self): widget = self.create() self.checkParam(widget, 'from', -100.0) self.checkFloatParam(widget, 'to', -10, 10.2, 11.7) self.checkInvalidParam(widget, 'to', -200, errmsg='-to value must be greater than -from value') def test_configure_values(self): # XXX widget = self.create() self.assertEqual(widget['values'], '') self.checkParam(widget, 'values', 'mon tue wed thur') self.checkParam(widget, 'values', ('mon', 'tue', 'wed', 'thur'), expected='mon tue wed thur') self.checkParam(widget, 'values', (42, 3.14, '', 'any string'), expected='42 3.14 {} {any string}') self.checkParam(widget, 'values', '') def test_configure_wrap(self): widget = self.create() self.checkBooleanParam(widget, 'wrap') def test_bbox(self): widget = self.create() self.assertIsBoundingBox(widget.bbox(0)) self.assertRaises(tkinter.TclError, widget.bbox, 'noindex') self.assertRaises(tkinter.TclError, widget.bbox, None) self.assertRaises(TypeError, widget.bbox) self.assertRaises(TypeError, widget.bbox, 0, 1) def test_selection_methods(self): widget = self.create() widget.insert(0, '12345') self.assertFalse(widget.selection_present()) widget.selection_range(0, 'end') self.assertEqual(widget.selection_get(), '12345') self.assertTrue(widget.selection_present()) widget.selection_from(1) widget.selection_to(2) self.assertEqual(widget.selection_get(), '2') widget.selection_range(3, 4) self.assertEqual(widget.selection_get(), '4') widget.selection_clear() self.assertFalse(widget.selection_present()) widget.selection_range(0, 'end') widget.selection_adjust(4) self.assertEqual(widget.selection_get(), '1234') widget.selection_adjust(1) self.assertEqual(widget.selection_get(), '234') widget.selection_adjust(5) self.assertEqual(widget.selection_get(), '2345') widget.selection_adjust(0) self.assertEqual(widget.selection_get(), '12345') def test_selection_element(self): widget = self.create() self.assertEqual(widget.selection_element(), "none") widget.selection_element("buttonup") self.assertEqual(widget.selection_element(), "buttonup") widget.selection_element("buttondown") self.assertEqual(widget.selection_element(), "buttondown") @add_standard_options(StandardOptionsTests) class TextTest(AbstractWidgetTest, unittest.TestCase): OPTIONS = ( 'autoseparators', 'background', 'blockcursor', 'borderwidth', 'cursor', 'endline', 'exportselection', 'font', 'foreground', 'height', 'highlightbackground', 'highlightcolor', 'highlightthickness', 'inactiveselectbackground', 'insertbackground', 'insertborderwidth', 'insertofftime', 'insertontime', 'insertunfocussed', 'insertwidth', 'maxundo', 'padx', 'pady', 'relief', 'selectbackground', 'selectborderwidth', 'selectforeground', 'setgrid', 'spacing1', 'spacing2', 'spacing3', 'startline', 'state', 'tabs', 'tabstyle', 'takefocus', 'undo', 'width', 'wrap', 'xscrollcommand', 'yscrollcommand', ) if tcl_version < (8, 5): _stringify = True def create(self, **kwargs): return tkinter.Text(self.root, **kwargs) def test_configure_autoseparators(self): widget = self.create() self.checkBooleanParam(widget, 'autoseparators') @requires_tcl(8, 5) def test_configure_blockcursor(self): widget = self.create() self.checkBooleanParam(widget, 'blockcursor') @requires_tcl(8, 5) def test_configure_endline(self): widget = self.create() text = '\n'.join('Line %d' for i in range(100)) widget.insert('end', text) self.checkParam(widget, 'endline', 200, expected='') self.checkParam(widget, 'endline', -10, expected='') self.checkInvalidParam(widget, 'endline', 'spam', errmsg='expected integer but got "spam"') self.checkParam(widget, 'endline', 50) self.checkParam(widget, 'startline', 15) self.checkInvalidParam(widget, 'endline', 10, errmsg='-startline must be less than or equal to -endline') def test_configure_height(self): widget = self.create() self.checkPixelsParam(widget, 'height', 100, 101.2, 102.6, '3c') self.checkParam(widget, 'height', -100, expected=1) self.checkParam(widget, 'height', 0, expected=1) def test_configure_maxundo(self): widget = self.create() self.checkIntegerParam(widget, 'maxundo', 0, 5, -1) @requires_tcl(8, 5) def test_configure_inactiveselectbackground(self): widget = self.create() self.checkColorParam(widget, 'inactiveselectbackground') @requires_tcl(8, 6) def test_configure_insertunfocussed(self): widget = self.create() self.checkEnumParam(widget, 'insertunfocussed', 'hollow', 'none', 'solid') def test_configure_selectborderwidth(self): widget = self.create() self.checkPixelsParam(widget, 'selectborderwidth', 1.3, 2.6, -2, '10p', conv=noconv, keep_orig=tcl_version >= (8, 5)) def test_configure_spacing1(self): widget = self.create() self.checkPixelsParam(widget, 'spacing1', 20, 21.4, 22.6, '0.5c') self.checkParam(widget, 'spacing1', -5, expected=0) def test_configure_spacing2(self): widget = self.create() self.checkPixelsParam(widget, 'spacing2', 5, 6.4, 7.6, '0.1c') self.checkParam(widget, 'spacing2', -1, expected=0) def test_configure_spacing3(self): widget = self.create() self.checkPixelsParam(widget, 'spacing3', 20, 21.4, 22.6, '0.5c') self.checkParam(widget, 'spacing3', -10, expected=0) @requires_tcl(8, 5) def test_configure_startline(self): widget = self.create() text = '\n'.join('Line %d' for i in range(100)) widget.insert('end', text) self.checkParam(widget, 'startline', 200, expected='') self.checkParam(widget, 'startline', -10, expected='') self.checkInvalidParam(widget, 'startline', 'spam', errmsg='expected integer but got "spam"') self.checkParam(widget, 'startline', 10) self.checkParam(widget, 'endline', 50) self.checkInvalidParam(widget, 'startline', 70, errmsg='-startline must be less than or equal to -endline') def test_configure_state(self): widget = self.create() if tcl_version < (8, 5): self.checkParams(widget, 'state', 'disabled', 'normal') else: self.checkEnumParam(widget, 'state', 'disabled', 'normal') def test_configure_tabs(self): widget = self.create() if get_tk_patchlevel() < (8, 5, 11): self.checkParam(widget, 'tabs', (10.2, 20.7, '1i', '2i'), expected=('10.2', '20.7', '1i', '2i')) else: self.checkParam(widget, 'tabs', (10.2, 20.7, '1i', '2i')) self.checkParam(widget, 'tabs', '10.2 20.7 1i 2i', expected=('10.2', '20.7', '1i', '2i')) self.checkParam(widget, 'tabs', '2c left 4c 6c center', expected=('2c', 'left', '4c', '6c', 'center')) self.checkInvalidParam(widget, 'tabs', 'spam', errmsg='bad screen distance "spam"', keep_orig=tcl_version >= (8, 5)) @requires_tcl(8, 5) def test_configure_tabstyle(self): widget = self.create() self.checkEnumParam(widget, 'tabstyle', 'tabular', 'wordprocessor') def test_configure_undo(self): widget = self.create() self.checkBooleanParam(widget, 'undo') def test_configure_width(self): widget = self.create() self.checkIntegerParam(widget, 'width', 402) self.checkParam(widget, 'width', -402, expected=1) self.checkParam(widget, 'width', 0, expected=1) def test_configure_wrap(self): widget = self.create() if tcl_version < (8, 5): self.checkParams(widget, 'wrap', 'char', 'none', 'word') else: self.checkEnumParam(widget, 'wrap', 'char', 'none', 'word') def test_bbox(self): widget = self.create() self.assertIsBoundingBox(widget.bbox('1.1')) self.assertIsNone(widget.bbox('end')) self.assertRaises(tkinter.TclError, widget.bbox, 'noindex') self.assertRaises(tkinter.TclError, widget.bbox, None) self.assertRaises(TypeError, widget.bbox) self.assertRaises(TypeError, widget.bbox, '1.1', 'end') @add_standard_options(PixelSizeTests, StandardOptionsTests) class CanvasTest(AbstractWidgetTest, unittest.TestCase): OPTIONS = ( 'background', 'borderwidth', 'closeenough', 'confine', 'cursor', 'height', 'highlightbackground', 'highlightcolor', 'highlightthickness', 'insertbackground', 'insertborderwidth', 'insertofftime', 'insertontime', 'insertwidth', 'offset', 'relief', 'scrollregion', 'selectbackground', 'selectborderwidth', 'selectforeground', 'state', 'takefocus', 'xscrollcommand', 'xscrollincrement', 'yscrollcommand', 'yscrollincrement', 'width', ) _conv_pixels = round _stringify = True def create(self, **kwargs): return tkinter.Canvas(self.root, **kwargs) def test_configure_closeenough(self): widget = self.create() self.checkFloatParam(widget, 'closeenough', 24, 2.4, 3.6, -3, conv=float) def test_configure_confine(self): widget = self.create() self.checkBooleanParam(widget, 'confine') def test_configure_offset(self): widget = self.create() self.assertEqual(widget['offset'], '0,0') self.checkParams(widget, 'offset', 'n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw', 'center') self.checkParam(widget, 'offset', '10,20') self.checkParam(widget, 'offset', '#5,6') self.checkInvalidParam(widget, 'offset', 'spam') def test_configure_scrollregion(self): widget = self.create() self.checkParam(widget, 'scrollregion', '0 0 200 150') self.checkParam(widget, 'scrollregion', (0, 0, 200, 150), expected='0 0 200 150') self.checkParam(widget, 'scrollregion', '') self.checkInvalidParam(widget, 'scrollregion', 'spam', errmsg='bad scrollRegion "spam"') self.checkInvalidParam(widget, 'scrollregion', (0, 0, 200, 'spam')) self.checkInvalidParam(widget, 'scrollregion', (0, 0, 200)) self.checkInvalidParam(widget, 'scrollregion', (0, 0, 200, 150, 0)) def test_configure_state(self): widget = self.create() self.checkEnumParam(widget, 'state', 'disabled', 'normal', errmsg='bad state value "{}": must be normal or disabled') def test_configure_xscrollincrement(self): widget = self.create() self.checkPixelsParam(widget, 'xscrollincrement', 40, 0, 41.2, 43.6, -40, '0.5i') def test_configure_yscrollincrement(self): widget = self.create() self.checkPixelsParam(widget, 'yscrollincrement', 10, 0, 11.2, 13.6, -10, '0.1i') @requires_tcl(8, 6) def test_moveto(self): widget = self.create() i1 = widget.create_rectangle(1, 1, 20, 20, tags='group') i2 = widget.create_rectangle(30, 30, 50, 70, tags='group') x1, y1, _, _ = widget.bbox(i1) x2, y2, _, _ = widget.bbox(i2) widget.moveto('group', 200, 100) x1_2, y1_2, _, _ = widget.bbox(i1) x2_2, y2_2, _, _ = widget.bbox(i2) self.assertEqual(x1_2, 200) self.assertEqual(y1_2, 100) self.assertEqual(x2 - x1, x2_2 - x1_2) self.assertEqual(y2 - y1, y2_2 - y1_2) widget.tag_lower(i2, i1) widget.moveto('group', y=50) x1_3, y1_3, _, _ = widget.bbox(i1) x2_3, y2_3, _, _ = widget.bbox(i2) self.assertEqual(y2_3, 50) self.assertEqual(x2_3, x2_2) self.assertEqual(x2_2 - x1_2, x2_3 - x1_3) self.assertEqual(y2_2 - y1_2, y2_3 - y1_3) @add_standard_options(IntegerSizeTests, StandardOptionsTests) class ListboxTest(AbstractWidgetTest, unittest.TestCase): OPTIONS = ( 'activestyle', 'background', 'borderwidth', 'cursor', 'disabledforeground', 'exportselection', 'font', 'foreground', 'height', 'highlightbackground', 'highlightcolor', 'highlightthickness', 'justify', 'listvariable', 'relief', 'selectbackground', 'selectborderwidth', 'selectforeground', 'selectmode', 'setgrid', 'state', 'takefocus', 'width', 'xscrollcommand', 'yscrollcommand', ) def create(self, **kwargs): return tkinter.Listbox(self.root, **kwargs) def test_configure_activestyle(self): widget = self.create() self.checkEnumParam(widget, 'activestyle', 'dotbox', 'none', 'underline') test_justify = requires_tcl(8, 6, 5)(StandardOptionsTests.test_configure_justify) def test_configure_listvariable(self): widget = self.create() var = tkinter.DoubleVar(self.root) self.checkVariableParam(widget, 'listvariable', var) def test_configure_selectmode(self): widget = self.create() self.checkParam(widget, 'selectmode', 'single') self.checkParam(widget, 'selectmode', 'browse') self.checkParam(widget, 'selectmode', 'multiple') self.checkParam(widget, 'selectmode', 'extended') def test_configure_state(self): widget = self.create() self.checkEnumParam(widget, 'state', 'disabled', 'normal') def test_itemconfigure(self): widget = self.create() with self.assertRaisesRegex(TclError, 'item number "0" out of range'): widget.itemconfigure(0) colors = 'red orange yellow green blue white violet'.split() widget.insert('end', *colors) for i, color in enumerate(colors): widget.itemconfigure(i, background=color) with self.assertRaises(TypeError): widget.itemconfigure() with self.assertRaisesRegex(TclError, 'bad listbox index "red"'): widget.itemconfigure('red') self.assertEqual(widget.itemconfigure(0, 'background'), ('background', 'background', 'Background', '', 'red')) self.assertEqual(widget.itemconfigure('end', 'background'), ('background', 'background', 'Background', '', 'violet')) self.assertEqual(widget.itemconfigure('@0,0', 'background'), ('background', 'background', 'Background', '', 'red')) d = widget.itemconfigure(0) self.assertIsInstance(d, dict) for k, v in d.items(): self.assertIn(len(v), (2, 5)) if len(v) == 5: self.assertEqual(v, widget.itemconfigure(0, k)) self.assertEqual(v[4], widget.itemcget(0, k)) def check_itemconfigure(self, name, value): widget = self.create() widget.insert('end', 'a', 'b', 'c', 'd') widget.itemconfigure(0, **{name: value}) self.assertEqual(widget.itemconfigure(0, name)[4], value) self.assertEqual(widget.itemcget(0, name), value) with self.assertRaisesRegex(TclError, 'unknown color name "spam"'): widget.itemconfigure(0, **{name: 'spam'}) def test_itemconfigure_background(self): self.check_itemconfigure('background', '#ff0000') def test_itemconfigure_bg(self): self.check_itemconfigure('bg', '#ff0000') def test_itemconfigure_fg(self): self.check_itemconfigure('fg', '#110022') def test_itemconfigure_foreground(self): self.check_itemconfigure('foreground', '#110022') def test_itemconfigure_selectbackground(self): self.check_itemconfigure('selectbackground', '#110022') def test_itemconfigure_selectforeground(self): self.check_itemconfigure('selectforeground', '#654321') def test_box(self): lb = self.create() lb.insert(0, *('el%d' % i for i in range(8))) lb.pack() self.assertIsBoundingBox(lb.bbox(0)) self.assertIsNone(lb.bbox(-1)) self.assertIsNone(lb.bbox(10)) self.assertRaises(TclError, lb.bbox, 'noindex') self.assertRaises(TclError, lb.bbox, None) self.assertRaises(TypeError, lb.bbox) self.assertRaises(TypeError, lb.bbox, 0, 1) def test_curselection(self): lb = self.create() lb.insert(0, *('el%d' % i for i in range(8))) lb.selection_clear(0, tkinter.END) lb.selection_set(2, 4) lb.selection_set(6) self.assertEqual(lb.curselection(), (2, 3, 4, 6)) self.assertRaises(TypeError, lb.curselection, 0) def test_get(self): lb = self.create() lb.insert(0, *('el%d' % i for i in range(8))) self.assertEqual(lb.get(0), 'el0') self.assertEqual(lb.get(3), 'el3') self.assertEqual(lb.get('end'), 'el7') self.assertEqual(lb.get(8), '') self.assertEqual(lb.get(-1), '') self.assertEqual(lb.get(3, 5), ('el3', 'el4', 'el5')) self.assertEqual(lb.get(5, 'end'), ('el5', 'el6', 'el7')) self.assertEqual(lb.get(5, 0), ()) self.assertEqual(lb.get(0, 0), ('el0',)) self.assertRaises(TclError, lb.get, 'noindex') self.assertRaises(TclError, lb.get, None) self.assertRaises(TypeError, lb.get) self.assertRaises(TclError, lb.get, 'end', 'noindex') self.assertRaises(TypeError, lb.get, 1, 2, 3) self.assertRaises(TclError, lb.get, 2.4) @add_standard_options(PixelSizeTests, StandardOptionsTests) class ScaleTest(AbstractWidgetTest, unittest.TestCase): OPTIONS = ( 'activebackground', 'background', 'bigincrement', 'borderwidth', 'command', 'cursor', 'digits', 'font', 'foreground', 'from', 'highlightbackground', 'highlightcolor', 'highlightthickness', 'label', 'length', 'orient', 'relief', 'repeatdelay', 'repeatinterval', 'resolution', 'showvalue', 'sliderlength', 'sliderrelief', 'state', 'takefocus', 'tickinterval', 'to', 'troughcolor', 'variable', 'width', ) default_orient = 'vertical' def create(self, **kwargs): return tkinter.Scale(self.root, **kwargs) def test_configure_bigincrement(self): widget = self.create() self.checkFloatParam(widget, 'bigincrement', 12.4, 23.6, -5) def test_configure_digits(self): widget = self.create() self.checkIntegerParam(widget, 'digits', 5, 0) def test_configure_from(self): widget = self.create() conv = False if get_tk_patchlevel() >= (8, 6, 10) else float_round self.checkFloatParam(widget, 'from', 100, 14.9, 15.1, conv=conv) def test_configure_label(self): widget = self.create() self.checkParam(widget, 'label', 'any string') self.checkParam(widget, 'label', '') def test_configure_length(self): widget = self.create() self.checkPixelsParam(widget, 'length', 130, 131.2, 135.6, '5i') def test_configure_resolution(self): widget = self.create() self.checkFloatParam(widget, 'resolution', 4.2, 0, 6.7, -2) def test_configure_showvalue(self): widget = self.create() self.checkBooleanParam(widget, 'showvalue') def test_configure_sliderlength(self): widget = self.create() self.checkPixelsParam(widget, 'sliderlength', 10, 11.2, 15.6, -3, '3m') def test_configure_sliderrelief(self): widget = self.create() self.checkReliefParam(widget, 'sliderrelief') def test_configure_tickinterval(self): widget = self.create() self.checkFloatParam(widget, 'tickinterval', 1, 4.3, 7.6, 0, conv=float_round) self.checkParam(widget, 'tickinterval', -2, expected=2, conv=float_round) def test_configure_to(self): widget = self.create() self.checkFloatParam(widget, 'to', 300, 14.9, 15.1, -10, conv=float_round) @add_standard_options(PixelSizeTests, StandardOptionsTests) class ScrollbarTest(AbstractWidgetTest, unittest.TestCase): OPTIONS = ( 'activebackground', 'activerelief', 'background', 'borderwidth', 'command', 'cursor', 'elementborderwidth', 'highlightbackground', 'highlightcolor', 'highlightthickness', 'jump', 'orient', 'relief', 'repeatdelay', 'repeatinterval', 'takefocus', 'troughcolor', 'width', ) _conv_pixels = round _stringify = True default_orient = 'vertical' def create(self, **kwargs): return tkinter.Scrollbar(self.root, **kwargs) def test_configure_activerelief(self): widget = self.create() self.checkReliefParam(widget, 'activerelief') def test_configure_elementborderwidth(self): widget = self.create() self.checkPixelsParam(widget, 'elementborderwidth', 4.3, 5.6, -2, '1m') def test_configure_orient(self): widget = self.create() self.checkEnumParam(widget, 'orient', 'vertical', 'horizontal', errmsg='bad orientation "{}": must be vertical or horizontal') def test_activate(self): sb = self.create() for e in ('arrow1', 'slider', 'arrow2'): sb.activate(e) self.assertEqual(sb.activate(), e) sb.activate('') self.assertIsNone(sb.activate()) self.assertRaises(TypeError, sb.activate, 'arrow1', 'arrow2') def test_set(self): sb = self.create() sb.set(0.2, 0.4) self.assertEqual(sb.get(), (0.2, 0.4)) self.assertRaises(TclError, sb.set, 'abc', 'def') self.assertRaises(TclError, sb.set, 0.6, 'def') self.assertRaises(TclError, sb.set, 0.6, None) self.assertRaises(TypeError, sb.set, 0.6) self.assertRaises(TypeError, sb.set, 0.6, 0.7, 0.8) @add_standard_options(StandardOptionsTests) class PanedWindowTest(AbstractWidgetTest, unittest.TestCase): OPTIONS = ( 'background', 'borderwidth', 'cursor', 'handlepad', 'handlesize', 'height', 'opaqueresize', 'orient', 'proxybackground', 'proxyborderwidth', 'proxyrelief', 'relief', 'sashcursor', 'sashpad', 'sashrelief', 'sashwidth', 'showhandle', 'width', ) default_orient = 'horizontal' def create(self, **kwargs): return tkinter.PanedWindow(self.root, **kwargs) def test_configure_handlepad(self): widget = self.create() self.checkPixelsParam(widget, 'handlepad', 5, 6.4, 7.6, -3, '1m') def test_configure_handlesize(self): widget = self.create() self.checkPixelsParam(widget, 'handlesize', 8, 9.4, 10.6, -3, '2m', conv=noconv) def test_configure_height(self): widget = self.create() self.checkPixelsParam(widget, 'height', 100, 101.2, 102.6, -100, 0, '1i', conv=noconv) def test_configure_opaqueresize(self): widget = self.create() self.checkBooleanParam(widget, 'opaqueresize') @requires_tcl(8, 6, 5) def test_configure_proxybackground(self): widget = self.create() self.checkColorParam(widget, 'proxybackground') @requires_tcl(8, 6, 5) def test_configure_proxyborderwidth(self): widget = self.create() self.checkPixelsParam(widget, 'proxyborderwidth', 0, 1.3, 2.9, 6, -2, '10p', conv=noconv) @requires_tcl(8, 6, 5) def test_configure_proxyrelief(self): widget = self.create() self.checkReliefParam(widget, 'proxyrelief') def test_configure_sashcursor(self): widget = self.create() self.checkCursorParam(widget, 'sashcursor') def test_configure_sashpad(self): widget = self.create() self.checkPixelsParam(widget, 'sashpad', 8, 1.3, 2.6, -2, '2m') def test_configure_sashrelief(self): widget = self.create() self.checkReliefParam(widget, 'sashrelief') def test_configure_sashwidth(self): widget = self.create() self.checkPixelsParam(widget, 'sashwidth', 10, 11.1, 15.6, -3, '1m', conv=noconv) def test_configure_showhandle(self): widget = self.create() self.checkBooleanParam(widget, 'showhandle') def test_configure_width(self): widget = self.create() self.checkPixelsParam(widget, 'width', 402, 403.4, 404.6, -402, 0, '5i', conv=noconv) def create2(self): p = self.create() b = tkinter.Button(p) c = tkinter.Button(p) p.add(b) p.add(c) return p, b, c def test_paneconfigure(self): p, b, c = self.create2() self.assertRaises(TypeError, p.paneconfigure) d = p.paneconfigure(b) self.assertIsInstance(d, dict) for k, v in d.items(): self.assertEqual(len(v), 5) self.assertEqual(v, p.paneconfigure(b, k)) self.assertEqual(v[4], p.panecget(b, k)) def check_paneconfigure(self, p, b, name, value, expected, stringify=False): conv = lambda x: x if not self.wantobjects or stringify: expected = str(expected) if self.wantobjects and stringify: conv = str p.paneconfigure(b, **{name: value}) self.assertEqual(conv(p.paneconfigure(b, name)[4]), expected) self.assertEqual(conv(p.panecget(b, name)), expected) def check_paneconfigure_bad(self, p, b, name, msg): with self.assertRaisesRegex(TclError, msg): p.paneconfigure(b, **{name: 'badValue'}) def test_paneconfigure_after(self): p, b, c = self.create2() self.check_paneconfigure(p, b, 'after', c, str(c)) self.check_paneconfigure_bad(p, b, 'after', 'bad window path name "badValue"') def test_paneconfigure_before(self): p, b, c = self.create2() self.check_paneconfigure(p, b, 'before', c, str(c)) self.check_paneconfigure_bad(p, b, 'before', 'bad window path name "badValue"') def test_paneconfigure_height(self): p, b, c = self.create2() self.check_paneconfigure(p, b, 'height', 10, 10, stringify=get_tk_patchlevel() < (8, 5, 11)) self.check_paneconfigure_bad(p, b, 'height', 'bad screen distance "badValue"') @requires_tcl(8, 5) def test_paneconfigure_hide(self): p, b, c = self.create2() self.check_paneconfigure(p, b, 'hide', False, 0) self.check_paneconfigure_bad(p, b, 'hide', 'expected boolean value but got "badValue"') def test_paneconfigure_minsize(self): p, b, c = self.create2() self.check_paneconfigure(p, b, 'minsize', 10, 10) self.check_paneconfigure_bad(p, b, 'minsize', 'bad screen distance "badValue"') def test_paneconfigure_padx(self): p, b, c = self.create2() self.check_paneconfigure(p, b, 'padx', 1.3, 1) self.check_paneconfigure_bad(p, b, 'padx', 'bad screen distance "badValue"') def test_paneconfigure_pady(self): p, b, c = self.create2() self.check_paneconfigure(p, b, 'pady', 1.3, 1) self.check_paneconfigure_bad(p, b, 'pady', 'bad screen distance "badValue"') def test_paneconfigure_sticky(self): p, b, c = self.create2() self.check_paneconfigure(p, b, 'sticky', 'nsew', 'nesw') self.check_paneconfigure_bad(p, b, 'sticky', 'bad stickyness value "badValue": must ' 'be a string containing zero or more of ' 'n, e, s, and w') @requires_tcl(8, 5) def test_paneconfigure_stretch(self): p, b, c = self.create2() self.check_paneconfigure(p, b, 'stretch', 'alw', 'always') self.check_paneconfigure_bad(p, b, 'stretch', 'bad stretch "badValue": must be ' 'always, first, last, middle, or never') def test_paneconfigure_width(self): p, b, c = self.create2() self.check_paneconfigure(p, b, 'width', 10, 10, stringify=get_tk_patchlevel() < (8, 5, 11)) self.check_paneconfigure_bad(p, b, 'width', 'bad screen distance "badValue"') @add_standard_options(StandardOptionsTests) class MenuTest(AbstractWidgetTest, unittest.TestCase): OPTIONS = ( 'activebackground', 'activeborderwidth', 'activeforeground', 'background', 'borderwidth', 'cursor', 'disabledforeground', 'font', 'foreground', 'postcommand', 'relief', 'selectcolor', 'takefocus', 'tearoff', 'tearoffcommand', 'title', 'type', ) _conv_pixels = noconv def create(self, **kwargs): return tkinter.Menu(self.root, **kwargs) def test_configure_postcommand(self): widget = self.create() self.checkCommandParam(widget, 'postcommand') def test_configure_tearoff(self): widget = self.create() self.checkBooleanParam(widget, 'tearoff') def test_configure_tearoffcommand(self): widget = self.create() self.checkCommandParam(widget, 'tearoffcommand') def test_configure_title(self): widget = self.create() self.checkParam(widget, 'title', 'any string') def test_configure_type(self): widget = self.create() self.checkEnumParam(widget, 'type', 'normal', 'tearoff', 'menubar') def test_entryconfigure(self): m1 = self.create() m1.add_command(label='test') self.assertRaises(TypeError, m1.entryconfigure) with self.assertRaisesRegex(TclError, 'bad menu entry index "foo"'): m1.entryconfigure('foo') d = m1.entryconfigure(1) self.assertIsInstance(d, dict) for k, v in d.items(): self.assertIsInstance(k, str) self.assertIsInstance(v, tuple) self.assertEqual(len(v), 5) self.assertEqual(v[0], k) self.assertEqual(m1.entrycget(1, k), v[4]) m1.destroy() def test_entryconfigure_label(self): m1 = self.create() m1.add_command(label='test') self.assertEqual(m1.entrycget(1, 'label'), 'test') m1.entryconfigure(1, label='changed') self.assertEqual(m1.entrycget(1, 'label'), 'changed') def test_entryconfigure_variable(self): m1 = self.create() v1 = tkinter.BooleanVar(self.root) v2 = tkinter.BooleanVar(self.root) m1.add_checkbutton(variable=v1, onvalue=True, offvalue=False, label='Nonsense') self.assertEqual(str(m1.entrycget(1, 'variable')), str(v1)) m1.entryconfigure(1, variable=v2) self.assertEqual(str(m1.entrycget(1, 'variable')), str(v2)) @add_standard_options(PixelSizeTests, StandardOptionsTests) class MessageTest(AbstractWidgetTest, unittest.TestCase): OPTIONS = ( 'anchor', 'aspect', 'background', 'borderwidth', 'cursor', 'font', 'foreground', 'highlightbackground', 'highlightcolor', 'highlightthickness', 'justify', 'padx', 'pady', 'relief', 'takefocus', 'text', 'textvariable', 'width', ) _conv_pad_pixels = noconv def create(self, **kwargs): return tkinter.Message(self.root, **kwargs) def test_configure_aspect(self): widget = self.create() self.checkIntegerParam(widget, 'aspect', 250, 0, -300) class DefaultRootTest(AbstractDefaultRootTest, unittest.TestCase): def test_frame(self): self._test_widget(tkinter.Frame) def test_label(self): self._test_widget(tkinter.Label) tests_gui = ( ButtonTest, CanvasTest, CheckbuttonTest, EntryTest, FrameTest, LabelFrameTest,LabelTest, ListboxTest, MenubuttonTest, MenuTest, MessageTest, OptionMenuTest, PanedWindowTest, RadiobuttonTest, ScaleTest, ScrollbarTest, SpinboxTest, TextTest, ToplevelTest, DefaultRootTest, ) if __name__ == '__main__': unittest.main() ================================================ FILE: modules/urllib3/connection.py ================================================ from __future__ import absolute_import import datetime import logging import os import re import socket import warnings from socket import error as SocketError from socket import timeout as SocketTimeout from .packages import six from .packages.six.moves.http_client import HTTPConnection as _HTTPConnection from .packages.six.moves.http_client import HTTPException # noqa: F401 from .util.proxy import create_proxy_ssl_context try: # Compiled with SSL? import ssl BaseSSLError = ssl.SSLError except (ImportError, AttributeError): # Platform-specific: No SSL. ssl = None class BaseSSLError(BaseException): pass try: # Python 3: not a no-op, we're adding this to the namespace so it can be imported. ConnectionError = ConnectionError except NameError: # Python 2 class ConnectionError(Exception): pass try: # Python 3: # Not a no-op, we're adding this to the namespace so it can be imported. BrokenPipeError = BrokenPipeError except NameError: # Python 2: class BrokenPipeError(Exception): pass from ._collections import HTTPHeaderDict # noqa (historical, removed in v2) from ._version import __version__ from .exceptions import ( ConnectTimeoutError, NewConnectionError, SubjectAltNameWarning, SystemTimeWarning, ) from .packages.ssl_match_hostname import CertificateError, match_hostname from .util import SKIP_HEADER, SKIPPABLE_HEADERS, connection from .util.ssl_ import ( assert_fingerprint, create_urllib3_context, resolve_cert_reqs, resolve_ssl_version, ssl_wrap_socket, ) log = logging.getLogger(__name__) port_by_scheme = {"http": 80, "https": 443} # When it comes time to update this value as a part of regular maintenance # (ie test_recent_date is failing) update it to ~6 months before the current date. RECENT_DATE = datetime.date(2020, 7, 1) _CONTAINS_CONTROL_CHAR_RE = re.compile(r"[^-!#$%&'*+.^_`|~0-9a-zA-Z]") class HTTPConnection(_HTTPConnection, object): """ Based on :class:`http.client.HTTPConnection` but provides an extra constructor backwards-compatibility layer between older and newer Pythons. Additional keyword parameters are used to configure attributes of the connection. Accepted parameters include: - ``strict``: See the documentation on :class:`urllib3.connectionpool.HTTPConnectionPool` - ``source_address``: Set the source address for the current connection. - ``socket_options``: Set specific options on the underlying socket. If not specified, then defaults are loaded from ``HTTPConnection.default_socket_options`` which includes disabling Nagle's algorithm (sets TCP_NODELAY to 1) unless the connection is behind a proxy. For example, if you wish to enable TCP Keep Alive in addition to the defaults, you might pass: .. code-block:: python HTTPConnection.default_socket_options + [ (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1), ] Or you may want to disable the defaults by passing an empty list (e.g., ``[]``). """ default_port = port_by_scheme["http"] #: Disable Nagle's algorithm by default. #: ``[(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)]`` default_socket_options = [(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)] #: Whether this connection verifies the host's certificate. is_verified = False def __init__(self, *args, **kw): if not six.PY2: kw.pop("strict", None) # Pre-set source_address. self.source_address = kw.get("source_address") #: The socket options provided by the user. If no options are #: provided, we use the default options. self.socket_options = kw.pop("socket_options", self.default_socket_options) # Proxy options provided by the user. self.proxy = kw.pop("proxy", None) self.proxy_config = kw.pop("proxy_config", None) _HTTPConnection.__init__(self, *args, **kw) @property def host(self): """ Getter method to remove any trailing dots that indicate the hostname is an FQDN. In general, SSL certificates don't include the trailing dot indicating a fully-qualified domain name, and thus, they don't validate properly when checked against a domain name that includes the dot. In addition, some servers may not expect to receive the trailing dot when provided. However, the hostname with trailing dot is critical to DNS resolution; doing a lookup with the trailing dot will properly only resolve the appropriate FQDN, whereas a lookup without a trailing dot will search the system's search domain list. Thus, it's important to keep the original host around for use only in those cases where it's appropriate (i.e., when doing DNS lookup to establish the actual TCP connection across which we're going to send HTTP requests). """ return self._dns_host.rstrip(".") @host.setter def host(self, value): """ Setter for the `host` property. We assume that only urllib3 uses the _dns_host attribute; httplib itself only uses `host`, and it seems reasonable that other libraries follow suit. """ self._dns_host = value def _new_conn(self): """Establish a socket connection and set nodelay settings on it. :return: New socket connection. """ extra_kw = {} if self.source_address: extra_kw["source_address"] = self.source_address if self.socket_options: extra_kw["socket_options"] = self.socket_options try: conn = connection.create_connection( (self._dns_host, self.port), self.timeout, **extra_kw ) except SocketTimeout: raise ConnectTimeoutError( self, "Connection to %s timed out. (connect timeout=%s)" % (self.host, self.timeout), ) except SocketError as e: raise NewConnectionError( self, "Failed to establish a new connection: %s" % e ) return conn def _is_using_tunnel(self): # Google App Engine's httplib does not define _tunnel_host return getattr(self, "_tunnel_host", None) def _prepare_conn(self, conn): self.sock = conn if self._is_using_tunnel(): # TODO: Fix tunnel so it doesn't depend on self.sock state. self._tunnel() # Mark this connection as not reusable self.auto_open = 0 def connect(self): conn = self._new_conn() self._prepare_conn(conn) def putrequest(self, method, url, *args, **kwargs): """""" # Empty docstring because the indentation of CPython's implementation # is broken but we don't want this method in our documentation. match = _CONTAINS_CONTROL_CHAR_RE.search(method) if match: raise ValueError( "Method cannot contain non-token characters %r (found at least %r)" % (method, match.group()) ) return _HTTPConnection.putrequest(self, method, url, *args, **kwargs) def putheader(self, header, *values): """""" if not any(isinstance(v, str) and v == SKIP_HEADER for v in values): _HTTPConnection.putheader(self, header, *values) elif six.ensure_str(header.lower()) not in SKIPPABLE_HEADERS: raise ValueError( "urllib3.util.SKIP_HEADER only supports '%s'" % ("', '".join(map(str.title, sorted(SKIPPABLE_HEADERS))),) ) def request(self, method, url, body=None, headers=None): if headers is None: headers = {} else: # Avoid modifying the headers passed into .request() headers = headers.copy() if "user-agent" not in (six.ensure_str(k.lower()) for k in headers): headers["User-Agent"] = _get_default_user_agent() super(HTTPConnection, self).request(method, url, body=body, headers=headers) def request_chunked(self, method, url, body=None, headers=None): """ Alternative to the common request method, which sends the body with chunked encoding and not as one block """ headers = headers or {} header_keys = set([six.ensure_str(k.lower()) for k in headers]) skip_accept_encoding = "accept-encoding" in header_keys skip_host = "host" in header_keys self.putrequest( method, url, skip_accept_encoding=skip_accept_encoding, skip_host=skip_host ) if "user-agent" not in header_keys: self.putheader("User-Agent", _get_default_user_agent()) for header, value in headers.items(): self.putheader(header, value) if "transfer-encoding" not in headers: self.putheader("Transfer-Encoding", "chunked") self.endheaders() if body is not None: stringish_types = six.string_types + (bytes,) if isinstance(body, stringish_types): body = (body,) for chunk in body: if not chunk: continue if not isinstance(chunk, bytes): chunk = chunk.encode("utf8") len_str = hex(len(chunk))[2:] to_send = bytearray(len_str.encode()) to_send += b"\r\n" to_send += chunk to_send += b"\r\n" self.send(to_send) # After the if clause, to always have a closed body self.send(b"0\r\n\r\n") class HTTPSConnection(HTTPConnection): """ Many of the parameters to this constructor are passed to the underlying SSL socket by means of :py:func:`urllib3.util.ssl_wrap_socket`. """ default_port = port_by_scheme["https"] cert_reqs = None ca_certs = None ca_cert_dir = None ca_cert_data = None ssl_version = None assert_fingerprint = None tls_in_tls_required = False def __init__( self, host, port=None, key_file=None, cert_file=None, key_password=None, strict=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, ssl_context=None, server_hostname=None, **kw ): HTTPConnection.__init__(self, host, port, strict=strict, timeout=timeout, **kw) self.key_file = key_file self.cert_file = cert_file self.key_password = key_password self.ssl_context = ssl_context self.server_hostname = server_hostname # Required property for Google AppEngine 1.9.0 which otherwise causes # HTTPS requests to go out as HTTP. (See Issue #356) self._protocol = "https" def set_cert( self, key_file=None, cert_file=None, cert_reqs=None, key_password=None, ca_certs=None, assert_hostname=None, assert_fingerprint=None, ca_cert_dir=None, ca_cert_data=None, ): """ This method should only be called once, before the connection is used. """ # If cert_reqs is not provided we'll assume CERT_REQUIRED unless we also # have an SSLContext object in which case we'll use its verify_mode. if cert_reqs is None: if self.ssl_context is not None: cert_reqs = self.ssl_context.verify_mode else: cert_reqs = resolve_cert_reqs(None) self.key_file = key_file self.cert_file = cert_file self.cert_reqs = cert_reqs self.key_password = key_password self.assert_hostname = assert_hostname self.assert_fingerprint = assert_fingerprint self.ca_certs = ca_certs and os.path.expanduser(ca_certs) self.ca_cert_dir = ca_cert_dir and os.path.expanduser(ca_cert_dir) self.ca_cert_data = ca_cert_data def connect(self): # Add certificate verification conn = self._new_conn() hostname = self.host tls_in_tls = False if self._is_using_tunnel(): if self.tls_in_tls_required: conn = self._connect_tls_proxy(hostname, conn) tls_in_tls = True self.sock = conn # Calls self._set_hostport(), so self.host is # self._tunnel_host below. self._tunnel() # Mark this connection as not reusable self.auto_open = 0 # Override the host with the one we're requesting data from. hostname = self._tunnel_host server_hostname = hostname if self.server_hostname is not None: server_hostname = self.server_hostname is_time_off = datetime.date.today() < RECENT_DATE if is_time_off: warnings.warn( ( "System time is way off (before {0}). This will probably " "lead to SSL verification errors" ).format(RECENT_DATE), SystemTimeWarning, ) # Wrap socket using verification with the root certs in # trusted_root_certs default_ssl_context = False if self.ssl_context is None: default_ssl_context = True self.ssl_context = create_urllib3_context( ssl_version=resolve_ssl_version(self.ssl_version), cert_reqs=resolve_cert_reqs(self.cert_reqs), ) context = self.ssl_context context.verify_mode = resolve_cert_reqs(self.cert_reqs) # Try to load OS default certs if none are given. # Works well on Windows (requires Python3.4+) if ( not self.ca_certs and not self.ca_cert_dir and not self.ca_cert_data and default_ssl_context and hasattr(context, "load_default_certs") ): context.load_default_certs() self.sock = ssl_wrap_socket( sock=conn, keyfile=self.key_file, certfile=self.cert_file, key_password=self.key_password, ca_certs=self.ca_certs, ca_cert_dir=self.ca_cert_dir, ca_cert_data=self.ca_cert_data, server_hostname=server_hostname, ssl_context=context, tls_in_tls=tls_in_tls, ) # If we're using all defaults and the connection # is TLSv1 or TLSv1.1 we throw a DeprecationWarning # for the host. if ( default_ssl_context and self.ssl_version is None and hasattr(self.sock, "version") and self.sock.version() in {"TLSv1", "TLSv1.1"} ): warnings.warn( "Negotiating TLSv1/TLSv1.1 by default is deprecated " "and will be disabled in urllib3 v2.0.0. Connecting to " "'%s' with '%s' can be enabled by explicitly opting-in " "with 'ssl_version'" % (self.host, self.sock.version()), DeprecationWarning, ) if self.assert_fingerprint: assert_fingerprint( self.sock.getpeercert(binary_form=True), self.assert_fingerprint ) elif ( context.verify_mode != ssl.CERT_NONE and not getattr(context, "check_hostname", False) and self.assert_hostname is not False ): # While urllib3 attempts to always turn off hostname matching from # the TLS library, this cannot always be done. So we check whether # the TLS Library still thinks it's matching hostnames. cert = self.sock.getpeercert() if not cert.get("subjectAltName", ()): warnings.warn( ( "Certificate for {0} has no `subjectAltName`, falling back to check for a " "`commonName` for now. This feature is being removed by major browsers and " "deprecated by RFC 2818. (See https://github.com/urllib3/urllib3/issues/497 " "for details.)".format(hostname) ), SubjectAltNameWarning, ) _match_hostname(cert, self.assert_hostname or server_hostname) self.is_verified = ( context.verify_mode == ssl.CERT_REQUIRED or self.assert_fingerprint is not None ) def _connect_tls_proxy(self, hostname, conn): """ Establish a TLS connection to the proxy using the provided SSL context. """ proxy_config = self.proxy_config ssl_context = proxy_config.ssl_context if ssl_context: # If the user provided a proxy context, we assume CA and client # certificates have already been set return ssl_wrap_socket( sock=conn, server_hostname=hostname, ssl_context=ssl_context, ) ssl_context = create_proxy_ssl_context( self.ssl_version, self.cert_reqs, self.ca_certs, self.ca_cert_dir, self.ca_cert_data, ) # By default urllib3's SSLContext disables `check_hostname` and uses # a custom check. For proxies we're good with relying on the default # verification. ssl_context.check_hostname = True # If no cert was provided, use only the default options for server # certificate validation return ssl_wrap_socket( sock=conn, ca_certs=self.ca_certs, ca_cert_dir=self.ca_cert_dir, ca_cert_data=self.ca_cert_data, server_hostname=hostname, ssl_context=ssl_context, ) def _match_hostname(cert, asserted_hostname): try: match_hostname(cert, asserted_hostname) except CertificateError as e: log.warning( "Certificate did not match expected hostname: %s. Certificate: %s", asserted_hostname, cert, ) # Add cert to exception and reraise so client code can inspect # the cert when catching the exception, if they want to e._peer_cert = cert raise def _get_default_user_agent(): return "python-urllib3/%s" % __version__ class DummyConnection(object): """Used to detect a failed ConnectionCls import.""" pass if not ssl: HTTPSConnection = DummyConnection # noqa: F811 VerifiedHTTPSConnection = HTTPSConnection ================================================ FILE: modules/urllib3/connectionpool.py ================================================ from __future__ import absolute_import import errno import logging import socket import sys import warnings from socket import error as SocketError from socket import timeout as SocketTimeout from .connection import ( BaseSSLError, BrokenPipeError, DummyConnection, HTTPConnection, HTTPException, HTTPSConnection, VerifiedHTTPSConnection, port_by_scheme, ) from .exceptions import ( ClosedPoolError, EmptyPoolError, HeaderParsingError, HostChangedError, InsecureRequestWarning, LocationValueError, MaxRetryError, NewConnectionError, ProtocolError, ProxyError, ReadTimeoutError, SSLError, TimeoutError, ) from .packages import six from .packages.six.moves import queue from .packages.ssl_match_hostname import CertificateError from .request import RequestMethods from .response import HTTPResponse from .util.connection import is_connection_dropped from .util.proxy import connection_requires_http_tunnel from .util.queue import LifoQueue from .util.request import set_file_position from .util.response import assert_header_parsing from .util.retry import Retry from .util.timeout import Timeout from .util.url import Url, _encode_target from .util.url import _normalize_host as normalize_host from .util.url import get_host, parse_url xrange = six.moves.xrange log = logging.getLogger(__name__) _Default = object() # Pool objects class ConnectionPool(object): """ Base class for all connection pools, such as :class:`.HTTPConnectionPool` and :class:`.HTTPSConnectionPool`. .. note:: ConnectionPool.urlopen() does not normalize or percent-encode target URIs which is useful if your target server doesn't support percent-encoded target URIs. """ scheme = None QueueCls = LifoQueue def __init__(self, host, port=None): if not host: raise LocationValueError("No host specified.") self.host = _normalize_host(host, scheme=self.scheme) self._proxy_host = host.lower() self.port = port def __str__(self): return "%s(host=%r, port=%r)" % (type(self).__name__, self.host, self.port) def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): self.close() # Return False to re-raise any potential exceptions return False def close(self): """ Close all pooled connections and disable the pool. """ pass # This is taken from http://hg.python.org/cpython/file/7aaba721ebc0/Lib/socket.py#l252 _blocking_errnos = {errno.EAGAIN, errno.EWOULDBLOCK} class HTTPConnectionPool(ConnectionPool, RequestMethods): """ Thread-safe connection pool for one host. :param host: Host used for this HTTP Connection (e.g. "localhost"), passed into :class:`http.client.HTTPConnection`. :param port: Port used for this HTTP Connection (None is equivalent to 80), passed into :class:`http.client.HTTPConnection`. :param strict: Causes BadStatusLine to be raised if the status line can't be parsed as a valid HTTP/1.0 or 1.1 status line, passed into :class:`http.client.HTTPConnection`. .. note:: Only works in Python 2. This parameter is ignored in Python 3. :param timeout: Socket timeout in seconds for each individual connection. This can be a float or integer, which sets the timeout for the HTTP request, or an instance of :class:`urllib3.util.Timeout` which gives you more fine-grained control over request timeouts. After the constructor has been parsed, this is always a `urllib3.util.Timeout` object. :param maxsize: Number of connections to save that can be reused. More than 1 is useful in multithreaded situations. If ``block`` is set to False, more connections will be created but they will not be saved once they've been used. :param block: If set to True, no more than ``maxsize`` connections will be used at a time. When no free connections are available, the call will block until a connection has been released. This is a useful side effect for particular multithreaded situations where one does not want to use more than maxsize connections per host to prevent flooding. :param headers: Headers to include with all requests, unless other headers are given explicitly. :param retries: Retry configuration to use by default with requests in this pool. :param _proxy: Parsed proxy URL, should not be used directly, instead, see :class:`urllib3.ProxyManager` :param _proxy_headers: A dictionary with proxy headers, should not be used directly, instead, see :class:`urllib3.ProxyManager` :param \\**conn_kw: Additional parameters are used to create fresh :class:`urllib3.connection.HTTPConnection`, :class:`urllib3.connection.HTTPSConnection` instances. """ scheme = "http" ConnectionCls = HTTPConnection ResponseCls = HTTPResponse def __init__( self, host, port=None, strict=False, timeout=Timeout.DEFAULT_TIMEOUT, maxsize=1, block=False, headers=None, retries=None, _proxy=None, _proxy_headers=None, _proxy_config=None, **conn_kw ): ConnectionPool.__init__(self, host, port) RequestMethods.__init__(self, headers) self.strict = strict if not isinstance(timeout, Timeout): timeout = Timeout.from_float(timeout) if retries is None: retries = Retry.DEFAULT self.timeout = timeout self.retries = retries self.pool = self.QueueCls(maxsize) self.block = block self.proxy = _proxy self.proxy_headers = _proxy_headers or {} self.proxy_config = _proxy_config # Fill the queue up so that doing get() on it will block properly for _ in xrange(maxsize): self.pool.put(None) # These are mostly for testing and debugging purposes. self.num_connections = 0 self.num_requests = 0 self.conn_kw = conn_kw if self.proxy: # Enable Nagle's algorithm for proxies, to avoid packet fragmentation. # We cannot know if the user has added default socket options, so we cannot replace the # list. self.conn_kw.setdefault("socket_options", []) self.conn_kw["proxy"] = self.proxy self.conn_kw["proxy_config"] = self.proxy_config def _new_conn(self): """ Return a fresh :class:`HTTPConnection`. """ self.num_connections += 1 log.debug( "Starting new HTTP connection (%d): %s:%s", self.num_connections, self.host, self.port or "80", ) conn = self.ConnectionCls( host=self.host, port=self.port, timeout=self.timeout.connect_timeout, strict=self.strict, **self.conn_kw ) return conn def _get_conn(self, timeout=None): """ Get a connection. Will return a pooled connection if one is available. If no connections are available and :prop:`.block` is ``False``, then a fresh connection is returned. :param timeout: Seconds to wait before giving up and raising :class:`urllib3.exceptions.EmptyPoolError` if the pool is empty and :prop:`.block` is ``True``. """ conn = None try: conn = self.pool.get(block=self.block, timeout=timeout) except AttributeError: # self.pool is None raise ClosedPoolError(self, "Pool is closed.") except queue.Empty: if self.block: raise EmptyPoolError( self, "Pool reached maximum size and no more connections are allowed.", ) pass # Oh well, we'll create a new connection then # If this is a persistent connection, check if it got disconnected if conn and is_connection_dropped(conn): log.debug("Resetting dropped connection: %s", self.host) conn.close() if getattr(conn, "auto_open", 1) == 0: # This is a proxied connection that has been mutated by # http.client._tunnel() and cannot be reused (since it would # attempt to bypass the proxy) conn = None return conn or self._new_conn() def _put_conn(self, conn): """ Put a connection back into the pool. :param conn: Connection object for the current host and port as returned by :meth:`._new_conn` or :meth:`._get_conn`. If the pool is already full, the connection is closed and discarded because we exceeded maxsize. If connections are discarded frequently, then maxsize should be increased. If the pool is closed, then the connection will be closed and discarded. """ try: self.pool.put(conn, block=False) return # Everything is dandy, done. except AttributeError: # self.pool is None. pass except queue.Full: # This should never happen if self.block == True log.warning("Connection pool is full, discarding connection: %s", self.host) # Connection never got put back into the pool, close it. if conn: conn.close() def _validate_conn(self, conn): """ Called right before a request is made, after the socket is created. """ pass def _prepare_proxy(self, conn): # Nothing to do for HTTP connections. pass def _get_timeout(self, timeout): """ Helper that always returns a :class:`urllib3.util.Timeout` """ if timeout is _Default: return self.timeout.clone() if isinstance(timeout, Timeout): return timeout.clone() else: # User passed us an int/float. This is for backwards compatibility, # can be removed later return Timeout.from_float(timeout) def _raise_timeout(self, err, url, timeout_value): """Is the error actually a timeout? Will raise a ReadTimeout or pass""" if isinstance(err, SocketTimeout): raise ReadTimeoutError( self, url, "Read timed out. (read timeout=%s)" % timeout_value ) # See the above comment about EAGAIN in Python 3. In Python 2 we have # to specifically catch it and throw the timeout error if hasattr(err, "errno") and err.errno in _blocking_errnos: raise ReadTimeoutError( self, url, "Read timed out. (read timeout=%s)" % timeout_value ) # Catch possible read timeouts thrown as SSL errors. If not the # case, rethrow the original. We need to do this because of: # http://bugs.python.org/issue10272 if "timed out" in str(err) or "did not complete (read)" in str( err ): # Python < 2.7.4 raise ReadTimeoutError( self, url, "Read timed out. (read timeout=%s)" % timeout_value ) def _make_request( self, conn, method, url, timeout=_Default, chunked=False, **httplib_request_kw ): """ Perform a request on a given urllib connection object taken from our pool. :param conn: a connection from one of our connection pools :param timeout: Socket timeout in seconds for the request. This can be a float or integer, which will set the same timeout value for the socket connect and the socket read, or an instance of :class:`urllib3.util.Timeout`, which gives you more fine-grained control over your timeouts. """ self.num_requests += 1 timeout_obj = self._get_timeout(timeout) timeout_obj.start_connect() conn.timeout = timeout_obj.connect_timeout # Trigger any extra validation we need to do. try: self._validate_conn(conn) except (SocketTimeout, BaseSSLError) as e: # Py2 raises this as a BaseSSLError, Py3 raises it as socket timeout. self._raise_timeout(err=e, url=url, timeout_value=conn.timeout) raise # conn.request() calls http.client.*.request, not the method in # urllib3.request. It also calls makefile (recv) on the socket. try: if chunked: conn.request_chunked(method, url, **httplib_request_kw) else: conn.request(method, url, **httplib_request_kw) # We are swallowing BrokenPipeError (errno.EPIPE) since the server is # legitimately able to close the connection after sending a valid response. # With this behaviour, the received response is still readable. except BrokenPipeError: # Python 3 pass except IOError as e: # Python 2 and macOS/Linux # EPIPE and ESHUTDOWN are BrokenPipeError on Python 2, and EPROTOTYPE is needed on macOS # https://erickt.github.io/blog/2014/11/19/adventures-in-debugging-a-potential-osx-kernel-bug/ if e.errno not in { errno.EPIPE, errno.ESHUTDOWN, errno.EPROTOTYPE, }: raise # Reset the timeout for the recv() on the socket read_timeout = timeout_obj.read_timeout # App Engine doesn't have a sock attr if getattr(conn, "sock", None): # In Python 3 socket.py will catch EAGAIN and return None when you # try and read into the file pointer created by http.client, which # instead raises a BadStatusLine exception. Instead of catching # the exception and assuming all BadStatusLine exceptions are read # timeouts, check for a zero timeout before making the request. if read_timeout == 0: raise ReadTimeoutError( self, url, "Read timed out. (read timeout=%s)" % read_timeout ) if read_timeout is Timeout.DEFAULT_TIMEOUT: conn.sock.settimeout(socket.getdefaulttimeout()) else: # None or a value conn.sock.settimeout(read_timeout) # Receive the response from the server try: try: # Python 2.7, use buffering of HTTP responses httplib_response = conn.getresponse(buffering=True) except TypeError: # Python 3 try: httplib_response = conn.getresponse() except BaseException as e: # Remove the TypeError from the exception chain in # Python 3 (including for exceptions like SystemExit). # Otherwise it looks like a bug in the code. six.raise_from(e, None) except (SocketTimeout, BaseSSLError, SocketError) as e: self._raise_timeout(err=e, url=url, timeout_value=read_timeout) raise # AppEngine doesn't have a version attr. http_version = getattr(conn, "_http_vsn_str", "HTTP/?") log.debug( '%s://%s:%s "%s %s %s" %s %s', self.scheme, self.host, self.port, method, url, http_version, httplib_response.status, httplib_response.length, ) try: assert_header_parsing(httplib_response.msg) except (HeaderParsingError, TypeError) as hpe: # Platform-specific: Python 3 log.warning( "Failed to parse headers (url=%s): %s", self._absolute_url(url), hpe, exc_info=True, ) return httplib_response def _absolute_url(self, path): return Url(scheme=self.scheme, host=self.host, port=self.port, path=path).url def close(self): """ Close all pooled connections and disable the pool. """ if self.pool is None: return # Disable access to the pool old_pool, self.pool = self.pool, None try: while True: conn = old_pool.get(block=False) if conn: conn.close() except queue.Empty: pass # Done. def is_same_host(self, url): """ Check if the given ``url`` is a member of the same host as this connection pool. """ if url.startswith("/"): return True # TODO: Add optional support for socket.gethostbyname checking. scheme, host, port = get_host(url) if host is not None: host = _normalize_host(host, scheme=scheme) # Use explicit default port for comparison when none is given if self.port and not port: port = port_by_scheme.get(scheme) elif not self.port and port == port_by_scheme.get(scheme): port = None return (scheme, host, port) == (self.scheme, self.host, self.port) def urlopen( self, method, url, body=None, headers=None, retries=None, redirect=True, assert_same_host=True, timeout=_Default, pool_timeout=None, release_conn=None, chunked=False, body_pos=None, **response_kw ): """ Get a connection from the pool and perform an HTTP request. This is the lowest level call for making a request, so you'll need to specify all the raw details. .. note:: More commonly, it's appropriate to use a convenience method provided by :class:`.RequestMethods`, such as :meth:`request`. .. note:: `release_conn` will only behave as expected if `preload_content=False` because we want to make `preload_content=False` the default behaviour someday soon without breaking backwards compatibility. :param method: HTTP request method (such as GET, POST, PUT, etc.) :param url: The URL to perform the request on. :param body: Data to send in the request body, either :class:`str`, :class:`bytes`, an iterable of :class:`str`/:class:`bytes`, or a file-like object. :param headers: Dictionary of custom headers to send, such as User-Agent, If-None-Match, etc. If None, pool headers are used. If provided, these headers completely replace any pool-specific headers. :param retries: Configure the number of retries to allow before raising a :class:`~urllib3.exceptions.MaxRetryError` exception. Pass ``None`` to retry until you receive a response. Pass a :class:`~urllib3.util.retry.Retry` object for fine-grained control over different types of retries. Pass an integer number to retry connection errors that many times, but no other types of errors. Pass zero to never retry. If ``False``, then retries are disabled and any exception is raised immediately. Also, instead of raising a MaxRetryError on redirects, the redirect response will be returned. :type retries: :class:`~urllib3.util.retry.Retry`, False, or an int. :param redirect: If True, automatically handle redirects (status codes 301, 302, 303, 307, 308). Each redirect counts as a retry. Disabling retries will disable redirect, too. :param assert_same_host: If ``True``, will make sure that the host of the pool requests is consistent else will raise HostChangedError. When ``False``, you can use the pool on an HTTP proxy and request foreign hosts. :param timeout: If specified, overrides the default timeout for this one request. It may be a float (in seconds) or an instance of :class:`urllib3.util.Timeout`. :param pool_timeout: If set and the pool is set to block=True, then this method will block for ``pool_timeout`` seconds and raise EmptyPoolError if no connection is available within the time period. :param release_conn: If False, then the urlopen call will not release the connection back into the pool once a response is received (but will release if you read the entire contents of the response such as when `preload_content=True`). This is useful if you're not preloading the response's content immediately. You will need to call ``r.release_conn()`` on the response ``r`` to return the connection back into the pool. If None, it takes the value of ``response_kw.get('preload_content', True)``. :param chunked: If True, urllib3 will send the body using chunked transfer encoding. Otherwise, urllib3 will send the body using the standard content-length form. Defaults to False. :param int body_pos: Position to seek to in file-like body in the event of a retry or redirect. Typically this won't need to be set because urllib3 will auto-populate the value when needed. :param \\**response_kw: Additional parameters are passed to :meth:`urllib3.response.HTTPResponse.from_httplib` """ parsed_url = parse_url(url) destination_scheme = parsed_url.scheme if headers is None: headers = self.headers if not isinstance(retries, Retry): retries = Retry.from_int(retries, redirect=redirect, default=self.retries) if release_conn is None: release_conn = response_kw.get("preload_content", True) # Check host if assert_same_host and not self.is_same_host(url): raise HostChangedError(self, url, retries) # Ensure that the URL we're connecting to is properly encoded if url.startswith("/"): url = six.ensure_str(_encode_target(url)) else: url = six.ensure_str(parsed_url.url) conn = None # Track whether `conn` needs to be released before # returning/raising/recursing. Update this variable if necessary, and # leave `release_conn` constant throughout the function. That way, if # the function recurses, the original value of `release_conn` will be # passed down into the recursive call, and its value will be respected. # # See issue #651 [1] for details. # # [1] release_this_conn = release_conn http_tunnel_required = connection_requires_http_tunnel( self.proxy, self.proxy_config, destination_scheme ) # Merge the proxy headers. Only done when not using HTTP CONNECT. We # have to copy the headers dict so we can safely change it without those # changes being reflected in anyone else's copy. if not http_tunnel_required: headers = headers.copy() headers.update(self.proxy_headers) # Must keep the exception bound to a separate variable or else Python 3 # complains about UnboundLocalError. err = None # Keep track of whether we cleanly exited the except block. This # ensures we do proper cleanup in finally. clean_exit = False # Rewind body position, if needed. Record current position # for future rewinds in the event of a redirect/retry. body_pos = set_file_position(body, body_pos) try: # Request a connection from the queue. timeout_obj = self._get_timeout(timeout) conn = self._get_conn(timeout=pool_timeout) conn.timeout = timeout_obj.connect_timeout is_new_proxy_conn = self.proxy is not None and not getattr( conn, "sock", None ) if is_new_proxy_conn and http_tunnel_required: self._prepare_proxy(conn) # Make the request on the httplib connection object. httplib_response = self._make_request( conn, method, url, timeout=timeout_obj, body=body, headers=headers, chunked=chunked, ) # If we're going to release the connection in ``finally:``, then # the response doesn't need to know about the connection. Otherwise # it will also try to release it and we'll have a double-release # mess. response_conn = conn if not release_conn else None # Pass method to Response for length checking response_kw["request_method"] = method # Import httplib's response into our own wrapper object response = self.ResponseCls.from_httplib( httplib_response, pool=self, connection=response_conn, retries=retries, **response_kw ) # Everything went great! clean_exit = True except EmptyPoolError: # Didn't get a connection from the pool, no need to clean up clean_exit = True release_this_conn = False raise except ( TimeoutError, HTTPException, SocketError, ProtocolError, BaseSSLError, SSLError, CertificateError, ) as e: # Discard the connection for these exceptions. It will be # replaced during the next _get_conn() call. clean_exit = False if isinstance(e, (BaseSSLError, CertificateError)): e = SSLError(e) elif isinstance(e, (SocketError, NewConnectionError)) and self.proxy: e = ProxyError("Cannot connect to proxy.", e) elif isinstance(e, (SocketError, HTTPException)): e = ProtocolError("Connection aborted.", e) retries = retries.increment( method, url, error=e, _pool=self, _stacktrace=sys.exc_info()[2] ) retries.sleep() # Keep track of the error for the retry warning. err = e finally: if not clean_exit: # We hit some kind of exception, handled or otherwise. We need # to throw the connection away unless explicitly told not to. # Close the connection, set the variable to None, and make sure # we put the None back in the pool to avoid leaking it. conn = conn and conn.close() release_this_conn = True if release_this_conn: # Put the connection back to be reused. If the connection is # expired then it will be None, which will get replaced with a # fresh connection during _get_conn. self._put_conn(conn) if not conn: # Try again log.warning( "Retrying (%r) after connection broken by '%r': %s", retries, err, url ) return self.urlopen( method, url, body, headers, retries, redirect, assert_same_host, timeout=timeout, pool_timeout=pool_timeout, release_conn=release_conn, chunked=chunked, body_pos=body_pos, **response_kw ) # Handle redirect? redirect_location = redirect and response.get_redirect_location() if redirect_location: if response.status == 303: method = "GET" try: retries = retries.increment(method, url, response=response, _pool=self) except MaxRetryError: if retries.raise_on_redirect: response.drain_conn() raise return response response.drain_conn() retries.sleep_for_retry(response) log.debug("Redirecting %s -> %s", url, redirect_location) return self.urlopen( method, redirect_location, body, headers, retries=retries, redirect=redirect, assert_same_host=assert_same_host, timeout=timeout, pool_timeout=pool_timeout, release_conn=release_conn, chunked=chunked, body_pos=body_pos, **response_kw ) # Check if we should retry the HTTP response. has_retry_after = bool(response.getheader("Retry-After")) if retries.is_retry(method, response.status, has_retry_after): try: retries = retries.increment(method, url, response=response, _pool=self) except MaxRetryError: if retries.raise_on_status: response.drain_conn() raise return response response.drain_conn() retries.sleep(response) log.debug("Retry: %s", url) return self.urlopen( method, url, body, headers, retries=retries, redirect=redirect, assert_same_host=assert_same_host, timeout=timeout, pool_timeout=pool_timeout, release_conn=release_conn, chunked=chunked, body_pos=body_pos, **response_kw ) return response class HTTPSConnectionPool(HTTPConnectionPool): """ Same as :class:`.HTTPConnectionPool`, but HTTPS. :class:`.HTTPSConnection` uses one of ``assert_fingerprint``, ``assert_hostname`` and ``host`` in this order to verify connections. If ``assert_hostname`` is False, no verification is done. The ``key_file``, ``cert_file``, ``cert_reqs``, ``ca_certs``, ``ca_cert_dir``, ``ssl_version``, ``key_password`` are only used if :mod:`ssl` is available and are fed into :meth:`urllib3.util.ssl_wrap_socket` to upgrade the connection socket into an SSL socket. """ scheme = "https" ConnectionCls = HTTPSConnection def __init__( self, host, port=None, strict=False, timeout=Timeout.DEFAULT_TIMEOUT, maxsize=1, block=False, headers=None, retries=None, _proxy=None, _proxy_headers=None, key_file=None, cert_file=None, cert_reqs=None, key_password=None, ca_certs=None, ssl_version=None, assert_hostname=None, assert_fingerprint=None, ca_cert_dir=None, **conn_kw ): HTTPConnectionPool.__init__( self, host, port, strict, timeout, maxsize, block, headers, retries, _proxy, _proxy_headers, **conn_kw ) self.key_file = key_file self.cert_file = cert_file self.cert_reqs = cert_reqs self.key_password = key_password self.ca_certs = ca_certs self.ca_cert_dir = ca_cert_dir self.ssl_version = ssl_version self.assert_hostname = assert_hostname self.assert_fingerprint = assert_fingerprint def _prepare_conn(self, conn): """ Prepare the ``connection`` for :meth:`urllib3.util.ssl_wrap_socket` and establish the tunnel if proxy is used. """ if isinstance(conn, VerifiedHTTPSConnection): conn.set_cert( key_file=self.key_file, key_password=self.key_password, cert_file=self.cert_file, cert_reqs=self.cert_reqs, ca_certs=self.ca_certs, ca_cert_dir=self.ca_cert_dir, assert_hostname=self.assert_hostname, assert_fingerprint=self.assert_fingerprint, ) conn.ssl_version = self.ssl_version return conn def _prepare_proxy(self, conn): """ Establishes a tunnel connection through HTTP CONNECT. Tunnel connection is established early because otherwise httplib would improperly set Host: header to proxy's IP:port. """ conn.set_tunnel(self._proxy_host, self.port, self.proxy_headers) if self.proxy.scheme == "https": conn.tls_in_tls_required = True conn.connect() def _new_conn(self): """ Return a fresh :class:`http.client.HTTPSConnection`. """ self.num_connections += 1 log.debug( "Starting new HTTPS connection (%d): %s:%s", self.num_connections, self.host, self.port or "443", ) if not self.ConnectionCls or self.ConnectionCls is DummyConnection: raise SSLError( "Can't connect to HTTPS URL because the SSL module is not available." ) actual_host = self.host actual_port = self.port if self.proxy is not None: actual_host = self.proxy.host actual_port = self.proxy.port conn = self.ConnectionCls( host=actual_host, port=actual_port, timeout=self.timeout.connect_timeout, strict=self.strict, cert_file=self.cert_file, key_file=self.key_file, key_password=self.key_password, **self.conn_kw ) return self._prepare_conn(conn) def _validate_conn(self, conn): """ Called right before a request is made, after the socket is created. """ super(HTTPSConnectionPool, self)._validate_conn(conn) # Force connect early to allow us to validate the connection. if not getattr(conn, "sock", None): # AppEngine might not have `.sock` conn.connect() if not conn.is_verified: warnings.warn( ( "Unverified HTTPS request is being made to host '%s'. " "Adding certificate verification is strongly advised. See: " "https://urllib3.readthedocs.io/en/latest/advanced-usage.html" "#ssl-warnings" % conn.host ), InsecureRequestWarning, ) def connection_from_url(url, **kw): """ Given a url, return an :class:`.ConnectionPool` instance of its host. This is a shortcut for not having to parse out the scheme, host, and port of the url before creating an :class:`.ConnectionPool` instance. :param url: Absolute URL string that must include the scheme. Port is optional. :param \\**kw: Passes additional parameters to the constructor of the appropriate :class:`.ConnectionPool`. Useful for specifying things like timeout, maxsize, headers, etc. Example:: >>> conn = connection_from_url('http://google.com/') >>> r = conn.request('GET', '/') """ scheme, host, port = get_host(url) port = port or port_by_scheme.get(scheme, 80) if scheme == "https": return HTTPSConnectionPool(host, port=port, **kw) else: return HTTPConnectionPool(host, port=port, **kw) def _normalize_host(host, scheme): """ Normalize hosts for comparisons and use with sockets. """ host = normalize_host(host, scheme) # httplib doesn't like it when we include brackets in IPv6 addresses # Specifically, if we include brackets but also pass the port then # httplib crazily doubles up the square brackets on the Host header. # Instead, we need to make sure we never pass ``None`` as the port. # However, for backward compatibility reasons we can't actually # *assert* that. See http://bugs.python.org/issue28539 if host.startswith("[") and host.endswith("]"): host = host[1:-1] return host ================================================ FILE: modules/urllib3/contrib/_appengine_environ.py ================================================ """ This module provides means to detect the App Engine environment. """ import os def is_appengine(): return is_local_appengine() or is_prod_appengine() def is_appengine_sandbox(): """Reports if the app is running in the first generation sandbox. The second generation runtimes are technically still in a sandbox, but it is much less restrictive, so generally you shouldn't need to check for it. see https://cloud.google.com/appengine/docs/standard/runtimes """ return is_appengine() and os.environ["APPENGINE_RUNTIME"] == "python27" def is_local_appengine(): return "APPENGINE_RUNTIME" in os.environ and os.environ.get( "SERVER_SOFTWARE", "" ).startswith("Development/") def is_prod_appengine(): return "APPENGINE_RUNTIME" in os.environ and os.environ.get( "SERVER_SOFTWARE", "" ).startswith("Google App Engine/") def is_prod_appengine_mvms(): """Deprecated.""" return False ================================================ FILE: modules/urllib3/contrib/_securetransport/bindings.py ================================================ """ This module uses ctypes to bind a whole bunch of functions and constants from SecureTransport. The goal here is to provide the low-level API to SecureTransport. These are essentially the C-level functions and constants, and they're pretty gross to work with. This code is a bastardised version of the code found in Will Bond's oscrypto library. An enormous debt is owed to him for blazing this trail for us. For that reason, this code should be considered to be covered both by urllib3's license and by oscrypto's: Copyright (c) 2015-2016 Will Bond Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ from __future__ import absolute_import import platform from ctypes import ( CDLL, CFUNCTYPE, POINTER, c_bool, c_byte, c_char_p, c_int32, c_long, c_size_t, c_uint32, c_ulong, c_void_p, ) from ctypes.util import find_library from urllib3.packages.six import raise_from if platform.system() != "Darwin": raise ImportError("Only macOS is supported") version = platform.mac_ver()[0] version_info = tuple(map(int, version.split("."))) if version_info < (10, 8): raise OSError( "Only OS X 10.8 and newer are supported, not %s.%s" % (version_info[0], version_info[1]) ) def load_cdll(name, macos10_16_path): """Loads a CDLL by name, falling back to known path on 10.16+""" try: # Big Sur is technically 11 but we use 10.16 due to the Big Sur # beta being labeled as 10.16. if version_info >= (10, 16): path = macos10_16_path else: path = find_library(name) if not path: raise OSError # Caught and reraised as 'ImportError' return CDLL(path, use_errno=True) except OSError: raise_from(ImportError("The library %s failed to load" % name), None) Security = load_cdll( "Security", "/System/Library/Frameworks/Security.framework/Security" ) CoreFoundation = load_cdll( "CoreFoundation", "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation", ) Boolean = c_bool CFIndex = c_long CFStringEncoding = c_uint32 CFData = c_void_p CFString = c_void_p CFArray = c_void_p CFMutableArray = c_void_p CFDictionary = c_void_p CFError = c_void_p CFType = c_void_p CFTypeID = c_ulong CFTypeRef = POINTER(CFType) CFAllocatorRef = c_void_p OSStatus = c_int32 CFDataRef = POINTER(CFData) CFStringRef = POINTER(CFString) CFArrayRef = POINTER(CFArray) CFMutableArrayRef = POINTER(CFMutableArray) CFDictionaryRef = POINTER(CFDictionary) CFArrayCallBacks = c_void_p CFDictionaryKeyCallBacks = c_void_p CFDictionaryValueCallBacks = c_void_p SecCertificateRef = POINTER(c_void_p) SecExternalFormat = c_uint32 SecExternalItemType = c_uint32 SecIdentityRef = POINTER(c_void_p) SecItemImportExportFlags = c_uint32 SecItemImportExportKeyParameters = c_void_p SecKeychainRef = POINTER(c_void_p) SSLProtocol = c_uint32 SSLCipherSuite = c_uint32 SSLContextRef = POINTER(c_void_p) SecTrustRef = POINTER(c_void_p) SSLConnectionRef = c_uint32 SecTrustResultType = c_uint32 SecTrustOptionFlags = c_uint32 SSLProtocolSide = c_uint32 SSLConnectionType = c_uint32 SSLSessionOption = c_uint32 try: Security.SecItemImport.argtypes = [ CFDataRef, CFStringRef, POINTER(SecExternalFormat), POINTER(SecExternalItemType), SecItemImportExportFlags, POINTER(SecItemImportExportKeyParameters), SecKeychainRef, POINTER(CFArrayRef), ] Security.SecItemImport.restype = OSStatus Security.SecCertificateGetTypeID.argtypes = [] Security.SecCertificateGetTypeID.restype = CFTypeID Security.SecIdentityGetTypeID.argtypes = [] Security.SecIdentityGetTypeID.restype = CFTypeID Security.SecKeyGetTypeID.argtypes = [] Security.SecKeyGetTypeID.restype = CFTypeID Security.SecCertificateCreateWithData.argtypes = [CFAllocatorRef, CFDataRef] Security.SecCertificateCreateWithData.restype = SecCertificateRef Security.SecCertificateCopyData.argtypes = [SecCertificateRef] Security.SecCertificateCopyData.restype = CFDataRef Security.SecCopyErrorMessageString.argtypes = [OSStatus, c_void_p] Security.SecCopyErrorMessageString.restype = CFStringRef Security.SecIdentityCreateWithCertificate.argtypes = [ CFTypeRef, SecCertificateRef, POINTER(SecIdentityRef), ] Security.SecIdentityCreateWithCertificate.restype = OSStatus Security.SecKeychainCreate.argtypes = [ c_char_p, c_uint32, c_void_p, Boolean, c_void_p, POINTER(SecKeychainRef), ] Security.SecKeychainCreate.restype = OSStatus Security.SecKeychainDelete.argtypes = [SecKeychainRef] Security.SecKeychainDelete.restype = OSStatus Security.SecPKCS12Import.argtypes = [ CFDataRef, CFDictionaryRef, POINTER(CFArrayRef), ] Security.SecPKCS12Import.restype = OSStatus SSLReadFunc = CFUNCTYPE(OSStatus, SSLConnectionRef, c_void_p, POINTER(c_size_t)) SSLWriteFunc = CFUNCTYPE( OSStatus, SSLConnectionRef, POINTER(c_byte), POINTER(c_size_t) ) Security.SSLSetIOFuncs.argtypes = [SSLContextRef, SSLReadFunc, SSLWriteFunc] Security.SSLSetIOFuncs.restype = OSStatus Security.SSLSetPeerID.argtypes = [SSLContextRef, c_char_p, c_size_t] Security.SSLSetPeerID.restype = OSStatus Security.SSLSetCertificate.argtypes = [SSLContextRef, CFArrayRef] Security.SSLSetCertificate.restype = OSStatus Security.SSLSetCertificateAuthorities.argtypes = [SSLContextRef, CFTypeRef, Boolean] Security.SSLSetCertificateAuthorities.restype = OSStatus Security.SSLSetConnection.argtypes = [SSLContextRef, SSLConnectionRef] Security.SSLSetConnection.restype = OSStatus Security.SSLSetPeerDomainName.argtypes = [SSLContextRef, c_char_p, c_size_t] Security.SSLSetPeerDomainName.restype = OSStatus Security.SSLHandshake.argtypes = [SSLContextRef] Security.SSLHandshake.restype = OSStatus Security.SSLRead.argtypes = [SSLContextRef, c_char_p, c_size_t, POINTER(c_size_t)] Security.SSLRead.restype = OSStatus Security.SSLWrite.argtypes = [SSLContextRef, c_char_p, c_size_t, POINTER(c_size_t)] Security.SSLWrite.restype = OSStatus Security.SSLClose.argtypes = [SSLContextRef] Security.SSLClose.restype = OSStatus Security.SSLGetNumberSupportedCiphers.argtypes = [SSLContextRef, POINTER(c_size_t)] Security.SSLGetNumberSupportedCiphers.restype = OSStatus Security.SSLGetSupportedCiphers.argtypes = [ SSLContextRef, POINTER(SSLCipherSuite), POINTER(c_size_t), ] Security.SSLGetSupportedCiphers.restype = OSStatus Security.SSLSetEnabledCiphers.argtypes = [ SSLContextRef, POINTER(SSLCipherSuite), c_size_t, ] Security.SSLSetEnabledCiphers.restype = OSStatus Security.SSLGetNumberEnabledCiphers.argtype = [SSLContextRef, POINTER(c_size_t)] Security.SSLGetNumberEnabledCiphers.restype = OSStatus Security.SSLGetEnabledCiphers.argtypes = [ SSLContextRef, POINTER(SSLCipherSuite), POINTER(c_size_t), ] Security.SSLGetEnabledCiphers.restype = OSStatus Security.SSLGetNegotiatedCipher.argtypes = [SSLContextRef, POINTER(SSLCipherSuite)] Security.SSLGetNegotiatedCipher.restype = OSStatus Security.SSLGetNegotiatedProtocolVersion.argtypes = [ SSLContextRef, POINTER(SSLProtocol), ] Security.SSLGetNegotiatedProtocolVersion.restype = OSStatus Security.SSLCopyPeerTrust.argtypes = [SSLContextRef, POINTER(SecTrustRef)] Security.SSLCopyPeerTrust.restype = OSStatus Security.SecTrustSetAnchorCertificates.argtypes = [SecTrustRef, CFArrayRef] Security.SecTrustSetAnchorCertificates.restype = OSStatus Security.SecTrustSetAnchorCertificatesOnly.argstypes = [SecTrustRef, Boolean] Security.SecTrustSetAnchorCertificatesOnly.restype = OSStatus Security.SecTrustEvaluate.argtypes = [SecTrustRef, POINTER(SecTrustResultType)] Security.SecTrustEvaluate.restype = OSStatus Security.SecTrustGetCertificateCount.argtypes = [SecTrustRef] Security.SecTrustGetCertificateCount.restype = CFIndex Security.SecTrustGetCertificateAtIndex.argtypes = [SecTrustRef, CFIndex] Security.SecTrustGetCertificateAtIndex.restype = SecCertificateRef Security.SSLCreateContext.argtypes = [ CFAllocatorRef, SSLProtocolSide, SSLConnectionType, ] Security.SSLCreateContext.restype = SSLContextRef Security.SSLSetSessionOption.argtypes = [SSLContextRef, SSLSessionOption, Boolean] Security.SSLSetSessionOption.restype = OSStatus Security.SSLSetProtocolVersionMin.argtypes = [SSLContextRef, SSLProtocol] Security.SSLSetProtocolVersionMin.restype = OSStatus Security.SSLSetProtocolVersionMax.argtypes = [SSLContextRef, SSLProtocol] Security.SSLSetProtocolVersionMax.restype = OSStatus try: Security.SSLSetALPNProtocols.argtypes = [SSLContextRef, CFArrayRef] Security.SSLSetALPNProtocols.restype = OSStatus except AttributeError: # Supported only in 10.12+ pass Security.SecCopyErrorMessageString.argtypes = [OSStatus, c_void_p] Security.SecCopyErrorMessageString.restype = CFStringRef Security.SSLReadFunc = SSLReadFunc Security.SSLWriteFunc = SSLWriteFunc Security.SSLContextRef = SSLContextRef Security.SSLProtocol = SSLProtocol Security.SSLCipherSuite = SSLCipherSuite Security.SecIdentityRef = SecIdentityRef Security.SecKeychainRef = SecKeychainRef Security.SecTrustRef = SecTrustRef Security.SecTrustResultType = SecTrustResultType Security.SecExternalFormat = SecExternalFormat Security.OSStatus = OSStatus Security.kSecImportExportPassphrase = CFStringRef.in_dll( Security, "kSecImportExportPassphrase" ) Security.kSecImportItemIdentity = CFStringRef.in_dll( Security, "kSecImportItemIdentity" ) # CoreFoundation time! CoreFoundation.CFRetain.argtypes = [CFTypeRef] CoreFoundation.CFRetain.restype = CFTypeRef CoreFoundation.CFRelease.argtypes = [CFTypeRef] CoreFoundation.CFRelease.restype = None CoreFoundation.CFGetTypeID.argtypes = [CFTypeRef] CoreFoundation.CFGetTypeID.restype = CFTypeID CoreFoundation.CFStringCreateWithCString.argtypes = [ CFAllocatorRef, c_char_p, CFStringEncoding, ] CoreFoundation.CFStringCreateWithCString.restype = CFStringRef CoreFoundation.CFStringGetCStringPtr.argtypes = [CFStringRef, CFStringEncoding] CoreFoundation.CFStringGetCStringPtr.restype = c_char_p CoreFoundation.CFStringGetCString.argtypes = [ CFStringRef, c_char_p, CFIndex, CFStringEncoding, ] CoreFoundation.CFStringGetCString.restype = c_bool CoreFoundation.CFDataCreate.argtypes = [CFAllocatorRef, c_char_p, CFIndex] CoreFoundation.CFDataCreate.restype = CFDataRef CoreFoundation.CFDataGetLength.argtypes = [CFDataRef] CoreFoundation.CFDataGetLength.restype = CFIndex CoreFoundation.CFDataGetBytePtr.argtypes = [CFDataRef] CoreFoundation.CFDataGetBytePtr.restype = c_void_p CoreFoundation.CFDictionaryCreate.argtypes = [ CFAllocatorRef, POINTER(CFTypeRef), POINTER(CFTypeRef), CFIndex, CFDictionaryKeyCallBacks, CFDictionaryValueCallBacks, ] CoreFoundation.CFDictionaryCreate.restype = CFDictionaryRef CoreFoundation.CFDictionaryGetValue.argtypes = [CFDictionaryRef, CFTypeRef] CoreFoundation.CFDictionaryGetValue.restype = CFTypeRef CoreFoundation.CFArrayCreate.argtypes = [ CFAllocatorRef, POINTER(CFTypeRef), CFIndex, CFArrayCallBacks, ] CoreFoundation.CFArrayCreate.restype = CFArrayRef CoreFoundation.CFArrayCreateMutable.argtypes = [ CFAllocatorRef, CFIndex, CFArrayCallBacks, ] CoreFoundation.CFArrayCreateMutable.restype = CFMutableArrayRef CoreFoundation.CFArrayAppendValue.argtypes = [CFMutableArrayRef, c_void_p] CoreFoundation.CFArrayAppendValue.restype = None CoreFoundation.CFArrayGetCount.argtypes = [CFArrayRef] CoreFoundation.CFArrayGetCount.restype = CFIndex CoreFoundation.CFArrayGetValueAtIndex.argtypes = [CFArrayRef, CFIndex] CoreFoundation.CFArrayGetValueAtIndex.restype = c_void_p CoreFoundation.kCFAllocatorDefault = CFAllocatorRef.in_dll( CoreFoundation, "kCFAllocatorDefault" ) CoreFoundation.kCFTypeArrayCallBacks = c_void_p.in_dll( CoreFoundation, "kCFTypeArrayCallBacks" ) CoreFoundation.kCFTypeDictionaryKeyCallBacks = c_void_p.in_dll( CoreFoundation, "kCFTypeDictionaryKeyCallBacks" ) CoreFoundation.kCFTypeDictionaryValueCallBacks = c_void_p.in_dll( CoreFoundation, "kCFTypeDictionaryValueCallBacks" ) CoreFoundation.CFTypeRef = CFTypeRef CoreFoundation.CFArrayRef = CFArrayRef CoreFoundation.CFStringRef = CFStringRef CoreFoundation.CFDictionaryRef = CFDictionaryRef except (AttributeError): raise ImportError("Error initializing ctypes") class CFConst(object): """ A class object that acts as essentially a namespace for CoreFoundation constants. """ kCFStringEncodingUTF8 = CFStringEncoding(0x08000100) class SecurityConst(object): """ A class object that acts as essentially a namespace for Security constants. """ kSSLSessionOptionBreakOnServerAuth = 0 kSSLProtocol2 = 1 kSSLProtocol3 = 2 kTLSProtocol1 = 4 kTLSProtocol11 = 7 kTLSProtocol12 = 8 # SecureTransport does not support TLS 1.3 even if there's a constant for it kTLSProtocol13 = 10 kTLSProtocolMaxSupported = 999 kSSLClientSide = 1 kSSLStreamType = 0 kSecFormatPEMSequence = 10 kSecTrustResultInvalid = 0 kSecTrustResultProceed = 1 # This gap is present on purpose: this was kSecTrustResultConfirm, which # is deprecated. kSecTrustResultDeny = 3 kSecTrustResultUnspecified = 4 kSecTrustResultRecoverableTrustFailure = 5 kSecTrustResultFatalTrustFailure = 6 kSecTrustResultOtherError = 7 errSSLProtocol = -9800 errSSLWouldBlock = -9803 errSSLClosedGraceful = -9805 errSSLClosedNoNotify = -9816 errSSLClosedAbort = -9806 errSSLXCertChainInvalid = -9807 errSSLCrypto = -9809 errSSLInternal = -9810 errSSLCertExpired = -9814 errSSLCertNotYetValid = -9815 errSSLUnknownRootCert = -9812 errSSLNoRootCert = -9813 errSSLHostNameMismatch = -9843 errSSLPeerHandshakeFail = -9824 errSSLPeerUserCancelled = -9839 errSSLWeakPeerEphemeralDHKey = -9850 errSSLServerAuthCompleted = -9841 errSSLRecordOverflow = -9847 errSecVerifyFailed = -67808 errSecNoTrustSettings = -25263 errSecItemNotFound = -25300 errSecInvalidTrustSettings = -25262 # Cipher suites. We only pick the ones our default cipher string allows. # Source: https://developer.apple.com/documentation/security/1550981-ssl_cipher_suite_values TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 = 0xC02C TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 = 0xC030 TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 = 0xC02B TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 = 0xC02F TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 = 0xCCA9 TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = 0xCCA8 TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 = 0x009F TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 = 0x009E TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 = 0xC024 TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 = 0xC028 TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA = 0xC00A TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA = 0xC014 TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 = 0x006B TLS_DHE_RSA_WITH_AES_256_CBC_SHA = 0x0039 TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 = 0xC023 TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 = 0xC027 TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA = 0xC009 TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA = 0xC013 TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 = 0x0067 TLS_DHE_RSA_WITH_AES_128_CBC_SHA = 0x0033 TLS_RSA_WITH_AES_256_GCM_SHA384 = 0x009D TLS_RSA_WITH_AES_128_GCM_SHA256 = 0x009C TLS_RSA_WITH_AES_256_CBC_SHA256 = 0x003D TLS_RSA_WITH_AES_128_CBC_SHA256 = 0x003C TLS_RSA_WITH_AES_256_CBC_SHA = 0x0035 TLS_RSA_WITH_AES_128_CBC_SHA = 0x002F TLS_AES_128_GCM_SHA256 = 0x1301 TLS_AES_256_GCM_SHA384 = 0x1302 TLS_AES_128_CCM_8_SHA256 = 0x1305 TLS_AES_128_CCM_SHA256 = 0x1304 ================================================ FILE: modules/urllib3/contrib/_securetransport/low_level.py ================================================ """ Low-level helpers for the SecureTransport bindings. These are Python functions that are not directly related to the high-level APIs but are necessary to get them to work. They include a whole bunch of low-level CoreFoundation messing about and memory management. The concerns in this module are almost entirely about trying to avoid memory leaks and providing appropriate and useful assistance to the higher-level code. """ import base64 import ctypes import itertools import os import re import ssl import struct import tempfile from .bindings import CFConst, CoreFoundation, Security # This regular expression is used to grab PEM data out of a PEM bundle. _PEM_CERTS_RE = re.compile( b"-----BEGIN CERTIFICATE-----\n(.*?)\n-----END CERTIFICATE-----", re.DOTALL ) def _cf_data_from_bytes(bytestring): """ Given a bytestring, create a CFData object from it. This CFData object must be CFReleased by the caller. """ return CoreFoundation.CFDataCreate( CoreFoundation.kCFAllocatorDefault, bytestring, len(bytestring) ) def _cf_dictionary_from_tuples(tuples): """ Given a list of Python tuples, create an associated CFDictionary. """ dictionary_size = len(tuples) # We need to get the dictionary keys and values out in the same order. keys = (t[0] for t in tuples) values = (t[1] for t in tuples) cf_keys = (CoreFoundation.CFTypeRef * dictionary_size)(*keys) cf_values = (CoreFoundation.CFTypeRef * dictionary_size)(*values) return CoreFoundation.CFDictionaryCreate( CoreFoundation.kCFAllocatorDefault, cf_keys, cf_values, dictionary_size, CoreFoundation.kCFTypeDictionaryKeyCallBacks, CoreFoundation.kCFTypeDictionaryValueCallBacks, ) def _cfstr(py_bstr): """ Given a Python binary data, create a CFString. The string must be CFReleased by the caller. """ c_str = ctypes.c_char_p(py_bstr) cf_str = CoreFoundation.CFStringCreateWithCString( CoreFoundation.kCFAllocatorDefault, c_str, CFConst.kCFStringEncodingUTF8, ) return cf_str def _create_cfstring_array(lst): """ Given a list of Python binary data, create an associated CFMutableArray. The array must be CFReleased by the caller. Raises an ssl.SSLError on failure. """ cf_arr = None try: cf_arr = CoreFoundation.CFArrayCreateMutable( CoreFoundation.kCFAllocatorDefault, 0, ctypes.byref(CoreFoundation.kCFTypeArrayCallBacks), ) if not cf_arr: raise MemoryError("Unable to allocate memory!") for item in lst: cf_str = _cfstr(item) if not cf_str: raise MemoryError("Unable to allocate memory!") try: CoreFoundation.CFArrayAppendValue(cf_arr, cf_str) finally: CoreFoundation.CFRelease(cf_str) except BaseException as e: if cf_arr: CoreFoundation.CFRelease(cf_arr) raise ssl.SSLError("Unable to allocate array: %s" % (e,)) return cf_arr def _cf_string_to_unicode(value): """ Creates a Unicode string from a CFString object. Used entirely for error reporting. Yes, it annoys me quite a lot that this function is this complex. """ value_as_void_p = ctypes.cast(value, ctypes.POINTER(ctypes.c_void_p)) string = CoreFoundation.CFStringGetCStringPtr( value_as_void_p, CFConst.kCFStringEncodingUTF8 ) if string is None: buffer = ctypes.create_string_buffer(1024) result = CoreFoundation.CFStringGetCString( value_as_void_p, buffer, 1024, CFConst.kCFStringEncodingUTF8 ) if not result: raise OSError("Error copying C string from CFStringRef") string = buffer.value if string is not None: string = string.decode("utf-8") return string def _assert_no_error(error, exception_class=None): """ Checks the return code and throws an exception if there is an error to report """ if error == 0: return cf_error_string = Security.SecCopyErrorMessageString(error, None) output = _cf_string_to_unicode(cf_error_string) CoreFoundation.CFRelease(cf_error_string) if output is None or output == u"": output = u"OSStatus %s" % error if exception_class is None: exception_class = ssl.SSLError raise exception_class(output) def _cert_array_from_pem(pem_bundle): """ Given a bundle of certs in PEM format, turns them into a CFArray of certs that can be used to validate a cert chain. """ # Normalize the PEM bundle's line endings. pem_bundle = pem_bundle.replace(b"\r\n", b"\n") der_certs = [ base64.b64decode(match.group(1)) for match in _PEM_CERTS_RE.finditer(pem_bundle) ] if not der_certs: raise ssl.SSLError("No root certificates specified") cert_array = CoreFoundation.CFArrayCreateMutable( CoreFoundation.kCFAllocatorDefault, 0, ctypes.byref(CoreFoundation.kCFTypeArrayCallBacks), ) if not cert_array: raise ssl.SSLError("Unable to allocate memory!") try: for der_bytes in der_certs: certdata = _cf_data_from_bytes(der_bytes) if not certdata: raise ssl.SSLError("Unable to allocate memory!") cert = Security.SecCertificateCreateWithData( CoreFoundation.kCFAllocatorDefault, certdata ) CoreFoundation.CFRelease(certdata) if not cert: raise ssl.SSLError("Unable to build cert object!") CoreFoundation.CFArrayAppendValue(cert_array, cert) CoreFoundation.CFRelease(cert) except Exception: # We need to free the array before the exception bubbles further. # We only want to do that if an error occurs: otherwise, the caller # should free. CoreFoundation.CFRelease(cert_array) return cert_array def _is_cert(item): """ Returns True if a given CFTypeRef is a certificate. """ expected = Security.SecCertificateGetTypeID() return CoreFoundation.CFGetTypeID(item) == expected def _is_identity(item): """ Returns True if a given CFTypeRef is an identity. """ expected = Security.SecIdentityGetTypeID() return CoreFoundation.CFGetTypeID(item) == expected def _temporary_keychain(): """ This function creates a temporary Mac keychain that we can use to work with credentials. This keychain uses a one-time password and a temporary file to store the data. We expect to have one keychain per socket. The returned SecKeychainRef must be freed by the caller, including calling SecKeychainDelete. Returns a tuple of the SecKeychainRef and the path to the temporary directory that contains it. """ # Unfortunately, SecKeychainCreate requires a path to a keychain. This # means we cannot use mkstemp to use a generic temporary file. Instead, # we're going to create a temporary directory and a filename to use there. # This filename will be 8 random bytes expanded into base64. We also need # some random bytes to password-protect the keychain we're creating, so we # ask for 40 random bytes. random_bytes = os.urandom(40) filename = base64.b16encode(random_bytes[:8]).decode("utf-8") password = base64.b16encode(random_bytes[8:]) # Must be valid UTF-8 tempdirectory = tempfile.mkdtemp() keychain_path = os.path.join(tempdirectory, filename).encode("utf-8") # We now want to create the keychain itself. keychain = Security.SecKeychainRef() status = Security.SecKeychainCreate( keychain_path, len(password), password, False, None, ctypes.byref(keychain) ) _assert_no_error(status) # Having created the keychain, we want to pass it off to the caller. return keychain, tempdirectory def _load_items_from_file(keychain, path): """ Given a single file, loads all the trust objects from it into arrays and the keychain. Returns a tuple of lists: the first list is a list of identities, the second a list of certs. """ certificates = [] identities = [] result_array = None with open(path, "rb") as f: raw_filedata = f.read() try: filedata = CoreFoundation.CFDataCreate( CoreFoundation.kCFAllocatorDefault, raw_filedata, len(raw_filedata) ) result_array = CoreFoundation.CFArrayRef() result = Security.SecItemImport( filedata, # cert data None, # Filename, leaving it out for now None, # What the type of the file is, we don't care None, # what's in the file, we don't care 0, # import flags None, # key params, can include passphrase in the future keychain, # The keychain to insert into ctypes.byref(result_array), # Results ) _assert_no_error(result) # A CFArray is not very useful to us as an intermediary # representation, so we are going to extract the objects we want # and then free the array. We don't need to keep hold of keys: the # keychain already has them! result_count = CoreFoundation.CFArrayGetCount(result_array) for index in range(result_count): item = CoreFoundation.CFArrayGetValueAtIndex(result_array, index) item = ctypes.cast(item, CoreFoundation.CFTypeRef) if _is_cert(item): CoreFoundation.CFRetain(item) certificates.append(item) elif _is_identity(item): CoreFoundation.CFRetain(item) identities.append(item) finally: if result_array: CoreFoundation.CFRelease(result_array) CoreFoundation.CFRelease(filedata) return (identities, certificates) def _load_client_cert_chain(keychain, *paths): """ Load certificates and maybe keys from a number of files. Has the end goal of returning a CFArray containing one SecIdentityRef, and then zero or more SecCertificateRef objects, suitable for use as a client certificate trust chain. """ # Ok, the strategy. # # This relies on knowing that macOS will not give you a SecIdentityRef # unless you have imported a key into a keychain. This is a somewhat # artificial limitation of macOS (for example, it doesn't necessarily # affect iOS), but there is nothing inside Security.framework that lets you # get a SecIdentityRef without having a key in a keychain. # # So the policy here is we take all the files and iterate them in order. # Each one will use SecItemImport to have one or more objects loaded from # it. We will also point at a keychain that macOS can use to work with the # private key. # # Once we have all the objects, we'll check what we actually have. If we # already have a SecIdentityRef in hand, fab: we'll use that. Otherwise, # we'll take the first certificate (which we assume to be our leaf) and # ask the keychain to give us a SecIdentityRef with that cert's associated # key. # # We'll then return a CFArray containing the trust chain: one # SecIdentityRef and then zero-or-more SecCertificateRef objects. The # responsibility for freeing this CFArray will be with the caller. This # CFArray must remain alive for the entire connection, so in practice it # will be stored with a single SSLSocket, along with the reference to the # keychain. certificates = [] identities = [] # Filter out bad paths. paths = (path for path in paths if path) try: for file_path in paths: new_identities, new_certs = _load_items_from_file(keychain, file_path) identities.extend(new_identities) certificates.extend(new_certs) # Ok, we have everything. The question is: do we have an identity? If # not, we want to grab one from the first cert we have. if not identities: new_identity = Security.SecIdentityRef() status = Security.SecIdentityCreateWithCertificate( keychain, certificates[0], ctypes.byref(new_identity) ) _assert_no_error(status) identities.append(new_identity) # We now want to release the original certificate, as we no longer # need it. CoreFoundation.CFRelease(certificates.pop(0)) # We now need to build a new CFArray that holds the trust chain. trust_chain = CoreFoundation.CFArrayCreateMutable( CoreFoundation.kCFAllocatorDefault, 0, ctypes.byref(CoreFoundation.kCFTypeArrayCallBacks), ) for item in itertools.chain(identities, certificates): # ArrayAppendValue does a CFRetain on the item. That's fine, # because the finally block will release our other refs to them. CoreFoundation.CFArrayAppendValue(trust_chain, item) return trust_chain finally: for obj in itertools.chain(identities, certificates): CoreFoundation.CFRelease(obj) TLS_PROTOCOL_VERSIONS = { "SSLv2": (0, 2), "SSLv3": (3, 0), "TLSv1": (3, 1), "TLSv1.1": (3, 2), "TLSv1.2": (3, 3), } def _build_tls_unknown_ca_alert(version): """ Builds a TLS alert record for an unknown CA. """ ver_maj, ver_min = TLS_PROTOCOL_VERSIONS[version] severity_fatal = 0x02 description_unknown_ca = 0x30 msg = struct.pack(">BB", severity_fatal, description_unknown_ca) msg_len = len(msg) record_type_alert = 0x15 record = struct.pack(">BBBH", record_type_alert, ver_maj, ver_min, msg_len) + msg return record ================================================ FILE: modules/urllib3/contrib/appengine.py ================================================ """ This module provides a pool manager that uses Google App Engine's `URLFetch Service `_. Example usage:: from urllib3 import PoolManager from urllib3.contrib.appengine import AppEngineManager, is_appengine_sandbox if is_appengine_sandbox(): # AppEngineManager uses AppEngine's URLFetch API behind the scenes http = AppEngineManager() else: # PoolManager uses a socket-level API behind the scenes http = PoolManager() r = http.request('GET', 'https://google.com/') There are `limitations `_ to the URLFetch service and it may not be the best choice for your application. There are three options for using urllib3 on Google App Engine: 1. You can use :class:`AppEngineManager` with URLFetch. URLFetch is cost-effective in many circumstances as long as your usage is within the limitations. 2. You can use a normal :class:`~urllib3.PoolManager` by enabling sockets. Sockets also have `limitations and restrictions `_ and have a lower free quota than URLFetch. To use sockets, be sure to specify the following in your ``app.yaml``:: env_variables: GAE_USE_SOCKETS_HTTPLIB : 'true' 3. If you are using `App Engine Flexible `_, you can use the standard :class:`PoolManager` without any configuration or special environment variables. """ from __future__ import absolute_import import io import logging import warnings from ..exceptions import ( HTTPError, HTTPWarning, MaxRetryError, ProtocolError, SSLError, TimeoutError, ) from ..packages.six.moves.urllib.parse import urljoin from ..request import RequestMethods from ..response import HTTPResponse from ..util.retry import Retry from ..util.timeout import Timeout from . import _appengine_environ try: from google.appengine.api import urlfetch except ImportError: urlfetch = None log = logging.getLogger(__name__) class AppEnginePlatformWarning(HTTPWarning): pass class AppEnginePlatformError(HTTPError): pass class AppEngineManager(RequestMethods): """ Connection manager for Google App Engine sandbox applications. This manager uses the URLFetch service directly instead of using the emulated httplib, and is subject to URLFetch limitations as described in the App Engine documentation `here `_. Notably it will raise an :class:`AppEnginePlatformError` if: * URLFetch is not available. * If you attempt to use this on App Engine Flexible, as full socket support is available. * If a request size is more than 10 megabytes. * If a response size is more than 32 megabytes. * If you use an unsupported request method such as OPTIONS. Beyond those cases, it will raise normal urllib3 errors. """ def __init__( self, headers=None, retries=None, validate_certificate=True, urlfetch_retries=True, ): if not urlfetch: raise AppEnginePlatformError( "URLFetch is not available in this environment." ) warnings.warn( "urllib3 is using URLFetch on Google App Engine sandbox instead " "of sockets. To use sockets directly instead of URLFetch see " "https://urllib3.readthedocs.io/en/latest/reference/urllib3.contrib.html.", AppEnginePlatformWarning, ) RequestMethods.__init__(self, headers) self.validate_certificate = validate_certificate self.urlfetch_retries = urlfetch_retries self.retries = retries or Retry.DEFAULT def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): # Return False to re-raise any potential exceptions return False def urlopen( self, method, url, body=None, headers=None, retries=None, redirect=True, timeout=Timeout.DEFAULT_TIMEOUT, **response_kw ): retries = self._get_retries(retries, redirect) try: follow_redirects = redirect and retries.redirect != 0 and retries.total response = urlfetch.fetch( url, payload=body, method=method, headers=headers or {}, allow_truncated=False, follow_redirects=self.urlfetch_retries and follow_redirects, deadline=self._get_absolute_timeout(timeout), validate_certificate=self.validate_certificate, ) except urlfetch.DeadlineExceededError as e: raise TimeoutError(self, e) except urlfetch.InvalidURLError as e: if "too large" in str(e): raise AppEnginePlatformError( "URLFetch request too large, URLFetch only " "supports requests up to 10mb in size.", e, ) raise ProtocolError(e) except urlfetch.DownloadError as e: if "Too many redirects" in str(e): raise MaxRetryError(self, url, reason=e) raise ProtocolError(e) except urlfetch.ResponseTooLargeError as e: raise AppEnginePlatformError( "URLFetch response too large, URLFetch only supports" "responses up to 32mb in size.", e, ) except urlfetch.SSLCertificateError as e: raise SSLError(e) except urlfetch.InvalidMethodError as e: raise AppEnginePlatformError( "URLFetch does not support method: %s" % method, e ) http_response = self._urlfetch_response_to_http_response( response, retries=retries, **response_kw ) # Handle redirect? redirect_location = redirect and http_response.get_redirect_location() if redirect_location: # Check for redirect response if self.urlfetch_retries and retries.raise_on_redirect: raise MaxRetryError(self, url, "too many redirects") else: if http_response.status == 303: method = "GET" try: retries = retries.increment( method, url, response=http_response, _pool=self ) except MaxRetryError: if retries.raise_on_redirect: raise MaxRetryError(self, url, "too many redirects") return http_response retries.sleep_for_retry(http_response) log.debug("Redirecting %s -> %s", url, redirect_location) redirect_url = urljoin(url, redirect_location) return self.urlopen( method, redirect_url, body, headers, retries=retries, redirect=redirect, timeout=timeout, **response_kw ) # Check if we should retry the HTTP response. has_retry_after = bool(http_response.getheader("Retry-After")) if retries.is_retry(method, http_response.status, has_retry_after): retries = retries.increment(method, url, response=http_response, _pool=self) log.debug("Retry: %s", url) retries.sleep(http_response) return self.urlopen( method, url, body=body, headers=headers, retries=retries, redirect=redirect, timeout=timeout, **response_kw ) return http_response def _urlfetch_response_to_http_response(self, urlfetch_resp, **response_kw): if is_prod_appengine(): # Production GAE handles deflate encoding automatically, but does # not remove the encoding header. content_encoding = urlfetch_resp.headers.get("content-encoding") if content_encoding == "deflate": del urlfetch_resp.headers["content-encoding"] transfer_encoding = urlfetch_resp.headers.get("transfer-encoding") # We have a full response's content, # so let's make sure we don't report ourselves as chunked data. if transfer_encoding == "chunked": encodings = transfer_encoding.split(",") encodings.remove("chunked") urlfetch_resp.headers["transfer-encoding"] = ",".join(encodings) original_response = HTTPResponse( # In order for decoding to work, we must present the content as # a file-like object. body=io.BytesIO(urlfetch_resp.content), msg=urlfetch_resp.header_msg, headers=urlfetch_resp.headers, status=urlfetch_resp.status_code, **response_kw ) return HTTPResponse( body=io.BytesIO(urlfetch_resp.content), headers=urlfetch_resp.headers, status=urlfetch_resp.status_code, original_response=original_response, **response_kw ) def _get_absolute_timeout(self, timeout): if timeout is Timeout.DEFAULT_TIMEOUT: return None # Defer to URLFetch's default. if isinstance(timeout, Timeout): if timeout._read is not None or timeout._connect is not None: warnings.warn( "URLFetch does not support granular timeout settings, " "reverting to total or default URLFetch timeout.", AppEnginePlatformWarning, ) return timeout.total return timeout def _get_retries(self, retries, redirect): if not isinstance(retries, Retry): retries = Retry.from_int(retries, redirect=redirect, default=self.retries) if retries.connect or retries.read or retries.redirect: warnings.warn( "URLFetch only supports total retries and does not " "recognize connect, read, or redirect retry parameters.", AppEnginePlatformWarning, ) return retries # Alias methods from _appengine_environ to maintain public API interface. is_appengine = _appengine_environ.is_appengine is_appengine_sandbox = _appengine_environ.is_appengine_sandbox is_local_appengine = _appengine_environ.is_local_appengine is_prod_appengine = _appengine_environ.is_prod_appengine is_prod_appengine_mvms = _appengine_environ.is_prod_appengine_mvms ================================================ FILE: modules/urllib3/contrib/ntlmpool.py ================================================ """ NTLM authenticating pool, contributed by erikcederstran Issue #10, see: http://code.google.com/p/urllib3/issues/detail?id=10 """ from __future__ import absolute_import from logging import getLogger from ntlm import ntlm from .. import HTTPSConnectionPool from ..packages.six.moves.http_client import HTTPSConnection log = getLogger(__name__) class NTLMConnectionPool(HTTPSConnectionPool): """ Implements an NTLM authentication version of an urllib3 connection pool """ scheme = "https" def __init__(self, user, pw, authurl, *args, **kwargs): """ authurl is a random URL on the server that is protected by NTLM. user is the Windows user, probably in the DOMAIN\\username format. pw is the password for the user. """ super(NTLMConnectionPool, self).__init__(*args, **kwargs) self.authurl = authurl self.rawuser = user user_parts = user.split("\\", 1) self.domain = user_parts[0].upper() self.user = user_parts[1] self.pw = pw def _new_conn(self): # Performs the NTLM handshake that secures the connection. The socket # must be kept open while requests are performed. self.num_connections += 1 log.debug( "Starting NTLM HTTPS connection no. %d: https://%s%s", self.num_connections, self.host, self.authurl, ) headers = {"Connection": "Keep-Alive"} req_header = "Authorization" resp_header = "www-authenticate" conn = HTTPSConnection(host=self.host, port=self.port) # Send negotiation message headers[req_header] = "NTLM %s" % ntlm.create_NTLM_NEGOTIATE_MESSAGE( self.rawuser ) log.debug("Request headers: %s", headers) conn.request("GET", self.authurl, None, headers) res = conn.getresponse() reshdr = dict(res.getheaders()) log.debug("Response status: %s %s", res.status, res.reason) log.debug("Response headers: %s", reshdr) log.debug("Response data: %s [...]", res.read(100)) # Remove the reference to the socket, so that it can not be closed by # the response object (we want to keep the socket open) res.fp = None # Server should respond with a challenge message auth_header_values = reshdr[resp_header].split(", ") auth_header_value = None for s in auth_header_values: if s[:5] == "NTLM ": auth_header_value = s[5:] if auth_header_value is None: raise Exception( "Unexpected %s response header: %s" % (resp_header, reshdr[resp_header]) ) # Send authentication message ServerChallenge, NegotiateFlags = ntlm.parse_NTLM_CHALLENGE_MESSAGE( auth_header_value ) auth_msg = ntlm.create_NTLM_AUTHENTICATE_MESSAGE( ServerChallenge, self.user, self.domain, self.pw, NegotiateFlags ) headers[req_header] = "NTLM %s" % auth_msg log.debug("Request headers: %s", headers) conn.request("GET", self.authurl, None, headers) res = conn.getresponse() log.debug("Response status: %s %s", res.status, res.reason) log.debug("Response headers: %s", dict(res.getheaders())) log.debug("Response data: %s [...]", res.read()[:100]) if res.status != 200: if res.status == 401: raise Exception("Server rejected request: wrong username or password") raise Exception("Wrong server response: %s %s" % (res.status, res.reason)) res.fp = None log.debug("Connection established") return conn def urlopen( self, method, url, body=None, headers=None, retries=3, redirect=True, assert_same_host=True, ): if headers is None: headers = {} headers["Connection"] = "Keep-Alive" return super(NTLMConnectionPool, self).urlopen( method, url, body, headers, retries, redirect, assert_same_host ) ================================================ FILE: modules/urllib3/contrib/pyopenssl.py ================================================ """ TLS with SNI_-support for Python 2. Follow these instructions if you would like to verify TLS certificates in Python 2. Note, the default libraries do *not* do certificate checking; you need to do additional work to validate certificates yourself. This needs the following packages installed: * `pyOpenSSL`_ (tested with 16.0.0) * `cryptography`_ (minimum 1.3.4, from pyopenssl) * `idna`_ (minimum 2.0, from cryptography) However, pyopenssl depends on cryptography, which depends on idna, so while we use all three directly here we end up having relatively few packages required. You can install them with the following command: .. code-block:: bash $ python -m pip install pyopenssl cryptography idna To activate certificate checking, call :func:`~urllib3.contrib.pyopenssl.inject_into_urllib3` from your Python code before you begin making HTTP requests. This can be done in a ``sitecustomize`` module, or at any other time before your application begins using ``urllib3``, like this: .. code-block:: python try: import urllib3.contrib.pyopenssl urllib3.contrib.pyopenssl.inject_into_urllib3() except ImportError: pass Now you can use :mod:`urllib3` as you normally would, and it will support SNI when the required modules are installed. Activating this module also has the positive side effect of disabling SSL/TLS compression in Python 2 (see `CRIME attack`_). .. _sni: https://en.wikipedia.org/wiki/Server_Name_Indication .. _crime attack: https://en.wikipedia.org/wiki/CRIME_(security_exploit) .. _pyopenssl: https://www.pyopenssl.org .. _cryptography: https://cryptography.io .. _idna: https://github.com/kjd/idna """ from __future__ import absolute_import import OpenSSL.SSL from cryptography import x509 from cryptography.hazmat.backends.openssl import backend as openssl_backend from cryptography.hazmat.backends.openssl.x509 import _Certificate try: from cryptography.x509 import UnsupportedExtension except ImportError: # UnsupportedExtension is gone in cryptography >= 2.1.0 class UnsupportedExtension(Exception): pass from io import BytesIO from socket import error as SocketError from socket import timeout try: # Platform-specific: Python 2 from socket import _fileobject except ImportError: # Platform-specific: Python 3 _fileobject = None from ..packages.backports.makefile import backport_makefile import logging import ssl import sys from .. import util from ..packages import six __all__ = ["inject_into_urllib3", "extract_from_urllib3"] # SNI always works. HAS_SNI = True # Map from urllib3 to PyOpenSSL compatible parameter-values. _openssl_versions = { util.PROTOCOL_TLS: OpenSSL.SSL.SSLv23_METHOD, ssl.PROTOCOL_TLSv1: OpenSSL.SSL.TLSv1_METHOD, } if hasattr(ssl, "PROTOCOL_SSLv3") and hasattr(OpenSSL.SSL, "SSLv3_METHOD"): _openssl_versions[ssl.PROTOCOL_SSLv3] = OpenSSL.SSL.SSLv3_METHOD if hasattr(ssl, "PROTOCOL_TLSv1_1") and hasattr(OpenSSL.SSL, "TLSv1_1_METHOD"): _openssl_versions[ssl.PROTOCOL_TLSv1_1] = OpenSSL.SSL.TLSv1_1_METHOD if hasattr(ssl, "PROTOCOL_TLSv1_2") and hasattr(OpenSSL.SSL, "TLSv1_2_METHOD"): _openssl_versions[ssl.PROTOCOL_TLSv1_2] = OpenSSL.SSL.TLSv1_2_METHOD _stdlib_to_openssl_verify = { ssl.CERT_NONE: OpenSSL.SSL.VERIFY_NONE, ssl.CERT_OPTIONAL: OpenSSL.SSL.VERIFY_PEER, ssl.CERT_REQUIRED: OpenSSL.SSL.VERIFY_PEER + OpenSSL.SSL.VERIFY_FAIL_IF_NO_PEER_CERT, } _openssl_to_stdlib_verify = dict((v, k) for k, v in _stdlib_to_openssl_verify.items()) # OpenSSL will only write 16K at a time SSL_WRITE_BLOCKSIZE = 16384 orig_util_HAS_SNI = util.HAS_SNI orig_util_SSLContext = util.ssl_.SSLContext log = logging.getLogger(__name__) def inject_into_urllib3(): "Monkey-patch urllib3 with PyOpenSSL-backed SSL-support." _validate_dependencies_met() util.SSLContext = PyOpenSSLContext util.ssl_.SSLContext = PyOpenSSLContext util.HAS_SNI = HAS_SNI util.ssl_.HAS_SNI = HAS_SNI util.IS_PYOPENSSL = True util.ssl_.IS_PYOPENSSL = True def extract_from_urllib3(): "Undo monkey-patching by :func:`inject_into_urllib3`." util.SSLContext = orig_util_SSLContext util.ssl_.SSLContext = orig_util_SSLContext util.HAS_SNI = orig_util_HAS_SNI util.ssl_.HAS_SNI = orig_util_HAS_SNI util.IS_PYOPENSSL = False util.ssl_.IS_PYOPENSSL = False def _validate_dependencies_met(): """ Verifies that PyOpenSSL's package-level dependencies have been met. Throws `ImportError` if they are not met. """ # Method added in `cryptography==1.1`; not available in older versions from cryptography.x509.extensions import Extensions if getattr(Extensions, "get_extension_for_class", None) is None: raise ImportError( "'cryptography' module missing required functionality. " "Try upgrading to v1.3.4 or newer." ) # pyOpenSSL 0.14 and above use cryptography for OpenSSL bindings. The _x509 # attribute is only present on those versions. from OpenSSL.crypto import X509 x509 = X509() if getattr(x509, "_x509", None) is None: raise ImportError( "'pyOpenSSL' module missing required functionality. " "Try upgrading to v0.14 or newer." ) def _dnsname_to_stdlib(name): """ Converts a dNSName SubjectAlternativeName field to the form used by the standard library on the given Python version. Cryptography produces a dNSName as a unicode string that was idna-decoded from ASCII bytes. We need to idna-encode that string to get it back, and then on Python 3 we also need to convert to unicode via UTF-8 (the stdlib uses PyUnicode_FromStringAndSize on it, which decodes via UTF-8). If the name cannot be idna-encoded then we return None signalling that the name given should be skipped. """ def idna_encode(name): """ Borrowed wholesale from the Python Cryptography Project. It turns out that we can't just safely call `idna.encode`: it can explode for wildcard names. This avoids that problem. """ import idna try: for prefix in [u"*.", u"."]: if name.startswith(prefix): name = name[len(prefix) :] return prefix.encode("ascii") + idna.encode(name) return idna.encode(name) except idna.core.IDNAError: return None # Don't send IPv6 addresses through the IDNA encoder. if ":" in name: return name name = idna_encode(name) if name is None: return None elif sys.version_info >= (3, 0): name = name.decode("utf-8") return name def get_subj_alt_name(peer_cert): """ Given an PyOpenSSL certificate, provides all the subject alternative names. """ # Pass the cert to cryptography, which has much better APIs for this. if hasattr(peer_cert, "to_cryptography"): cert = peer_cert.to_cryptography() else: # This is technically using private APIs, but should work across all # relevant versions before PyOpenSSL got a proper API for this. cert = _Certificate(openssl_backend, peer_cert._x509) # We want to find the SAN extension. Ask Cryptography to locate it (it's # faster than looping in Python) try: ext = cert.extensions.get_extension_for_class(x509.SubjectAlternativeName).value except x509.ExtensionNotFound: # No such extension, return the empty list. return [] except ( x509.DuplicateExtension, UnsupportedExtension, x509.UnsupportedGeneralNameType, UnicodeError, ) as e: # A problem has been found with the quality of the certificate. Assume # no SAN field is present. log.warning( "A problem was encountered with the certificate that prevented " "urllib3 from finding the SubjectAlternativeName field. This can " "affect certificate validation. The error was %s", e, ) return [] # We want to return dNSName and iPAddress fields. We need to cast the IPs # back to strings because the match_hostname function wants them as # strings. # Sadly the DNS names need to be idna encoded and then, on Python 3, UTF-8 # decoded. This is pretty frustrating, but that's what the standard library # does with certificates, and so we need to attempt to do the same. # We also want to skip over names which cannot be idna encoded. names = [ ("DNS", name) for name in map(_dnsname_to_stdlib, ext.get_values_for_type(x509.DNSName)) if name is not None ] names.extend( ("IP Address", str(name)) for name in ext.get_values_for_type(x509.IPAddress) ) return names class WrappedSocket(object): """API-compatibility wrapper for Python OpenSSL's Connection-class. Note: _makefile_refs, _drop() and _reuse() are needed for the garbage collector of pypy. """ def __init__(self, connection, socket, suppress_ragged_eofs=True): self.connection = connection self.socket = socket self.suppress_ragged_eofs = suppress_ragged_eofs self._makefile_refs = 0 self._closed = False def fileno(self): return self.socket.fileno() # Copy-pasted from Python 3.5 source code def _decref_socketios(self): if self._makefile_refs > 0: self._makefile_refs -= 1 if self._closed: self.close() def recv(self, *args, **kwargs): try: data = self.connection.recv(*args, **kwargs) except OpenSSL.SSL.SysCallError as e: if self.suppress_ragged_eofs and e.args == (-1, "Unexpected EOF"): return b"" else: raise SocketError(str(e)) except OpenSSL.SSL.ZeroReturnError: if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN: return b"" else: raise except OpenSSL.SSL.WantReadError: if not util.wait_for_read(self.socket, self.socket.gettimeout()): raise timeout("The read operation timed out") else: return self.recv(*args, **kwargs) # TLS 1.3 post-handshake authentication except OpenSSL.SSL.Error as e: raise ssl.SSLError("read error: %r" % e) else: return data def recv_into(self, *args, **kwargs): try: return self.connection.recv_into(*args, **kwargs) except OpenSSL.SSL.SysCallError as e: if self.suppress_ragged_eofs and e.args == (-1, "Unexpected EOF"): return 0 else: raise SocketError(str(e)) except OpenSSL.SSL.ZeroReturnError: if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN: return 0 else: raise except OpenSSL.SSL.WantReadError: if not util.wait_for_read(self.socket, self.socket.gettimeout()): raise timeout("The read operation timed out") else: return self.recv_into(*args, **kwargs) # TLS 1.3 post-handshake authentication except OpenSSL.SSL.Error as e: raise ssl.SSLError("read error: %r" % e) def settimeout(self, timeout): return self.socket.settimeout(timeout) def _send_until_done(self, data): while True: try: return self.connection.send(data) except OpenSSL.SSL.WantWriteError: if not util.wait_for_write(self.socket, self.socket.gettimeout()): raise timeout() continue except OpenSSL.SSL.SysCallError as e: raise SocketError(str(e)) def sendall(self, data): total_sent = 0 while total_sent < len(data): sent = self._send_until_done( data[total_sent : total_sent + SSL_WRITE_BLOCKSIZE] ) total_sent += sent def shutdown(self): # FIXME rethrow compatible exceptions should we ever use this self.connection.shutdown() def close(self): if self._makefile_refs < 1: try: self._closed = True return self.connection.close() except OpenSSL.SSL.Error: return else: self._makefile_refs -= 1 def getpeercert(self, binary_form=False): x509 = self.connection.get_peer_certificate() if not x509: return x509 if binary_form: return OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_ASN1, x509) return { "subject": ((("commonName", x509.get_subject().CN),),), "subjectAltName": get_subj_alt_name(x509), } def version(self): return self.connection.get_protocol_version_name() def _reuse(self): self._makefile_refs += 1 def _drop(self): if self._makefile_refs < 1: self.close() else: self._makefile_refs -= 1 if _fileobject: # Platform-specific: Python 2 def makefile(self, mode, bufsize=-1): self._makefile_refs += 1 return _fileobject(self, mode, bufsize, close=True) else: # Platform-specific: Python 3 makefile = backport_makefile WrappedSocket.makefile = makefile class PyOpenSSLContext(object): """ I am a wrapper class for the PyOpenSSL ``Context`` object. I am responsible for translating the interface of the standard library ``SSLContext`` object to calls into PyOpenSSL. """ def __init__(self, protocol): self.protocol = _openssl_versions[protocol] self._ctx = OpenSSL.SSL.Context(self.protocol) self._options = 0 self.check_hostname = False @property def options(self): return self._options @options.setter def options(self, value): self._options = value self._ctx.set_options(value) @property def verify_mode(self): return _openssl_to_stdlib_verify[self._ctx.get_verify_mode()] @verify_mode.setter def verify_mode(self, value): self._ctx.set_verify(_stdlib_to_openssl_verify[value], _verify_callback) def set_default_verify_paths(self): self._ctx.set_default_verify_paths() def set_ciphers(self, ciphers): if isinstance(ciphers, six.text_type): ciphers = ciphers.encode("utf-8") self._ctx.set_cipher_list(ciphers) def load_verify_locations(self, cafile=None, capath=None, cadata=None): if cafile is not None: cafile = cafile.encode("utf-8") if capath is not None: capath = capath.encode("utf-8") try: self._ctx.load_verify_locations(cafile, capath) if cadata is not None: self._ctx.load_verify_locations(BytesIO(cadata)) except OpenSSL.SSL.Error as e: raise ssl.SSLError("unable to load trusted certificates: %r" % e) def load_cert_chain(self, certfile, keyfile=None, password=None): self._ctx.use_certificate_chain_file(certfile) if password is not None: if not isinstance(password, six.binary_type): password = password.encode("utf-8") self._ctx.set_passwd_cb(lambda *_: password) self._ctx.use_privatekey_file(keyfile or certfile) def set_alpn_protocols(self, protocols): protocols = [six.ensure_binary(p) for p in protocols] return self._ctx.set_alpn_protos(protocols) def wrap_socket( self, sock, server_side=False, do_handshake_on_connect=True, suppress_ragged_eofs=True, server_hostname=None, ): cnx = OpenSSL.SSL.Connection(self._ctx, sock) if isinstance(server_hostname, six.text_type): # Platform-specific: Python 3 server_hostname = server_hostname.encode("utf-8") if server_hostname is not None: cnx.set_tlsext_host_name(server_hostname) cnx.set_connect_state() while True: try: cnx.do_handshake() except OpenSSL.SSL.WantReadError: if not util.wait_for_read(sock, sock.gettimeout()): raise timeout("select timed out") continue except OpenSSL.SSL.Error as e: raise ssl.SSLError("bad handshake: %r" % e) break return WrappedSocket(cnx, sock) def _verify_callback(cnx, x509, err_no, err_depth, return_code): return err_no == 0 ================================================ FILE: modules/urllib3/contrib/securetransport.py ================================================ """ SecureTranport support for urllib3 via ctypes. This makes platform-native TLS available to urllib3 users on macOS without the use of a compiler. This is an important feature because the Python Package Index is moving to become a TLSv1.2-or-higher server, and the default OpenSSL that ships with macOS is not capable of doing TLSv1.2. The only way to resolve this is to give macOS users an alternative solution to the problem, and that solution is to use SecureTransport. We use ctypes here because this solution must not require a compiler. That's because pip is not allowed to require a compiler either. This is not intended to be a seriously long-term solution to this problem. The hope is that PEP 543 will eventually solve this issue for us, at which point we can retire this contrib module. But in the short term, we need to solve the impending tire fire that is Python on Mac without this kind of contrib module. So...here we are. To use this module, simply import and inject it:: import urllib3.contrib.securetransport urllib3.contrib.securetransport.inject_into_urllib3() Happy TLSing! This code is a bastardised version of the code found in Will Bond's oscrypto library. An enormous debt is owed to him for blazing this trail for us. For that reason, this code should be considered to be covered both by urllib3's license and by oscrypto's: .. code-block:: Copyright (c) 2015-2016 Will Bond Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ from __future__ import absolute_import import contextlib import ctypes import errno import os.path import shutil import socket import ssl import struct import threading import weakref import six from .. import util from ._securetransport.bindings import CoreFoundation, Security, SecurityConst from ._securetransport.low_level import ( _assert_no_error, _build_tls_unknown_ca_alert, _cert_array_from_pem, _create_cfstring_array, _load_client_cert_chain, _temporary_keychain, ) try: # Platform-specific: Python 2 from socket import _fileobject except ImportError: # Platform-specific: Python 3 _fileobject = None from ..packages.backports.makefile import backport_makefile __all__ = ["inject_into_urllib3", "extract_from_urllib3"] # SNI always works HAS_SNI = True orig_util_HAS_SNI = util.HAS_SNI orig_util_SSLContext = util.ssl_.SSLContext # This dictionary is used by the read callback to obtain a handle to the # calling wrapped socket. This is a pretty silly approach, but for now it'll # do. I feel like I should be able to smuggle a handle to the wrapped socket # directly in the SSLConnectionRef, but for now this approach will work I # guess. # # We need to lock around this structure for inserts, but we don't do it for # reads/writes in the callbacks. The reasoning here goes as follows: # # 1. It is not possible to call into the callbacks before the dictionary is # populated, so once in the callback the id must be in the dictionary. # 2. The callbacks don't mutate the dictionary, they only read from it, and # so cannot conflict with any of the insertions. # # This is good: if we had to lock in the callbacks we'd drastically slow down # the performance of this code. _connection_refs = weakref.WeakValueDictionary() _connection_ref_lock = threading.Lock() # Limit writes to 16kB. This is OpenSSL's limit, but we'll cargo-cult it over # for no better reason than we need *a* limit, and this one is right there. SSL_WRITE_BLOCKSIZE = 16384 # This is our equivalent of util.ssl_.DEFAULT_CIPHERS, but expanded out to # individual cipher suites. We need to do this because this is how # SecureTransport wants them. CIPHER_SUITES = [ SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, SecurityConst.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, SecurityConst.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, SecurityConst.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, SecurityConst.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, SecurityConst.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, SecurityConst.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, SecurityConst.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, SecurityConst.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, SecurityConst.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, SecurityConst.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, SecurityConst.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, SecurityConst.TLS_DHE_RSA_WITH_AES_256_CBC_SHA, SecurityConst.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, SecurityConst.TLS_DHE_RSA_WITH_AES_128_CBC_SHA, SecurityConst.TLS_AES_256_GCM_SHA384, SecurityConst.TLS_AES_128_GCM_SHA256, SecurityConst.TLS_RSA_WITH_AES_256_GCM_SHA384, SecurityConst.TLS_RSA_WITH_AES_128_GCM_SHA256, SecurityConst.TLS_AES_128_CCM_8_SHA256, SecurityConst.TLS_AES_128_CCM_SHA256, SecurityConst.TLS_RSA_WITH_AES_256_CBC_SHA256, SecurityConst.TLS_RSA_WITH_AES_128_CBC_SHA256, SecurityConst.TLS_RSA_WITH_AES_256_CBC_SHA, SecurityConst.TLS_RSA_WITH_AES_128_CBC_SHA, ] # Basically this is simple: for PROTOCOL_SSLv23 we turn it into a low of # TLSv1 and a high of TLSv1.2. For everything else, we pin to that version. # TLSv1 to 1.2 are supported on macOS 10.8+ _protocol_to_min_max = { util.PROTOCOL_TLS: (SecurityConst.kTLSProtocol1, SecurityConst.kTLSProtocol12) } if hasattr(ssl, "PROTOCOL_SSLv2"): _protocol_to_min_max[ssl.PROTOCOL_SSLv2] = ( SecurityConst.kSSLProtocol2, SecurityConst.kSSLProtocol2, ) if hasattr(ssl, "PROTOCOL_SSLv3"): _protocol_to_min_max[ssl.PROTOCOL_SSLv3] = ( SecurityConst.kSSLProtocol3, SecurityConst.kSSLProtocol3, ) if hasattr(ssl, "PROTOCOL_TLSv1"): _protocol_to_min_max[ssl.PROTOCOL_TLSv1] = ( SecurityConst.kTLSProtocol1, SecurityConst.kTLSProtocol1, ) if hasattr(ssl, "PROTOCOL_TLSv1_1"): _protocol_to_min_max[ssl.PROTOCOL_TLSv1_1] = ( SecurityConst.kTLSProtocol11, SecurityConst.kTLSProtocol11, ) if hasattr(ssl, "PROTOCOL_TLSv1_2"): _protocol_to_min_max[ssl.PROTOCOL_TLSv1_2] = ( SecurityConst.kTLSProtocol12, SecurityConst.kTLSProtocol12, ) def inject_into_urllib3(): """ Monkey-patch urllib3 with SecureTransport-backed SSL-support. """ util.SSLContext = SecureTransportContext util.ssl_.SSLContext = SecureTransportContext util.HAS_SNI = HAS_SNI util.ssl_.HAS_SNI = HAS_SNI util.IS_SECURETRANSPORT = True util.ssl_.IS_SECURETRANSPORT = True def extract_from_urllib3(): """ Undo monkey-patching by :func:`inject_into_urllib3`. """ util.SSLContext = orig_util_SSLContext util.ssl_.SSLContext = orig_util_SSLContext util.HAS_SNI = orig_util_HAS_SNI util.ssl_.HAS_SNI = orig_util_HAS_SNI util.IS_SECURETRANSPORT = False util.ssl_.IS_SECURETRANSPORT = False def _read_callback(connection_id, data_buffer, data_length_pointer): """ SecureTransport read callback. This is called by ST to request that data be returned from the socket. """ wrapped_socket = None try: wrapped_socket = _connection_refs.get(connection_id) if wrapped_socket is None: return SecurityConst.errSSLInternal base_socket = wrapped_socket.socket requested_length = data_length_pointer[0] timeout = wrapped_socket.gettimeout() error = None read_count = 0 try: while read_count < requested_length: if timeout is None or timeout >= 0: if not util.wait_for_read(base_socket, timeout): raise socket.error(errno.EAGAIN, "timed out") remaining = requested_length - read_count buffer = (ctypes.c_char * remaining).from_address( data_buffer + read_count ) chunk_size = base_socket.recv_into(buffer, remaining) read_count += chunk_size if not chunk_size: if not read_count: return SecurityConst.errSSLClosedGraceful break except (socket.error) as e: error = e.errno if error is not None and error != errno.EAGAIN: data_length_pointer[0] = read_count if error == errno.ECONNRESET or error == errno.EPIPE: return SecurityConst.errSSLClosedAbort raise data_length_pointer[0] = read_count if read_count != requested_length: return SecurityConst.errSSLWouldBlock return 0 except Exception as e: if wrapped_socket is not None: wrapped_socket._exception = e return SecurityConst.errSSLInternal def _write_callback(connection_id, data_buffer, data_length_pointer): """ SecureTransport write callback. This is called by ST to request that data actually be sent on the network. """ wrapped_socket = None try: wrapped_socket = _connection_refs.get(connection_id) if wrapped_socket is None: return SecurityConst.errSSLInternal base_socket = wrapped_socket.socket bytes_to_write = data_length_pointer[0] data = ctypes.string_at(data_buffer, bytes_to_write) timeout = wrapped_socket.gettimeout() error = None sent = 0 try: while sent < bytes_to_write: if timeout is None or timeout >= 0: if not util.wait_for_write(base_socket, timeout): raise socket.error(errno.EAGAIN, "timed out") chunk_sent = base_socket.send(data) sent += chunk_sent # This has some needless copying here, but I'm not sure there's # much value in optimising this data path. data = data[chunk_sent:] except (socket.error) as e: error = e.errno if error is not None and error != errno.EAGAIN: data_length_pointer[0] = sent if error == errno.ECONNRESET or error == errno.EPIPE: return SecurityConst.errSSLClosedAbort raise data_length_pointer[0] = sent if sent != bytes_to_write: return SecurityConst.errSSLWouldBlock return 0 except Exception as e: if wrapped_socket is not None: wrapped_socket._exception = e return SecurityConst.errSSLInternal # We need to keep these two objects references alive: if they get GC'd while # in use then SecureTransport could attempt to call a function that is in freed # memory. That would be...uh...bad. Yeah, that's the word. Bad. _read_callback_pointer = Security.SSLReadFunc(_read_callback) _write_callback_pointer = Security.SSLWriteFunc(_write_callback) class WrappedSocket(object): """ API-compatibility wrapper for Python's OpenSSL wrapped socket object. Note: _makefile_refs, _drop(), and _reuse() are needed for the garbage collector of PyPy. """ def __init__(self, socket): self.socket = socket self.context = None self._makefile_refs = 0 self._closed = False self._exception = None self._keychain = None self._keychain_dir = None self._client_cert_chain = None # We save off the previously-configured timeout and then set it to # zero. This is done because we use select and friends to handle the # timeouts, but if we leave the timeout set on the lower socket then # Python will "kindly" call select on that socket again for us. Avoid # that by forcing the timeout to zero. self._timeout = self.socket.gettimeout() self.socket.settimeout(0) @contextlib.contextmanager def _raise_on_error(self): """ A context manager that can be used to wrap calls that do I/O from SecureTransport. If any of the I/O callbacks hit an exception, this context manager will correctly propagate the exception after the fact. This avoids silently swallowing those exceptions. It also correctly forces the socket closed. """ self._exception = None # We explicitly don't catch around this yield because in the unlikely # event that an exception was hit in the block we don't want to swallow # it. yield if self._exception is not None: exception, self._exception = self._exception, None self.close() raise exception def _set_ciphers(self): """ Sets up the allowed ciphers. By default this matches the set in util.ssl_.DEFAULT_CIPHERS, at least as supported by macOS. This is done custom and doesn't allow changing at this time, mostly because parsing OpenSSL cipher strings is going to be a freaking nightmare. """ ciphers = (Security.SSLCipherSuite * len(CIPHER_SUITES))(*CIPHER_SUITES) result = Security.SSLSetEnabledCiphers( self.context, ciphers, len(CIPHER_SUITES) ) _assert_no_error(result) def _set_alpn_protocols(self, protocols): """ Sets up the ALPN protocols on the context. """ if not protocols: return protocols_arr = _create_cfstring_array(protocols) try: result = Security.SSLSetALPNProtocols(self.context, protocols_arr) _assert_no_error(result) finally: CoreFoundation.CFRelease(protocols_arr) def _custom_validate(self, verify, trust_bundle): """ Called when we have set custom validation. We do this in two cases: first, when cert validation is entirely disabled; and second, when using a custom trust DB. Raises an SSLError if the connection is not trusted. """ # If we disabled cert validation, just say: cool. if not verify: return successes = ( SecurityConst.kSecTrustResultUnspecified, SecurityConst.kSecTrustResultProceed, ) try: trust_result = self._evaluate_trust(trust_bundle) if trust_result in successes: return reason = "error code: %d" % (trust_result,) except Exception as e: # Do not trust on error reason = "exception: %r" % (e,) # SecureTransport does not send an alert nor shuts down the connection. rec = _build_tls_unknown_ca_alert(self.version()) self.socket.sendall(rec) # close the connection immediately # l_onoff = 1, activate linger # l_linger = 0, linger for 0 seoncds opts = struct.pack("ii", 1, 0) self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, opts) self.close() raise ssl.SSLError("certificate verify failed, %s" % reason) def _evaluate_trust(self, trust_bundle): # We want data in memory, so load it up. if os.path.isfile(trust_bundle): with open(trust_bundle, "rb") as f: trust_bundle = f.read() cert_array = None trust = Security.SecTrustRef() try: # Get a CFArray that contains the certs we want. cert_array = _cert_array_from_pem(trust_bundle) # Ok, now the hard part. We want to get the SecTrustRef that ST has # created for this connection, shove our CAs into it, tell ST to # ignore everything else it knows, and then ask if it can build a # chain. This is a buuuunch of code. result = Security.SSLCopyPeerTrust(self.context, ctypes.byref(trust)) _assert_no_error(result) if not trust: raise ssl.SSLError("Failed to copy trust reference") result = Security.SecTrustSetAnchorCertificates(trust, cert_array) _assert_no_error(result) result = Security.SecTrustSetAnchorCertificatesOnly(trust, True) _assert_no_error(result) trust_result = Security.SecTrustResultType() result = Security.SecTrustEvaluate(trust, ctypes.byref(trust_result)) _assert_no_error(result) finally: if trust: CoreFoundation.CFRelease(trust) if cert_array is not None: CoreFoundation.CFRelease(cert_array) return trust_result.value def handshake( self, server_hostname, verify, trust_bundle, min_version, max_version, client_cert, client_key, client_key_passphrase, alpn_protocols, ): """ Actually performs the TLS handshake. This is run automatically by wrapped socket, and shouldn't be needed in user code. """ # First, we do the initial bits of connection setup. We need to create # a context, set its I/O funcs, and set the connection reference. self.context = Security.SSLCreateContext( None, SecurityConst.kSSLClientSide, SecurityConst.kSSLStreamType ) result = Security.SSLSetIOFuncs( self.context, _read_callback_pointer, _write_callback_pointer ) _assert_no_error(result) # Here we need to compute the handle to use. We do this by taking the # id of self modulo 2**31 - 1. If this is already in the dictionary, we # just keep incrementing by one until we find a free space. with _connection_ref_lock: handle = id(self) % 2147483647 while handle in _connection_refs: handle = (handle + 1) % 2147483647 _connection_refs[handle] = self result = Security.SSLSetConnection(self.context, handle) _assert_no_error(result) # If we have a server hostname, we should set that too. if server_hostname: if not isinstance(server_hostname, bytes): server_hostname = server_hostname.encode("utf-8") result = Security.SSLSetPeerDomainName( self.context, server_hostname, len(server_hostname) ) _assert_no_error(result) # Setup the ciphers. self._set_ciphers() # Setup the ALPN protocols. self._set_alpn_protocols(alpn_protocols) # Set the minimum and maximum TLS versions. result = Security.SSLSetProtocolVersionMin(self.context, min_version) _assert_no_error(result) result = Security.SSLSetProtocolVersionMax(self.context, max_version) _assert_no_error(result) # If there's a trust DB, we need to use it. We do that by telling # SecureTransport to break on server auth. We also do that if we don't # want to validate the certs at all: we just won't actually do any # authing in that case. if not verify or trust_bundle is not None: result = Security.SSLSetSessionOption( self.context, SecurityConst.kSSLSessionOptionBreakOnServerAuth, True ) _assert_no_error(result) # If there's a client cert, we need to use it. if client_cert: self._keychain, self._keychain_dir = _temporary_keychain() self._client_cert_chain = _load_client_cert_chain( self._keychain, client_cert, client_key ) result = Security.SSLSetCertificate(self.context, self._client_cert_chain) _assert_no_error(result) while True: with self._raise_on_error(): result = Security.SSLHandshake(self.context) if result == SecurityConst.errSSLWouldBlock: raise socket.timeout("handshake timed out") elif result == SecurityConst.errSSLServerAuthCompleted: self._custom_validate(verify, trust_bundle) continue else: _assert_no_error(result) break def fileno(self): return self.socket.fileno() # Copy-pasted from Python 3.5 source code def _decref_socketios(self): if self._makefile_refs > 0: self._makefile_refs -= 1 if self._closed: self.close() def recv(self, bufsiz): buffer = ctypes.create_string_buffer(bufsiz) bytes_read = self.recv_into(buffer, bufsiz) data = buffer[:bytes_read] return data def recv_into(self, buffer, nbytes=None): # Read short on EOF. if self._closed: return 0 if nbytes is None: nbytes = len(buffer) buffer = (ctypes.c_char * nbytes).from_buffer(buffer) processed_bytes = ctypes.c_size_t(0) with self._raise_on_error(): result = Security.SSLRead( self.context, buffer, nbytes, ctypes.byref(processed_bytes) ) # There are some result codes that we want to treat as "not always # errors". Specifically, those are errSSLWouldBlock, # errSSLClosedGraceful, and errSSLClosedNoNotify. if result == SecurityConst.errSSLWouldBlock: # If we didn't process any bytes, then this was just a time out. # However, we can get errSSLWouldBlock in situations when we *did* # read some data, and in those cases we should just read "short" # and return. if processed_bytes.value == 0: # Timed out, no data read. raise socket.timeout("recv timed out") elif result in ( SecurityConst.errSSLClosedGraceful, SecurityConst.errSSLClosedNoNotify, ): # The remote peer has closed this connection. We should do so as # well. Note that we don't actually return here because in # principle this could actually be fired along with return data. # It's unlikely though. self.close() else: _assert_no_error(result) # Ok, we read and probably succeeded. We should return whatever data # was actually read. return processed_bytes.value def settimeout(self, timeout): self._timeout = timeout def gettimeout(self): return self._timeout def send(self, data): processed_bytes = ctypes.c_size_t(0) with self._raise_on_error(): result = Security.SSLWrite( self.context, data, len(data), ctypes.byref(processed_bytes) ) if result == SecurityConst.errSSLWouldBlock and processed_bytes.value == 0: # Timed out raise socket.timeout("send timed out") else: _assert_no_error(result) # We sent, and probably succeeded. Tell them how much we sent. return processed_bytes.value def sendall(self, data): total_sent = 0 while total_sent < len(data): sent = self.send(data[total_sent : total_sent + SSL_WRITE_BLOCKSIZE]) total_sent += sent def shutdown(self): with self._raise_on_error(): Security.SSLClose(self.context) def close(self): # TODO: should I do clean shutdown here? Do I have to? if self._makefile_refs < 1: self._closed = True if self.context: CoreFoundation.CFRelease(self.context) self.context = None if self._client_cert_chain: CoreFoundation.CFRelease(self._client_cert_chain) self._client_cert_chain = None if self._keychain: Security.SecKeychainDelete(self._keychain) CoreFoundation.CFRelease(self._keychain) shutil.rmtree(self._keychain_dir) self._keychain = self._keychain_dir = None return self.socket.close() else: self._makefile_refs -= 1 def getpeercert(self, binary_form=False): # Urgh, annoying. # # Here's how we do this: # # 1. Call SSLCopyPeerTrust to get hold of the trust object for this # connection. # 2. Call SecTrustGetCertificateAtIndex for index 0 to get the leaf. # 3. To get the CN, call SecCertificateCopyCommonName and process that # string so that it's of the appropriate type. # 4. To get the SAN, we need to do something a bit more complex: # a. Call SecCertificateCopyValues to get the data, requesting # kSecOIDSubjectAltName. # b. Mess about with this dictionary to try to get the SANs out. # # This is gross. Really gross. It's going to be a few hundred LoC extra # just to repeat something that SecureTransport can *already do*. So my # operating assumption at this time is that what we want to do is # instead to just flag to urllib3 that it shouldn't do its own hostname # validation when using SecureTransport. if not binary_form: raise ValueError("SecureTransport only supports dumping binary certs") trust = Security.SecTrustRef() certdata = None der_bytes = None try: # Grab the trust store. result = Security.SSLCopyPeerTrust(self.context, ctypes.byref(trust)) _assert_no_error(result) if not trust: # Probably we haven't done the handshake yet. No biggie. return None cert_count = Security.SecTrustGetCertificateCount(trust) if not cert_count: # Also a case that might happen if we haven't handshaked. # Handshook? Handshaken? return None leaf = Security.SecTrustGetCertificateAtIndex(trust, 0) assert leaf # Ok, now we want the DER bytes. certdata = Security.SecCertificateCopyData(leaf) assert certdata data_length = CoreFoundation.CFDataGetLength(certdata) data_buffer = CoreFoundation.CFDataGetBytePtr(certdata) der_bytes = ctypes.string_at(data_buffer, data_length) finally: if certdata: CoreFoundation.CFRelease(certdata) if trust: CoreFoundation.CFRelease(trust) return der_bytes def version(self): protocol = Security.SSLProtocol() result = Security.SSLGetNegotiatedProtocolVersion( self.context, ctypes.byref(protocol) ) _assert_no_error(result) if protocol.value == SecurityConst.kTLSProtocol13: raise ssl.SSLError("SecureTransport does not support TLS 1.3") elif protocol.value == SecurityConst.kTLSProtocol12: return "TLSv1.2" elif protocol.value == SecurityConst.kTLSProtocol11: return "TLSv1.1" elif protocol.value == SecurityConst.kTLSProtocol1: return "TLSv1" elif protocol.value == SecurityConst.kSSLProtocol3: return "SSLv3" elif protocol.value == SecurityConst.kSSLProtocol2: return "SSLv2" else: raise ssl.SSLError("Unknown TLS version: %r" % protocol) def _reuse(self): self._makefile_refs += 1 def _drop(self): if self._makefile_refs < 1: self.close() else: self._makefile_refs -= 1 if _fileobject: # Platform-specific: Python 2 def makefile(self, mode, bufsize=-1): self._makefile_refs += 1 return _fileobject(self, mode, bufsize, close=True) else: # Platform-specific: Python 3 def makefile(self, mode="r", buffering=None, *args, **kwargs): # We disable buffering with SecureTransport because it conflicts with # the buffering that ST does internally (see issue #1153 for more). buffering = 0 return backport_makefile(self, mode, buffering, *args, **kwargs) WrappedSocket.makefile = makefile class SecureTransportContext(object): """ I am a wrapper class for the SecureTransport library, to translate the interface of the standard library ``SSLContext`` object to calls into SecureTransport. """ def __init__(self, protocol): self._min_version, self._max_version = _protocol_to_min_max[protocol] self._options = 0 self._verify = False self._trust_bundle = None self._client_cert = None self._client_key = None self._client_key_passphrase = None self._alpn_protocols = None @property def check_hostname(self): """ SecureTransport cannot have its hostname checking disabled. For more, see the comment on getpeercert() in this file. """ return True @check_hostname.setter def check_hostname(self, value): """ SecureTransport cannot have its hostname checking disabled. For more, see the comment on getpeercert() in this file. """ pass @property def options(self): # TODO: Well, crap. # # So this is the bit of the code that is the most likely to cause us # trouble. Essentially we need to enumerate all of the SSL options that # users might want to use and try to see if we can sensibly translate # them, or whether we should just ignore them. return self._options @options.setter def options(self, value): # TODO: Update in line with above. self._options = value @property def verify_mode(self): return ssl.CERT_REQUIRED if self._verify else ssl.CERT_NONE @verify_mode.setter def verify_mode(self, value): self._verify = True if value == ssl.CERT_REQUIRED else False def set_default_verify_paths(self): # So, this has to do something a bit weird. Specifically, what it does # is nothing. # # This means that, if we had previously had load_verify_locations # called, this does not undo that. We need to do that because it turns # out that the rest of the urllib3 code will attempt to load the # default verify paths if it hasn't been told about any paths, even if # the context itself was sometime earlier. We resolve that by just # ignoring it. pass def load_default_certs(self): return self.set_default_verify_paths() def set_ciphers(self, ciphers): # For now, we just require the default cipher string. if ciphers != util.ssl_.DEFAULT_CIPHERS: raise ValueError("SecureTransport doesn't support custom cipher strings") def load_verify_locations(self, cafile=None, capath=None, cadata=None): # OK, we only really support cadata and cafile. if capath is not None: raise ValueError("SecureTransport does not support cert directories") # Raise if cafile does not exist. if cafile is not None: with open(cafile): pass self._trust_bundle = cafile or cadata def load_cert_chain(self, certfile, keyfile=None, password=None): self._client_cert = certfile self._client_key = keyfile self._client_cert_passphrase = password def set_alpn_protocols(self, protocols): """ Sets the ALPN protocols that will later be set on the context. Raises a NotImplementedError if ALPN is not supported. """ if not hasattr(Security, "SSLSetALPNProtocols"): raise NotImplementedError( "SecureTransport supports ALPN only in macOS 10.12+" ) self._alpn_protocols = [six.ensure_binary(p) for p in protocols] def wrap_socket( self, sock, server_side=False, do_handshake_on_connect=True, suppress_ragged_eofs=True, server_hostname=None, ): # So, what do we do here? Firstly, we assert some properties. This is a # stripped down shim, so there is some functionality we don't support. # See PEP 543 for the real deal. assert not server_side assert do_handshake_on_connect assert suppress_ragged_eofs # Ok, we're good to go. Now we want to create the wrapped socket object # and store it in the appropriate place. wrapped_socket = WrappedSocket(sock) # Now we can handshake wrapped_socket.handshake( server_hostname, self._verify, self._trust_bundle, self._min_version, self._max_version, self._client_cert, self._client_key, self._client_key_passphrase, self._alpn_protocols, ) return wrapped_socket ================================================ FILE: modules/urllib3/contrib/socks.py ================================================ # -*- coding: utf-8 -*- """ This module contains provisional support for SOCKS proxies from within urllib3. This module supports SOCKS4, SOCKS4A (an extension of SOCKS4), and SOCKS5. To enable its functionality, either install PySocks or install this module with the ``socks`` extra. The SOCKS implementation supports the full range of urllib3 features. It also supports the following SOCKS features: - SOCKS4A (``proxy_url='socks4a://...``) - SOCKS4 (``proxy_url='socks4://...``) - SOCKS5 with remote DNS (``proxy_url='socks5h://...``) - SOCKS5 with local DNS (``proxy_url='socks5://...``) - Usernames and passwords for the SOCKS proxy .. note:: It is recommended to use ``socks5h://`` or ``socks4a://`` schemes in your ``proxy_url`` to ensure that DNS resolution is done from the remote server instead of client-side when connecting to a domain name. SOCKS4 supports IPv4 and domain names with the SOCKS4A extension. SOCKS5 supports IPv4, IPv6, and domain names. When connecting to a SOCKS4 proxy the ``username`` portion of the ``proxy_url`` will be sent as the ``userid`` section of the SOCKS request: .. code-block:: python proxy_url="socks4a://@proxy-host" When connecting to a SOCKS5 proxy the ``username`` and ``password`` portion of the ``proxy_url`` will be sent as the username/password to authenticate with the proxy: .. code-block:: python proxy_url="socks5h://:@proxy-host" """ from __future__ import absolute_import try: import socks except ImportError: import warnings from ..exceptions import DependencyWarning warnings.warn( ( "SOCKS support in urllib3 requires the installation of optional " "dependencies: specifically, PySocks. For more information, see " "https://urllib3.readthedocs.io/en/latest/contrib.html#socks-proxies" ), DependencyWarning, ) raise from socket import error as SocketError from socket import timeout as SocketTimeout from ..connection import HTTPConnection, HTTPSConnection from ..connectionpool import HTTPConnectionPool, HTTPSConnectionPool from ..exceptions import ConnectTimeoutError, NewConnectionError from ..poolmanager import PoolManager from ..util.url import parse_url try: import ssl except ImportError: ssl = None class SOCKSConnection(HTTPConnection): """ A plain-text HTTP connection that connects via a SOCKS proxy. """ def __init__(self, *args, **kwargs): self._socks_options = kwargs.pop("_socks_options") super(SOCKSConnection, self).__init__(*args, **kwargs) def _new_conn(self): """ Establish a new connection via the SOCKS proxy. """ extra_kw = {} if self.source_address: extra_kw["source_address"] = self.source_address if self.socket_options: extra_kw["socket_options"] = self.socket_options try: conn = socks.create_connection( (self.host, self.port), proxy_type=self._socks_options["socks_version"], proxy_addr=self._socks_options["proxy_host"], proxy_port=self._socks_options["proxy_port"], proxy_username=self._socks_options["username"], proxy_password=self._socks_options["password"], proxy_rdns=self._socks_options["rdns"], timeout=self.timeout, **extra_kw ) except SocketTimeout: raise ConnectTimeoutError( self, "Connection to %s timed out. (connect timeout=%s)" % (self.host, self.timeout), ) except socks.ProxyError as e: # This is fragile as hell, but it seems to be the only way to raise # useful errors here. if e.socket_err: error = e.socket_err if isinstance(error, SocketTimeout): raise ConnectTimeoutError( self, "Connection to %s timed out. (connect timeout=%s)" % (self.host, self.timeout), ) else: raise NewConnectionError( self, "Failed to establish a new connection: %s" % error ) else: raise NewConnectionError( self, "Failed to establish a new connection: %s" % e ) except SocketError as e: # Defensive: PySocks should catch all these. raise NewConnectionError( self, "Failed to establish a new connection: %s" % e ) return conn # We don't need to duplicate the Verified/Unverified distinction from # urllib3/connection.py here because the HTTPSConnection will already have been # correctly set to either the Verified or Unverified form by that module. This # means the SOCKSHTTPSConnection will automatically be the correct type. class SOCKSHTTPSConnection(SOCKSConnection, HTTPSConnection): pass class SOCKSHTTPConnectionPool(HTTPConnectionPool): ConnectionCls = SOCKSConnection class SOCKSHTTPSConnectionPool(HTTPSConnectionPool): ConnectionCls = SOCKSHTTPSConnection class SOCKSProxyManager(PoolManager): """ A version of the urllib3 ProxyManager that routes connections via the defined SOCKS proxy. """ pool_classes_by_scheme = { "http": SOCKSHTTPConnectionPool, "https": SOCKSHTTPSConnectionPool, } def __init__( self, proxy_url, username=None, password=None, num_pools=10, headers=None, **connection_pool_kw ): parsed = parse_url(proxy_url) if username is None and password is None and parsed.auth is not None: split = parsed.auth.split(":") if len(split) == 2: username, password = split if parsed.scheme == "socks5": socks_version = socks.PROXY_TYPE_SOCKS5 rdns = False elif parsed.scheme == "socks5h": socks_version = socks.PROXY_TYPE_SOCKS5 rdns = True elif parsed.scheme == "socks4": socks_version = socks.PROXY_TYPE_SOCKS4 rdns = False elif parsed.scheme == "socks4a": socks_version = socks.PROXY_TYPE_SOCKS4 rdns = True else: raise ValueError("Unable to determine SOCKS version from %s" % proxy_url) self.proxy_url = proxy_url socks_options = { "socks_version": socks_version, "proxy_host": parsed.host, "proxy_port": parsed.port, "username": username, "password": password, "rdns": rdns, } connection_pool_kw["_socks_options"] = socks_options super(SOCKSProxyManager, self).__init__( num_pools, headers, **connection_pool_kw ) self.pool_classes_by_scheme = SOCKSProxyManager.pool_classes_by_scheme ================================================ FILE: mysterium.py ================================================ # Made by @venaxyt on Github (helped by @IDRALOU, @Bleu-No and @vjousse) # @venaxyt: All code and interface | @Bleu-No: Blue Mysterium | @vjousse: Linux support # >>> https://github.com/venaxyt/mysterium # Checking if needed modules are installed import argparse, platform, zipfile, sys, os from shutil import copyfile # Detecting the operating system is_windows = True if platform.system() == "Windows" else False try: import gratient, fade except: try: if is_windows: output = ">nul" else: output = "/dev/null" os.system(f"py -m pip install -r requirements.txt {output}") import gratient, fade except: exit() system = platform.system() if is_windows: # Mysterium top bar title os.system("title 𝙈 𝙔 𝙎 𝙏 𝙀 𝙍 𝙄 𝙐 𝙈 (Github: @venaxyt)") # Definitions def clear(): if is_windows: os.system("cls") else: os.system("clear") def pause(): if is_windows: os.system(f"pause >nul") else: input() def leave(): try: sys.exit() except: exit() def error(error): print(gratient.red(f" [>] Error: {error}"), end="") pause(); clear(); leave() # Custom purple gratient color definition def purple(text): os.system("") faded = "" down = False for line in text.splitlines(): red = 40 for character in line: if down: red -= 3 else: red += 3 if red > 254: red = 255 down = True elif red < 1: red = 30 down = False faded += (f"\033[38;2;{red};0;220m{character}\033[0m") return faded # Gradient coloured banner banner = f""" ::: ::: ::: ::: :::::::: ::::::::::: :::::::::: ::::::::: ::::::::::: ::: ::: ::: ::: :+:+: :+:+: :+: :+: :+: :+: :+: :+: :+: :+: :+: :+: :+: :+:+: :+:+: +:+ +:+:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+:+ +:+ +#+ +:+ +#+ +#++: +#++:++#++ +#+ +#++:++# +#++:++#: +#+ +#+ +:+ +#+ +:+ +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+ #+# #+# #+# #+# #+# #+# #+# #+# #+# #+# #+# #+# #+# #+# ### ### ### ######## ### ########## ### ### ########### ######## ### ### {purple("[>] Mysterium has been created by @venaxyt on Github / https://github.com/venaxyt/mysterium / Mysterium 2021©")} {purple("[>] To inspect a code encrypted with Pyarmor, put it in a zip with the pytransform folder and it's architecture")} {purple(f"[>] Mysterium version : 1.2.6 / Running with Python {sys.version_info[0]}.{sys.version_info[1]}.{sys.version_info[2]} / Discord server : https://discord.gg/mysterium")} """ # [EN]: Editing this banner will not make you a programmer / [FR]: Ce n'est pas en changeant la bannière que vous allez devenir développeur # Allow mysterium to be used in command line interface (CLI) parser = argparse.ArgumentParser(description="[+] Mysterium CLI") parser.add_argument("-f", dest="filepath", required=False, default=None, help="The path to the file you want to inspect") args = parser.parse_args() blue_mysterium = False uninspected_file_directory = args.filepath # Mysterium user makes his choice about Blue Mysterium usage # Blue Mysterium is not working anymore blue_mysterium = "no" #while not blue_mysterium: # clear() # print(fade.water(banner)) # try: # blue_mysterium = input(purple(" [>] Do you want to use Blue Mysterium (y/n) : ") + "\033[38;2;184;0;230m") # except: # pass if blue_mysterium.lower() in ["y", "ye", "yes"]: blue_mysterium = True elif blue_mysterium.lower() in ["n", "no"]: blue_mysterium = False else: error(f'You have to make your choice, "{blue_mysterium}" is not a choice') # Mysterium user inputs his uninspected file directory if not specified # With the -f flag while not uninspected_file_directory: clear() print(fade.water(banner)) try: uninspected_file_directory = input(purple(" [>] Enter uninspected file path : ") + "\033[38;2;148;0;230m") except: pass uninspected_file_directory = uninspected_file_directory.replace("'", "").replace('"', "") uninspected_file_name = "uninspected" filename, uninspected_file_extension = os.path.splitext(uninspected_file_directory) # Removing the dot from the file's extension uninspected_file_extension = uninspected_file_extension[1:] # Checking if Mysterium user specified uninspected file extension if not uninspected_file_extension: error("You have to specify the file extension") supported_file_extensions = ["py", "pyc", "exe", "zip"] if uninspected_file_extension not in supported_file_extensions: error("This extension of the file is not supported. You can only scan the following formats: {}.".format(",".join(supported_file_extensions))) # Extraction of Python files from the executable one if uninspected_file_extension == "exe": copyfile(uninspected_file_directory, os.path.join("executable", "uninspected.exe")) print(gratient.blue("\n [>] Trying to extracted Python files from the executable..."), end="") os.system("cd executable && python pyinstxtractor.py uninspected.exe") if os.path.isdir(os.path.join("executable", "uninspected.exe_extracted")): print(gratient.blue("\n [>] Successfully extracted files from the executable"), end="") else: error("There was an error extracting Python files from the executable") # Remove exported executable file os.remove(os.path.join("executable", "uninspected.exe")) print("") # To jump a line (for a better aesthetic interface) uninspected_file_name = input(purple(" [>] Enter Python file name with extension : ") + "\033[38;2;178;0;230m") exe_base_path = os.path.join("executable", "uninspected.exe_extracted") if os.path.isfile(os.path.join(exe_base_path, f"{uninspected_file_name}.pyc")): copyfile(os.path.join(exe_base_path, f"{uninspected_file_name}.pyc"), os.path.join("modules", f"{uninspected_file_name}.pyc")) elif os.path.isfile(os.path.join(exe_base_path, f"{uninspected_file_name}")): copyfile(os.path.join(exe_base_path, f"{uninspected_file_name}"), os.path.join("modules", f"{uninspected_file_name}.pyc")) else: error("The extracted pyc file has not been found") # Define uninspected file extension as .pyc uninspected_file_extension = "pyc" # Check if Mysterium directory exists if not os.path.isdir("modules"): error("You have to download modules before inspecting any file") if not uninspected_file_extension == "exe": try: copyfile(uninspected_file_directory, os.path.join("modules", f"uninspected.{uninspected_file_extension}")) except FileNotFoundError: error("File to inspect not found. Check the uninspected file path.") # Unzip the file if it is in a "zip" file (used for Pyarmor / external encryptages) if uninspected_file_extension == "zip": zipfile.ZipFile(os.path.join("modules", "uninspected.zip"), "r").extractall("modules") os.remove(os.path.join("modules", "uninspected.zip")) uninspected_file_name = input(purple(" [>] Enter Python obfuscated file name with extension : ") + "\033[38;2;211;0;230m") uninspected_file_name, uninspected_file_extension = os.path.splitext(uninspected_file_name) # Remove the dot from the extension uninspected_file_extension = uninspected_file_extension[1:] if not uninspected_file_extension == "py" and not uninspected_file_extension == "pyc": error("The file extension can only be .py or .pyc") # Jump a line even zip file detected (for a better aesthetic interface) print("") # Define pyarmor uninspected file as ".py" (important for zip / exe files) if not uninspected_file_extension == "pyc": uninspected_file_extension = "py" # Start uninspected file under Mysterium modules if user enabled it if blue_mysterium: import modules.blue modules.blue.Blue(f'modules\\{uninspected_file_name}.{uninspected_file_extension}', uninspected_file_directory) else: os.system("python {}".format(os.path.join("modules", f"{uninspected_file_name}.{uninspected_file_extension}"))) print(gratient.blue("\n [>] The code is finished"), end="") # os.remove(os.path.join("modules", f"{uninspected_file_name}.{uninspected_file_extension}")) # It's better to keep it to avoid re-extracting the zip folder pause(); clear(); leave() ================================================ FILE: requirements.txt ================================================ wheel fade gratient