/usr/lib/python2.7/dist-packages/hashids.py is in python-hashids 1.2.0-1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 | """Implements the hashids algorithm in python. For more information, visit
http://www.hashids.org/. Compatible with Python 2.6, 2.7 and 3"""
import warnings
from functools import wraps
from math import ceil
__version__ = '1.2.0'
RATIO_SEPARATORS = 3.5
RATIO_GUARDS = 12
try:
StrType = basestring
except NameError:
StrType = str
def _is_str(candidate):
"""Returns whether a value is a string."""
return isinstance(candidate, StrType)
def _is_uint(number):
"""Returns whether a value is an unsigned integer."""
try:
return number == int(number) and number >= 0
except ValueError:
return False
def _split(string, splitters):
"""Splits a string into parts at multiple characters"""
part = ''
for character in string:
if character in splitters:
yield part
part = ''
else:
part += character
yield part
def _hash(number, alphabet):
"""Hashes `number` using the given `alphabet` sequence."""
hashed = ''
len_alphabet = len(alphabet)
while True:
hashed = alphabet[number % len_alphabet] + hashed
number //= len_alphabet
if not number:
return hashed
def _unhash(hashed, alphabet):
"""Restores a number tuple from hashed using the given `alphabet` index."""
number = 0
len_alphabet = len(alphabet)
for character in hashed:
position = alphabet.index(character)
number *= len_alphabet
number += position
return number
def _reorder(string, salt):
"""Reorders `string` according to `salt`."""
len_salt = len(salt)
if len_salt != 0:
string = list(string)
index, integer_sum = 0, 0
for i in range(len(string) - 1, 0, -1):
integer = ord(salt[index])
integer_sum += integer
j = (integer + index + integer_sum) % i
string[i], string[j] = string[j], string[i]
index = (index + 1) % len_salt
string = ''.join(string)
return string
def _index_from_ratio(dividend, divisor):
"""Returns the ceiled ratio of two numbers as int."""
return int(ceil(float(dividend) / divisor))
def _ensure_length(encoded, min_length, alphabet, guards, values_hash):
"""Ensures the minimal hash length"""
len_guards = len(guards)
guard_index = (values_hash + ord(encoded[0])) % len_guards
encoded = guards[guard_index] + encoded
if len(encoded) < min_length:
guard_index = (values_hash + ord(encoded[2])) % len_guards
encoded += guards[guard_index]
split_at = len(alphabet) // 2
while len(encoded) < min_length:
alphabet = _reorder(alphabet, alphabet)
encoded = alphabet[split_at:] + encoded + alphabet[:split_at]
excess = len(encoded) - min_length
if excess > 0:
from_index = excess // 2
encoded = encoded[from_index:from_index+min_length]
return encoded
def _encode(values, salt, min_length, alphabet, separators, guards):
"""Helper function that does the hash building without argument checks."""
len_alphabet = len(alphabet)
len_separators = len(separators)
values_hash = sum(x % (i + 100) for i, x in enumerate(values))
encoded = lottery = alphabet[values_hash % len(alphabet)]
for i, value in enumerate(values):
alphabet_salt = (lottery + salt + alphabet)[:len_alphabet]
alphabet = _reorder(alphabet, alphabet_salt)
last = _hash(value, alphabet)
encoded += last
value %= ord(last[0]) + i
encoded += separators[value % len_separators]
encoded = encoded[:-1] # cut off last separator
return (encoded if len(encoded) >= min_length else
_ensure_length(encoded, min_length, alphabet, guards, values_hash))
def _decode(hashid, salt, alphabet, separators, guards):
"""Helper method that restores the values encoded in a hashid without
argument checks."""
parts = tuple(_split(hashid, guards))
hashid = parts[1] if 2 <= len(parts) <= 3 else parts[0]
if not hashid:
return
lottery_char = hashid[0]
hashid = hashid[1:]
hash_parts = _split(hashid, separators)
for part in hash_parts:
alphabet_salt = (lottery_char + salt + alphabet)[:len(alphabet)]
alphabet = _reorder(alphabet, alphabet_salt)
yield _unhash(part, alphabet)
def _deprecated(func):
"""A decorator that warns about deprecation when the passed-in function is
invoked."""
@wraps(func)
def with_warning(*args, **kwargs):
warnings.warn(
('The %s method is deprecated and will be removed in v2.*.*' %
func.__name__),
DeprecationWarning
)
return func(*args, **kwargs)
return with_warning
class Hashids(object):
"""Hashes and restores values using the "hashids" algorithm."""
ALPHABET = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'
def __init__(self, salt='', min_length=0, alphabet=ALPHABET):
"""
Initializes a Hashids object with salt, minimum length, and alphabet.
:param salt: A string influencing the generated hash ids.
:param min_length: The minimum length for generated hashes
:param alphabet: The characters to use for the generated hash ids.
"""
self._min_length = max(int(min_length), 0)
self._salt = salt
separators = ''.join(x for x in 'cfhistuCFHISTU' if x in alphabet)
alphabet = ''.join(x for i, x in enumerate(alphabet)
if alphabet.index(x) == i and x not in separators)
len_alphabet, len_separators = len(alphabet), len(separators)
if len_alphabet + len_separators < 16:
raise ValueError('Alphabet must contain at least 16 '
'unique characters.')
separators = _reorder(separators, salt)
min_separators = _index_from_ratio(len_alphabet, RATIO_SEPARATORS)
number_of_missing_separators = min_separators - len_separators
if number_of_missing_separators > 0:
separators += alphabet[:number_of_missing_separators]
alphabet = alphabet[number_of_missing_separators:]
len_alphabet = len(alphabet)
alphabet = _reorder(alphabet, salt)
num_guards = _index_from_ratio(len_alphabet, RATIO_GUARDS)
if len_alphabet < 3:
guards = separators[:num_guards]
separators = separators[num_guards:]
else:
guards = alphabet[:num_guards]
alphabet = alphabet[num_guards:]
self._alphabet = alphabet
self._guards = guards
self._separators = separators
# Support old API
self.decrypt = _deprecated(self.decode)
self.encrypt = _deprecated(self.encode)
def encode(self, *values):
"""Builds a hash from the passed `values`.
:param values The values to transform into a hashid
>>> hashids = Hashids('arbitrary salt', 16, 'abcdefghijkl0123456')
>>> hashids.encode(1, 23, 456)
'1d6216i30h53elk3'
"""
if not (values and all(_is_uint(x) for x in values)):
return ''
return _encode(values, self._salt, self._min_length, self._alphabet,
self._separators, self._guards)
def decode(self, hashid):
"""Restore a tuple of numbers from the passed `hashid`.
:param hashid The hashid to decode
>>> hashids = Hashids('arbitrary salt', 16, 'abcdefghijkl0123456')
>>> hashids.decode('1d6216i30h53elk3')
(1, 23, 456)
"""
if not hashid or not _is_str(hashid):
return ()
try:
numbers = tuple(_decode(hashid, self._salt, self._alphabet,
self._separators, self._guards))
return numbers if hashid == self.encode(*numbers) else ()
except ValueError:
return ()
def encode_hex(self, hex_str):
"""Converts a hexadecimal string (e.g. a MongoDB id) to a hashid.
:param hex_str The hexadecimal string to encodes
>>> Hashids.encode_hex('507f1f77bcf86cd799439011')
'y42LW46J9luq3Xq9XMly'
"""
numbers = (int('1' + hex_str[i:i+12], 16)
for i in range(0, len(hex_str), 12))
try:
return self.encode(*numbers)
except ValueError:
return ''
def decode_hex(self, hashid):
"""Restores a hexadecimal string (e.g. a MongoDB id) from a hashid.
:param hashid The hashid to decode
>>> Hashids.decode_hex('y42LW46J9luq3Xq9XMly')
'507f1f77bcf86cd799439011'
"""
return ''.join(('%x' % x)[1:] for x in self.decode(hashid))
|