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