187 lines
5.1 KiB
Python
187 lines
5.1 KiB
Python
# Copyright 2012-2019, Damian Johnson and The Tor Project
|
|
# See LICENSE for licensing information
|
|
|
|
"""
|
|
Miscellaneous utility functions for working with tor.
|
|
|
|
.. versionadded:: 1.2.0
|
|
|
|
**Module Overview:**
|
|
|
|
::
|
|
|
|
is_valid_fingerprint - checks if a string is a valid tor relay fingerprint
|
|
is_valid_nickname - checks if a string is a valid tor relay nickname
|
|
is_valid_circuit_id - checks if a string is a valid tor circuit id
|
|
is_valid_stream_id - checks if a string is a valid tor stream id
|
|
is_valid_connection_id - checks if a string is a valid tor connection id
|
|
is_valid_hidden_service_address - checks if a string is a valid hidden service address
|
|
is_hex_digits - checks if a string is only made up of hex digits
|
|
"""
|
|
|
|
import re
|
|
|
|
import stem.util.str_tools
|
|
|
|
# The control-spec defines the following as...
|
|
#
|
|
# Fingerprint = "$" 40*HEXDIG
|
|
# NicknameChar = "a"-"z" / "A"-"Z" / "0" - "9"
|
|
# Nickname = 1*19 NicknameChar
|
|
#
|
|
# CircuitID = 1*16 IDChar
|
|
# IDChar = ALPHA / DIGIT
|
|
#
|
|
# HEXDIG is defined in RFC 5234 as being uppercase and used in RFC 5987 as
|
|
# case insensitive. Tor doesn't define this in the spec so flipping a coin
|
|
# and going with case insensitive.
|
|
|
|
NICKNAME_PATTERN = re.compile('^[a-zA-Z0-9]{1,19}$')
|
|
CIRC_ID_PATTERN = re.compile('^[a-zA-Z0-9]{1,16}$')
|
|
|
|
# Hidden service addresses are sixteen or fifty six base32 characters.
|
|
|
|
HS_V2_ADDRESS_PATTERN = re.compile('^[a-z2-7]{16}$')
|
|
HS_V3_ADDRESS_PATTERN = re.compile('^[a-z2-7]{56}$')
|
|
|
|
|
|
def is_valid_fingerprint(entry, check_prefix = False):
|
|
"""
|
|
Checks if a string is a properly formatted relay fingerprint. This checks for
|
|
a '$' prefix if check_prefix is true, otherwise this only validates the hex
|
|
digits.
|
|
|
|
:param str entry: string to be checked
|
|
:param bool check_prefix: checks for a '$' prefix
|
|
|
|
:returns: **True** if the string could be a relay fingerprint, **False** otherwise
|
|
"""
|
|
|
|
if isinstance(entry, bytes):
|
|
entry = stem.util.str_tools._to_unicode(entry)
|
|
|
|
try:
|
|
if check_prefix:
|
|
if not entry or entry[0] != '$':
|
|
return False
|
|
|
|
entry = entry[1:]
|
|
|
|
return is_hex_digits(entry, 40)
|
|
except TypeError:
|
|
return False
|
|
|
|
|
|
def is_valid_nickname(entry):
|
|
"""
|
|
Checks if a string is a valid format for being a nickname.
|
|
|
|
:param str entry: string to be checked
|
|
|
|
:returns: **True** if the string could be a nickname, **False** otherwise
|
|
"""
|
|
|
|
if isinstance(entry, bytes):
|
|
entry = stem.util.str_tools._to_unicode(entry)
|
|
|
|
try:
|
|
return bool(NICKNAME_PATTERN.match(entry))
|
|
except TypeError:
|
|
return False
|
|
|
|
|
|
def is_valid_circuit_id(entry):
|
|
"""
|
|
Checks if a string is a valid format for being a circuit identifier.
|
|
|
|
:returns: **True** if the string could be a circuit id, **False** otherwise
|
|
"""
|
|
|
|
if isinstance(entry, bytes):
|
|
entry = stem.util.str_tools._to_unicode(entry)
|
|
|
|
try:
|
|
return bool(CIRC_ID_PATTERN.match(entry))
|
|
except TypeError:
|
|
return False
|
|
|
|
|
|
def is_valid_stream_id(entry):
|
|
"""
|
|
Checks if a string is a valid format for being a stream identifier.
|
|
Currently, this is just an alias to :func:`~stem.util.tor_tools.is_valid_circuit_id`.
|
|
|
|
:returns: **True** if the string could be a stream id, **False** otherwise
|
|
"""
|
|
|
|
return is_valid_circuit_id(entry)
|
|
|
|
|
|
def is_valid_connection_id(entry):
|
|
"""
|
|
Checks if a string is a valid format for being a connection identifier.
|
|
Currently, this is just an alias to :func:`~stem.util.tor_tools.is_valid_circuit_id`.
|
|
|
|
:returns: **True** if the string could be a connection id, **False** otherwise
|
|
"""
|
|
|
|
return is_valid_circuit_id(entry)
|
|
|
|
|
|
def is_valid_hidden_service_address(entry, version = None):
|
|
"""
|
|
Checks if a string is a valid format for being a hidden service address (not
|
|
including the '.onion' suffix).
|
|
|
|
.. versionchanged:: 1.8.0
|
|
Added the **version** argument, and responds with **True** if a version 3
|
|
hidden service address rather than just version 2 addresses.
|
|
|
|
:param int,list version: versions to check for, if unspecified either v2 or v3
|
|
hidden service address will provide **True**
|
|
|
|
:returns: **True** if the string could be a hidden service address, **False**
|
|
otherwise
|
|
"""
|
|
|
|
if isinstance(entry, bytes):
|
|
entry = stem.util.str_tools._to_unicode(entry)
|
|
|
|
if version is None:
|
|
version = (2, 3)
|
|
elif isinstance(version, int):
|
|
version = [version]
|
|
elif not isinstance(version, (list, tuple)):
|
|
raise ValueError('Hidden service version must be an integer or list, not a %s' % type(version).__name__)
|
|
|
|
try:
|
|
if 2 in version and bool(HS_V2_ADDRESS_PATTERN.match(entry)):
|
|
return True
|
|
|
|
if 3 in version and bool(HS_V3_ADDRESS_PATTERN.match(entry)):
|
|
return True
|
|
|
|
return False
|
|
except TypeError:
|
|
return False
|
|
|
|
|
|
def is_hex_digits(entry, count):
|
|
"""
|
|
Checks if a string is the given number of hex digits. Digits represented by
|
|
letters are case insensitive.
|
|
|
|
:param str entry: string to be checked
|
|
:param int count: number of hex digits to be checked for
|
|
|
|
:returns: **True** if the given number of hex digits, **False** otherwise
|
|
"""
|
|
|
|
try:
|
|
if len(entry) != count:
|
|
return False
|
|
|
|
int(entry, 16) # attempt to convert it as hex
|
|
return True
|
|
except (ValueError, TypeError):
|
|
return False
|