1# Copyright 2012-2019, Damian Johnson and The Tor Project 2# See LICENSE for licensing information 3 4""" 5Miscellaneous utility functions for working with tor. 6 7.. versionadded:: 1.2.0 8 9**Module Overview:** 10 11:: 12 13 is_valid_fingerprint - checks if a string is a valid tor relay fingerprint 14 is_valid_nickname - checks if a string is a valid tor relay nickname 15 is_valid_circuit_id - checks if a string is a valid tor circuit id 16 is_valid_stream_id - checks if a string is a valid tor stream id 17 is_valid_connection_id - checks if a string is a valid tor connection id 18 is_valid_hidden_service_address - checks if a string is a valid hidden service address 19 is_hex_digits - checks if a string is only made up of hex digits 20""" 21 22import re 23 24import stem.util.str_tools 25 26# The control-spec defines the following as... 27# 28# Fingerprint = "$" 40*HEXDIG 29# NicknameChar = "a"-"z" / "A"-"Z" / "0" - "9" 30# Nickname = 1*19 NicknameChar 31# 32# CircuitID = 1*16 IDChar 33# IDChar = ALPHA / DIGIT 34# 35# HEXDIG is defined in RFC 5234 as being uppercase and used in RFC 5987 as 36# case insensitive. Tor doesn't define this in the spec so flipping a coin 37# and going with case insensitive. 38 39NICKNAME_PATTERN = re.compile('^[a-zA-Z0-9]{1,19}$') 40CIRC_ID_PATTERN = re.compile('^[a-zA-Z0-9]{1,16}$') 41 42# Hidden service addresses are sixteen or fifty six base32 characters. 43 44HS_V2_ADDRESS_PATTERN = re.compile('^[a-z2-7]{16}$') 45HS_V3_ADDRESS_PATTERN = re.compile('^[a-z2-7]{56}$') 46 47 48def is_valid_fingerprint(entry, check_prefix = False): 49 """ 50 Checks if a string is a properly formatted relay fingerprint. This checks for 51 a '$' prefix if check_prefix is true, otherwise this only validates the hex 52 digits. 53 54 :param str entry: string to be checked 55 :param bool check_prefix: checks for a '$' prefix 56 57 :returns: **True** if the string could be a relay fingerprint, **False** otherwise 58 """ 59 60 if isinstance(entry, bytes): 61 entry = stem.util.str_tools._to_unicode(entry) 62 63 try: 64 if check_prefix: 65 if not entry or entry[0] != '$': 66 return False 67 68 entry = entry[1:] 69 70 return is_hex_digits(entry, 40) 71 except TypeError: 72 return False 73 74 75def is_valid_nickname(entry): 76 """ 77 Checks if a string is a valid format for being a nickname. 78 79 :param str entry: string to be checked 80 81 :returns: **True** if the string could be a nickname, **False** otherwise 82 """ 83 84 if isinstance(entry, bytes): 85 entry = stem.util.str_tools._to_unicode(entry) 86 87 try: 88 return bool(NICKNAME_PATTERN.match(entry)) 89 except TypeError: 90 return False 91 92 93def is_valid_circuit_id(entry): 94 """ 95 Checks if a string is a valid format for being a circuit identifier. 96 97 :returns: **True** if the string could be a circuit id, **False** otherwise 98 """ 99 100 if isinstance(entry, bytes): 101 entry = stem.util.str_tools._to_unicode(entry) 102 103 try: 104 return bool(CIRC_ID_PATTERN.match(entry)) 105 except TypeError: 106 return False 107 108 109def is_valid_stream_id(entry): 110 """ 111 Checks if a string is a valid format for being a stream identifier. 112 Currently, this is just an alias to :func:`~stem.util.tor_tools.is_valid_circuit_id`. 113 114 :returns: **True** if the string could be a stream id, **False** otherwise 115 """ 116 117 return is_valid_circuit_id(entry) 118 119 120def is_valid_connection_id(entry): 121 """ 122 Checks if a string is a valid format for being a connection identifier. 123 Currently, this is just an alias to :func:`~stem.util.tor_tools.is_valid_circuit_id`. 124 125 :returns: **True** if the string could be a connection id, **False** otherwise 126 """ 127 128 return is_valid_circuit_id(entry) 129 130 131def is_valid_hidden_service_address(entry, version = None): 132 """ 133 Checks if a string is a valid format for being a hidden service address (not 134 including the '.onion' suffix). 135 136 .. versionchanged:: 1.8.0 137 Added the **version** argument, and responds with **True** if a version 3 138 hidden service address rather than just version 2 addresses. 139 140 :param int,list version: versions to check for, if unspecified either v2 or v3 141 hidden service address will provide **True** 142 143 :returns: **True** if the string could be a hidden service address, **False** 144 otherwise 145 """ 146 147 if isinstance(entry, bytes): 148 entry = stem.util.str_tools._to_unicode(entry) 149 150 if version is None: 151 version = (2, 3) 152 elif isinstance(version, int): 153 version = [version] 154 elif not isinstance(version, (list, tuple)): 155 raise ValueError('Hidden service version must be an integer or list, not a %s' % type(version).__name__) 156 157 try: 158 if 2 in version and bool(HS_V2_ADDRESS_PATTERN.match(entry)): 159 return True 160 161 if 3 in version and bool(HS_V3_ADDRESS_PATTERN.match(entry)): 162 return True 163 164 return False 165 except TypeError: 166 return False 167 168 169def is_hex_digits(entry, count): 170 """ 171 Checks if a string is the given number of hex digits. Digits represented by 172 letters are case insensitive. 173 174 :param str entry: string to be checked 175 :param int count: number of hex digits to be checked for 176 177 :returns: **True** if the given number of hex digits, **False** otherwise 178 """ 179 180 try: 181 if len(entry) != count: 182 return False 183 184 int(entry, 16) # attempt to convert it as hex 185 return True 186 except (ValueError, TypeError): 187 return False 188