@@ -59,36 +59,68 @@ def __init__(self, length: int = 16, style: str = "similar", seed=None):
5959 )
6060 self .random_letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
6161
62+ # Define homograph confusables to avoid
63+ self .homograph_confusables = {
64+ "a" : ["а" , "а" ], # Latin 'a' vs Cyrillic 'а'
65+ "b" : ["Ь" , "ь" ], # Latin 'b' vs Cyrillic 'ь'
66+ "c" : ["с" , "с" ], # Latin 'c' vs Cyrillic 'с'
67+ "e" : ["е" , "е" ], # Latin 'e' vs Cyrillic 'е'
68+ "h" : ["н" , "н" ], # Latin 'h' vs Cyrillic 'н'
69+ "i" : ["і" , "і" ], # Latin 'i' vs Ukrainian 'і'
70+ "k" : ["к" , "к" ], # Latin 'k' vs Cyrillic 'к'
71+ "m" : ["м" , "м" ], # Latin 'm' vs Cyrillic 'м'
72+ "o" : ["о" , "о" ], # Latin 'o' vs Cyrillic 'о'
73+ "p" : ["р" , "р" ], # Latin 'p' vs Cyrillic 'р'
74+ "s" : ["ѕ" , "ѕ" ], # Latin 's' vs Cyrillic 'ѕ'
75+ "x" : ["х" , "х" ], # Latin 'x' vs Cyrillic 'х'
76+ "y" : ["у" , "у" ], # Latin 'y' vs Cyrillic 'у'
77+ "0" : ["о" , "о" ], # Digit '0' vs Cyrillic 'о'
78+ "1" : ["і" , "і" ], # Digit '1' vs Ukrainian 'і'
79+ }
80+
6281 def generate (self ) -> str :
6382 """
6483 Generate unique obfuscated identifier
6584 """
66- max_attempts = 100
85+ max_attempts = 1000 # Increased attempts to handle more complex cases
6786 for attempt in range (max_attempts ):
6887 token = self ._generate_single ()
6988
70- # Check for duplicates
71- if token not in self .generated_tokens :
89+ # Check for duplicates and homograph issues
90+ if (
91+ token not in self .generated_tokens
92+ and self ._validate_token_for_homographs (token )
93+ ):
7294 self .generated_tokens .add (token )
7395 self .counter += 1
7496 return token
7597
7698 self .collision_count += 1
7799
100+ # If we still can't generate unique tokens, try increasing length
101+ if self .length < 32 :
102+ old_length = self .length
103+ self .length = min (
104+ self .length + 4 , 32
105+ ) # Increase length to reduce collisions
106+ try :
107+ token = self ._generate_single ()
108+ if (
109+ token not in self .generated_tokens
110+ and self ._validate_token_for_homographs (token )
111+ ):
112+ self .generated_tokens .add (token )
113+ self .counter += 1
114+ return token
115+ finally :
116+ self .length = old_length # Restore original length after trying
117+
78118 raise RuntimeError (
79119 f"Unable to generate unique token, collision persists after "
80- f"{ max_attempts } attempts"
120+ f"{ max_attempts } attempts with length { self .length } . "
121+ f"Consider using a different style or increasing token length."
81122 )
82123
83- def _generate_single (self ) -> str :
84- """
85- Generate single token (no duplicate check)
86- """
87- if self .style == "similar" :
88- return self ._generate_similar ()
89- else :
90- return self ._generate_random ()
91-
92124 def _generate_similar (self ) -> str :
93125 """
94126 Generate similar character style identifier with random first character
@@ -102,6 +134,21 @@ def _generate_similar(self) -> str:
102134
103135 return first_char + "" .join (remaining_chars )
104136
137+ def _validate_token_for_homographs (self , token : str ) -> bool :
138+ """
139+ Check if token contains potentially confusing homograph characters
140+ """
141+ # Convert to lowercase for comparison
142+ lower_token = token .lower ()
143+
144+ for char in lower_token :
145+ if char in self .homograph_confusables :
146+ # Check if any confusable character exists in the token
147+ for confusable in self .homograph_confusables [char ]:
148+ if confusable in token :
149+ return False
150+ return True
151+
105152 def _generate_random (self ) -> str :
106153 """
107154 Generate pure random style token
@@ -114,12 +161,35 @@ def _generate_random(self) -> str:
114161
115162 return first_char + "" .join (remaining_chars )
116163
164+ def _generate_single (self ) -> str :
165+ """
166+ Generate single token (no duplicate check)
167+ """
168+ if self .style == "similar" :
169+ token = self ._generate_similar ()
170+ else :
171+ token = self ._generate_random ()
172+
173+ # Ensure the token doesn't contain homograph confusables
174+ while not self ._validate_token_for_homographs (token ):
175+ # If the token contains homograph confusables, regenerate
176+ if self .style == "similar" :
177+ token = self ._generate_similar ()
178+ else :
179+ token = self ._generate_random ()
180+
181+ return token
182+
117183 def set_length (self , length : int ):
118184 """
119185 Set token length
120186 """
187+ if not isinstance (length , int ):
188+ raise TypeError ("Length must be an integer" )
121189 if length < 8 or length > 32 :
122190 raise ValueError (f"Length must be between 8 and 32, current: { length } " )
191+ if length < 1 or length > 255 : # Additional check for extreme values
192+ raise ValueError (f"Length must be between 1 and 255, current: { length } " )
123193 self .length = length
124194
125195 def set_style (self , style : str ):
@@ -243,6 +313,11 @@ def __init__(self, key=None):
243313 # Generate random key between 1-255
244314 self .key = random .randint (1 , 255 )
245315 else :
316+ # Validate the key is an integer between 1 and 255
317+ if not isinstance (key , int ):
318+ raise TypeError ("Key must be an integer" )
319+ if key < 1 or key > 255 :
320+ raise ValueError ("Key must be between 1 and 255" )
246321 self .key = key
247322
248323 def encrypt (self , text ):
0 commit comments