codext 1.16.4__py3-none-any.whl → 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- codext/VERSION.txt +1 -1
- codext/__common__.py +181 -86
- codext/__init__.py +0 -227
- codext/__main__.py +236 -0
- codext/base/__init__.py +5 -0
- codext/base/_base.py +14 -4
- codext/base/_base2n.py +4 -0
- codext/base/base100.py +7 -1
- codext/base/base1024.py +177 -0
- codext/base/base122.py +8 -0
- codext/base/base38.py +62 -0
- codext/base/base45.py +7 -0
- codext/base/base65536.py +158 -0
- codext/base/base85.py +13 -1
- codext/base/base91.py +7 -0
- codext/base/baseN.py +15 -0
- codext/base/bech32.py +119 -0
- codext/base/leb128.py +73 -0
- codext/binary/__init__.py +3 -0
- codext/binary/_universal.py +57 -0
- codext/binary/baudot.py +12 -1
- codext/binary/bcd.py +7 -0
- codext/binary/chucknorris.py +59 -0
- codext/binary/elias.py +78 -0
- codext/binary/excess3.py +7 -0
- codext/binary/fibonacci.py +65 -0
- codext/binary/gray.py +6 -0
- codext/binary/manchester.py +8 -0
- codext/binary/rotate.py +11 -7
- codext/checksums/adler.py +5 -1
- codext/checksums/crc.py +11 -1
- codext/checksums/luhn.py +8 -1
- codext/common/a1z26.py +7 -0
- codext/common/cases.py +6 -1
- codext/common/dummy.py +6 -1
- codext/common/octal.py +3 -0
- codext/common/ordinal.py +3 -0
- codext/compressions/__init__.py +1 -0
- codext/compressions/gzipp.py +6 -0
- codext/compressions/lz77.py +7 -0
- codext/compressions/lz78.py +7 -0
- codext/compressions/pkzip.py +6 -0
- codext/compressions/rle.py +116 -0
- codext/crypto/__init__.py +8 -0
- codext/crypto/adfgx.py +124 -0
- codext/crypto/affine.py +5 -0
- codext/crypto/atbash.py +5 -0
- codext/crypto/bacon.py +5 -0
- codext/crypto/barbie.py +4 -2
- codext/crypto/bazeries.py +132 -0
- codext/crypto/bifid.py +87 -0
- codext/crypto/chaocipher.py +77 -0
- codext/crypto/citrix.py +4 -2
- codext/crypto/columnar.py +80 -0
- codext/crypto/hill.py +110 -0
- codext/crypto/nihilist.py +86 -0
- codext/crypto/playfair.py +30 -16
- codext/crypto/polybius.py +6 -0
- codext/crypto/railfence.py +6 -0
- codext/crypto/rot.py +8 -0
- codext/crypto/scytale.py +6 -0
- codext/crypto/shift.py +3 -0
- codext/crypto/skip.py +59 -0
- codext/crypto/vic.py +9 -0
- codext/crypto/vigenere.py +54 -3
- codext/crypto/xor.py +3 -0
- codext/hashing/blake.py +7 -1
- codext/hashing/crypt.py +7 -1
- codext/hashing/md.py +8 -1
- codext/hashing/mmh3.py +6 -1
- codext/hashing/sha.py +7 -1
- codext/hashing/shake.py +5 -1
- codext/languages/braille.py +6 -0
- codext/languages/galactic.py +6 -0
- codext/languages/ipsum.py +8 -1
- codext/languages/leetspeak.py +6 -0
- codext/languages/morse.py +7 -0
- codext/languages/navajo.py +7 -0
- codext/languages/radio.py +6 -0
- codext/languages/southpark.py +7 -0
- codext/languages/tap.py +7 -0
- codext/languages/tomtom.py +6 -0
- codext/others/__init__.py +3 -0
- codext/others/binhex.py +125 -0
- codext/others/dna.py +4 -2
- codext/others/kbshift.py +5 -0
- codext/others/letters.py +5 -0
- codext/others/markdown.py +6 -0
- codext/others/uuencode.py +8 -0
- codext/others/xxencode.py +83 -0
- codext/others/yenc.py +121 -0
- codext/stegano/__init__.py +2 -0
- codext/stegano/hexagram.py +7 -0
- codext/stegano/klopf.py +6 -0
- codext/stegano/resistor.py +6 -0
- codext/stegano/rick.py +4 -0
- codext/stegano/sms.py +6 -0
- codext/stegano/tags.py +62 -0
- codext/stegano/whitespace.py +8 -0
- codext/stegano/zerowidth.py +112 -0
- codext/web/html.py +8 -0
- codext/web/url.py +6 -0
- {codext-1.16.4.dist-info → codext-2.0.0.dist-info}/METADATA +45 -18
- codext-2.0.0.dist-info/RECORD +116 -0
- {codext-1.16.4.dist-info → codext-2.0.0.dist-info}/WHEEL +1 -1
- {codext-1.16.4.dist-info → codext-2.0.0.dist-info}/entry_points.txt +6 -1
- codext-1.16.4.dist-info/RECORD +0 -92
- {codext-1.16.4.dist-info → codext-2.0.0.dist-info}/licenses/LICENSE +0 -0
- {codext-1.16.4.dist-info → codext-2.0.0.dist-info}/top_level.txt +0 -0
codext/VERSION.txt
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
2.0.0
|
codext/__common__.py
CHANGED
|
@@ -11,12 +11,13 @@ import sys
|
|
|
11
11
|
from encodings.aliases import aliases as ALIASES
|
|
12
12
|
from functools import reduce, update_wrapper, wraps
|
|
13
13
|
from importlib import import_module
|
|
14
|
+
from importlib.util import find_spec
|
|
14
15
|
from inspect import currentframe
|
|
16
|
+
from collections import Counter
|
|
15
17
|
from io import BytesIO
|
|
16
18
|
from itertools import chain, product
|
|
17
19
|
from locale import getlocale
|
|
18
20
|
from math import log
|
|
19
|
-
from pkgutil import iter_modules
|
|
20
21
|
from platform import system
|
|
21
22
|
from random import randint
|
|
22
23
|
from string import *
|
|
@@ -41,6 +42,7 @@ CODECS_REGISTRY = None
|
|
|
41
42
|
CODECS_OVERWRITTEN = []
|
|
42
43
|
CODECS_CATEGORIES = ["native", "custom"]
|
|
43
44
|
CODECS_CACHE = {}
|
|
45
|
+
PRINTABLES = frozenset(printable)
|
|
44
46
|
LANG = getlocale()
|
|
45
47
|
if LANG:
|
|
46
48
|
LANG = (LANG[0] or "")[:2].lower()
|
|
@@ -57,6 +59,10 @@ MASKS = {
|
|
|
57
59
|
}
|
|
58
60
|
|
|
59
61
|
__codecs_registry = []
|
|
62
|
+
# caches depending on the state of the registry (cleared whenever it changes)
|
|
63
|
+
__guess_lists = {}
|
|
64
|
+
__names_table = []
|
|
65
|
+
__lookup_cache = {}
|
|
60
66
|
|
|
61
67
|
MACROS = {}
|
|
62
68
|
PERS_MACROS = {}
|
|
@@ -67,7 +73,12 @@ LINUX = system() == "Linux"
|
|
|
67
73
|
UNIX = DARWIN or LINUX
|
|
68
74
|
WINDOWS = system() == "Windows"
|
|
69
75
|
|
|
70
|
-
|
|
76
|
+
|
|
77
|
+
def entropy(s):
|
|
78
|
+
""" Shannon entropy of the given string or bytes, counting the characters in a single pass. """
|
|
79
|
+
n, l = Counter(s), len(s)
|
|
80
|
+
return -sum([p * log(p, 2) for p in [n[c] / l for c in set(s)]])
|
|
81
|
+
|
|
71
82
|
|
|
72
83
|
isb = lambda s: isinstance(s, bytes)
|
|
73
84
|
iss = lambda s: isinstance(s, str)
|
|
@@ -178,14 +189,10 @@ def __stdin_pipe():
|
|
|
178
189
|
|
|
179
190
|
def _input(infile):
|
|
180
191
|
# handle input file or stdin
|
|
181
|
-
c = b("")
|
|
182
192
|
if infile:
|
|
183
193
|
with open(infile, 'rb') as f:
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
for line in __stdin_pipe():
|
|
187
|
-
c += line
|
|
188
|
-
return c
|
|
194
|
+
return f.read()
|
|
195
|
+
return b("").join(__stdin_pipe())
|
|
189
196
|
|
|
190
197
|
|
|
191
198
|
def _set_exc(name, etype="ValueError"):
|
|
@@ -238,15 +245,23 @@ def add(ename, encode=None, decode=None, pattern=None, text=True, add_to_codecs=
|
|
|
238
245
|
for exc in kwargs.get('extra_exceptions', []):
|
|
239
246
|
_set_exc(exc) # create additional custom exceptions as builtins
|
|
240
247
|
glob = currentframe().f_back.f_globals
|
|
248
|
+
# the pattern is compiled once, as it is matched against every encoding name looked up
|
|
249
|
+
rx = re.compile(pattern) if pattern else None
|
|
250
|
+
# category of the codec, from the folder of the module adding it
|
|
251
|
+
cat = glob.get('__file__', os.path.join("custom", "_")).split(os.path.sep)[-2].rstrip("s")
|
|
252
|
+
category = kwargs.get('category', cat)
|
|
253
|
+
for c in [cat, category]:
|
|
254
|
+
if c not in CODECS_CATEGORIES:
|
|
255
|
+
CODECS_CATEGORIES.append(c)
|
|
241
256
|
# search function for the new encoding
|
|
242
257
|
@_with_repr(ename)
|
|
243
258
|
def getregentry(encoding):
|
|
244
|
-
if encoding != ename and not (
|
|
259
|
+
if encoding != ename and not (rx and rx.match(encoding)):
|
|
245
260
|
return
|
|
246
261
|
fenc, fdec, name = encode, decode, encoding
|
|
247
262
|
# prepare CodecInfo input arguments
|
|
248
263
|
if pattern:
|
|
249
|
-
m, args, i =
|
|
264
|
+
m, args, i = rx.match(encoding), [], 1
|
|
250
265
|
try:
|
|
251
266
|
while True:
|
|
252
267
|
try:
|
|
@@ -275,12 +290,12 @@ def add(ename, encode=None, decode=None, pattern=None, text=True, add_to_codecs=
|
|
|
275
290
|
if fenc:
|
|
276
291
|
fenc = fix_inout_formats(fenc)
|
|
277
292
|
if fdec:
|
|
293
|
+
sl, sc = getattr(fdec, '__strip__', (False, False))
|
|
278
294
|
fdec = fix_inout_formats(fdec)
|
|
279
|
-
sl, sc = kwargs.pop('strip_lines', False), kwargs.pop('strip_crlf', False)
|
|
280
295
|
if sl or sc:
|
|
281
296
|
def _striplines(f):
|
|
282
297
|
def __wrapper(input, *a, **kw):
|
|
283
|
-
return f(_stripl(input,
|
|
298
|
+
return f(_stripl(input, sl, sc), *a, **kw)
|
|
284
299
|
return __wrapper
|
|
285
300
|
# this fixes issues with wrapped encoded inputs
|
|
286
301
|
fdec = _striplines(fdec)
|
|
@@ -329,11 +344,7 @@ def add(ename, encode=None, decode=None, pattern=None, text=True, add_to_codecs=
|
|
|
329
344
|
ci.parameters['add_to_codecs'] = add_to_codecs
|
|
330
345
|
ci.parameters['pattern'] = pattern
|
|
331
346
|
ci.parameters['text'] = text
|
|
332
|
-
|
|
333
|
-
cat = f.split(os.path.sep)[-2].rstrip("s")
|
|
334
|
-
if cat not in CODECS_CATEGORIES:
|
|
335
|
-
CODECS_CATEGORIES.append(cat)
|
|
336
|
-
ci.parameters['category'] = kwargs.get('category', cat)
|
|
347
|
+
ci.parameters['category'] = category
|
|
337
348
|
ci.parameters['examples'] = kwargs.get('examples', glob.get('__examples__'))
|
|
338
349
|
ci.parameters['guess'] = kwargs.get('guess', glob.get('__guess__', [ename])) or []
|
|
339
350
|
ci.parameters['module'] = kwargs.get('module', glob.get('__name__'))
|
|
@@ -349,6 +360,7 @@ def add(ename, encode=None, decode=None, pattern=None, text=True, add_to_codecs=
|
|
|
349
360
|
if kwargs.get('aliases'):
|
|
350
361
|
getregentry.__aliases__ = list(map(lambda n: re.sub(r"[\s\-]", "_", n), kwargs['aliases']))
|
|
351
362
|
getregentry.__pattern__ = pattern
|
|
363
|
+
getregentry.__category__ = category
|
|
352
364
|
register(getregentry, add_to_codecs)
|
|
353
365
|
return getregentry
|
|
354
366
|
|
|
@@ -495,6 +507,7 @@ def add_map(ename, encmap, repl_char="?", sep="", ignore_case=None, no_error=Fal
|
|
|
495
507
|
smapdict = {getattr(k, case_e)() if i in ["both", "encode"] else k: \
|
|
496
508
|
([getattr(x, case_d)() for x in v] if isinstance(v, list) else getattr(v, case_d)()) \
|
|
497
509
|
if i in ["both", "decode"] else v for k, v in smapdict.items()}
|
|
510
|
+
strip = (False, False)
|
|
498
511
|
if decode:
|
|
499
512
|
tmp = {}
|
|
500
513
|
# this has a meaning for encoding maps that could have clashes in encoded chars (e.g. Bacon's cipher ;
|
|
@@ -506,7 +519,10 @@ def add_map(ename, encmap, repl_char="?", sep="", ignore_case=None, no_error=Fal
|
|
|
506
519
|
if x not in tmp.keys():
|
|
507
520
|
tmp[x] = k
|
|
508
521
|
smapdict, cs = tmp, reduce(lambda acc, x: acc + x, tmp.keys())
|
|
509
|
-
|
|
522
|
+
# line breaks that are not in the decoding map are stripped from the input (wrapped encoded text),
|
|
523
|
+
# except for codecs triggering no error, as they keep the unknown characters (thus, the lines)
|
|
524
|
+
if not no_error:
|
|
525
|
+
strip = ("\n" not in set(cs), "\r\n" not in cs)
|
|
510
526
|
# this allows to avoid an error with Python2 in the "for i, c in enumerate(parts)" loop
|
|
511
527
|
if '' not in smapdict.keys():
|
|
512
528
|
smapdict[''] = ""
|
|
@@ -517,14 +533,14 @@ def add_map(ename, encmap, repl_char="?", sep="", ignore_case=None, no_error=Fal
|
|
|
517
533
|
for x in smapdict.values():
|
|
518
534
|
getattr(l, ["append", "extend"][isinstance(x, list)])(x)
|
|
519
535
|
rminlen = max(1, min(map(len, set(l) - {''})))
|
|
536
|
+
# case handling, separator and kind of token do not depend on the text, hence they are determined once
|
|
537
|
+
icase = ignore_case in ["both", "decode" if decode else "encode"]
|
|
538
|
+
case = getattr(str, case_d if decode else case_e) if icase else None
|
|
539
|
+
lsep = "" if decode else sep if len(sep) <= 1 else sep[0]
|
|
540
|
+
kind = ["character", "token"][tmaxlen > 1]
|
|
520
541
|
|
|
521
542
|
# generic encoding/decoding function for map encodings
|
|
522
543
|
def code(text, errors="strict"):
|
|
523
|
-
icase = ignore_case == "both" or \
|
|
524
|
-
decode and ignore_case == "decode" or \
|
|
525
|
-
not decode and ignore_case == "encode"
|
|
526
|
-
if icase:
|
|
527
|
-
case = case_d if decode else case_e
|
|
528
544
|
if no_error:
|
|
529
545
|
errors = "leave"
|
|
530
546
|
text = ensure_str(text)
|
|
@@ -533,9 +549,7 @@ def add_map(ename, encmap, repl_char="?", sep="", ignore_case=None, no_error=Fal
|
|
|
533
549
|
text = "".join(f"{bin(ord(c))[2:]:0>8}" for c in text)
|
|
534
550
|
elif intype == "ord":
|
|
535
551
|
text = "".join(str(ord(c)).zfill(3) for c in text)
|
|
536
|
-
r =
|
|
537
|
-
lsep = "" if decode else sep if len(sep) <= 1 else sep[0]
|
|
538
|
-
kind = ["character", "token"][tmaxlen > 1]
|
|
552
|
+
r = []
|
|
539
553
|
error_func = handle_error(ename, errors, lsep, repl_char, rminlen, decode, kind)
|
|
540
554
|
|
|
541
555
|
# get the value from the mapping dictionary, trying the token with its inverted case if relevant
|
|
@@ -544,8 +558,7 @@ def add_map(ename, encmap, repl_char="?", sep="", ignore_case=None, no_error=Fal
|
|
|
544
558
|
result = smapdict[token]
|
|
545
559
|
except KeyError:
|
|
546
560
|
if icase and not case_changed:
|
|
547
|
-
|
|
548
|
-
return __get_value(token_inv_case, position, True)
|
|
561
|
+
return __get_value(case(token), position, True)
|
|
549
562
|
return error_func(token, position)
|
|
550
563
|
if isinstance(result, list):
|
|
551
564
|
result = result[0]
|
|
@@ -554,7 +567,7 @@ def add_map(ename, encmap, repl_char="?", sep="", ignore_case=None, no_error=Fal
|
|
|
554
567
|
# if a separator is defined, rely on it by splitting the input text
|
|
555
568
|
if decode and len(sep) > 0:
|
|
556
569
|
for i, c in enumerate(re.split("[" + sep + "]", text)):
|
|
557
|
-
r
|
|
570
|
+
r.append(__get_value(c, i))
|
|
558
571
|
# otherwise, move through the text using a cursor for tokenizing it ; this allows defining more complex
|
|
559
572
|
# encodings with variable token lengths
|
|
560
573
|
else:
|
|
@@ -563,8 +576,8 @@ def add_map(ename, encmap, repl_char="?", sep="", ignore_case=None, no_error=Fal
|
|
|
563
576
|
token = text[cursor:cursor+1]
|
|
564
577
|
for l in range(tminlen, tmaxlen + 1):
|
|
565
578
|
token = text[cursor:cursor+l]
|
|
566
|
-
if token in smapdict
|
|
567
|
-
r
|
|
579
|
+
if token in smapdict or icase and case(token) in smapdict:
|
|
580
|
+
r.append(__get_value(token, cursor))
|
|
568
581
|
cursor += l
|
|
569
582
|
break
|
|
570
583
|
else:
|
|
@@ -574,21 +587,24 @@ def add_map(ename, encmap, repl_char="?", sep="", ignore_case=None, no_error=Fal
|
|
|
574
587
|
# if the number of bad chars is the minimum token length, consume it and start a new buffer
|
|
575
588
|
if len(bad) == tminlen or errors == "leave":
|
|
576
589
|
posn = cursor - len(bad)
|
|
577
|
-
r
|
|
590
|
+
r.append(error_func(bad, posn))
|
|
578
591
|
bad = ""
|
|
592
|
+
r = "".join(r)
|
|
579
593
|
if decode:
|
|
580
594
|
if outype in ["bin", "ord"]:
|
|
581
|
-
tmp, r =
|
|
595
|
+
tmp, r = [], r.replace(lsep, "")
|
|
582
596
|
step = [3, 8][outype == "bin"]
|
|
583
597
|
for i in range(0, len(r), step):
|
|
584
598
|
s = r[i:i+step]
|
|
585
599
|
try:
|
|
586
|
-
tmp
|
|
600
|
+
tmp.append(chr(int(s, 2) if outype == "bin" else int(s)))
|
|
587
601
|
except ValueError:
|
|
588
602
|
if len(s) > 0:
|
|
589
|
-
tmp
|
|
590
|
-
r = tmp + lsep
|
|
603
|
+
tmp.append("[" + s + "]")
|
|
604
|
+
r = "".join(tmp) + lsep
|
|
591
605
|
return r[:len(r)-len(lsep)], len(b(text))
|
|
606
|
+
# the stripping flags are carried by the function, as it is built for each looked up encoding name
|
|
607
|
+
code.__strip__ = strip
|
|
592
608
|
return code
|
|
593
609
|
if re.search(r"\([^(?:)]", kwargs.get('pattern', "")) is None:
|
|
594
610
|
# in this case, there is no capturing group for parametrization
|
|
@@ -623,6 +639,7 @@ def clear():
|
|
|
623
639
|
""" Clear codext's local registry of search functions. """
|
|
624
640
|
global __codecs_registry, MACROS, PERS_MACROS # noqa: F824
|
|
625
641
|
__codecs_registry, MACROS, PERS_MACROS = [], {}, {}
|
|
642
|
+
__registry_changed()
|
|
626
643
|
codecs.clear = clear
|
|
627
644
|
|
|
628
645
|
|
|
@@ -664,14 +681,13 @@ def is_native(encoding):
|
|
|
664
681
|
|
|
665
682
|
def list_categories():
|
|
666
683
|
""" Get a list of all codec categories. """
|
|
667
|
-
c = CODECS_CATEGORIES
|
|
668
684
|
root = os.path.dirname(__file__)
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
return
|
|
685
|
+
# categories from the folders of the codecs, plus a particular category, hardcoded from base/_base.py
|
|
686
|
+
for c in [d.rstrip("s") for d in os.listdir(root) if os.path.isdir(os.path.join(root, d)) and
|
|
687
|
+
not d.startswith("__")] + ["base-generic"]:
|
|
688
|
+
if c not in CODECS_CATEGORIES:
|
|
689
|
+
CODECS_CATEGORIES.append(c)
|
|
690
|
+
return CODECS_CATEGORIES[:]
|
|
675
691
|
list_categories()
|
|
676
692
|
|
|
677
693
|
|
|
@@ -705,9 +721,11 @@ def list_encodings(*categories):
|
|
|
705
721
|
enc.append(ci.name)
|
|
706
722
|
for search_function in CODECS_OVERWRITTEN + __codecs_registry:
|
|
707
723
|
name = search_function.__name__.replace("_", "-")
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
724
|
+
c = getattr(search_function, "__category__", None)
|
|
725
|
+
if c is None:
|
|
726
|
+
p = getattr(search_function, "__pattern__", None)
|
|
727
|
+
ci = search_function(name) if p is None else search_function(_sample_name(search_function))
|
|
728
|
+
c = "other" if ci is None else ci.parameters['category']
|
|
711
729
|
if (len(categories) == 0 or c in categories) and c not in exclude:
|
|
712
730
|
enc.append(name)
|
|
713
731
|
for category in categories:
|
|
@@ -731,6 +749,7 @@ def remove(name):
|
|
|
731
749
|
tbr.append(search_function)
|
|
732
750
|
for search_function in tbr:
|
|
733
751
|
__codecs_registry.remove(search_function)
|
|
752
|
+
__registry_changed()
|
|
734
753
|
try:
|
|
735
754
|
del MACROS[name]
|
|
736
755
|
except KeyError:
|
|
@@ -769,6 +788,7 @@ def reset():
|
|
|
769
788
|
# restore codext's registry
|
|
770
789
|
else:
|
|
771
790
|
__codecs_registry = CODECS_REGISTRY[:]
|
|
791
|
+
__registry_changed()
|
|
772
792
|
# restore codext's embedded set of macros
|
|
773
793
|
with open(os.path.join(os.path.dirname(__file__), "macros.json")) as f:
|
|
774
794
|
MACROS = json.load(f)
|
|
@@ -897,12 +917,15 @@ def decode(obj, encoding='utf-8', errors='strict'):
|
|
|
897
917
|
codecs.decode = decode
|
|
898
918
|
|
|
899
919
|
|
|
920
|
+
_REPEAT = re.compile(r"\[(\d+)\]$")
|
|
921
|
+
|
|
922
|
+
|
|
900
923
|
def encode(obj, encoding='utf-8', errors='strict'):
|
|
901
924
|
""" Custom encode function relying on the hooked lookup function. """
|
|
902
|
-
n, m = 1,
|
|
925
|
+
n, m = 1, _REPEAT.search(encoding)
|
|
903
926
|
if m:
|
|
904
927
|
n = int(m.group(1))
|
|
905
|
-
encoding =
|
|
928
|
+
encoding = encoding[:m.start()]
|
|
906
929
|
ci = lookup(encoding)
|
|
907
930
|
for i in range(n):
|
|
908
931
|
try:
|
|
@@ -919,6 +942,26 @@ codecs.encode = encode
|
|
|
919
942
|
|
|
920
943
|
def lookup(encoding, macro=True):
|
|
921
944
|
""" Hooked lookup function for searching first for codecs in the local registry of this module. """
|
|
945
|
+
# the results are cached per encoding name, as searching through the registry is costly ; a macro is only returned
|
|
946
|
+
# when allowed, and failures are not cached (a codec or a macro may be added afterwards)
|
|
947
|
+
try:
|
|
948
|
+
ci = __lookup_cache[encoding]
|
|
949
|
+
if macro or not isinstance(ci, CodecMacro):
|
|
950
|
+
return ci
|
|
951
|
+
except (KeyError, TypeError):
|
|
952
|
+
pass
|
|
953
|
+
ci = __lookup(encoding, macro)
|
|
954
|
+
try:
|
|
955
|
+
__lookup_cache[encoding] = ci
|
|
956
|
+
except TypeError:
|
|
957
|
+
pass
|
|
958
|
+
return ci
|
|
959
|
+
codecs.lookup = lookup
|
|
960
|
+
|
|
961
|
+
|
|
962
|
+
def __lookup(encoding, macro=True):
|
|
963
|
+
""" Search for the given encoding, first in the local registry of this module, then with the original lookup
|
|
964
|
+
function and finally among the macros. """
|
|
922
965
|
# first, try to match the given encoding with codecs' search functions
|
|
923
966
|
for search_function in CODECS_OVERWRITTEN + __codecs_registry:
|
|
924
967
|
codecinfo = search_function(encoding)
|
|
@@ -928,7 +971,7 @@ def lookup(encoding, macro=True):
|
|
|
928
971
|
for search_function in CODECS_OVERWRITTEN + __codecs_registry:
|
|
929
972
|
if search_function.__name__.replace("_", "-") == encoding or \
|
|
930
973
|
encoding in getattr(search_function, "__aliases__", []):
|
|
931
|
-
codecinfo = search_function(
|
|
974
|
+
codecinfo = search_function(_sample_name(search_function))
|
|
932
975
|
if codecinfo is not None:
|
|
933
976
|
return codecinfo
|
|
934
977
|
# finally, get a CodecInfo with the original lookup function and refine it with a dictionary of parameters
|
|
@@ -945,7 +988,6 @@ def lookup(encoding, macro=True):
|
|
|
945
988
|
e = LookupError(f"unknown encoding: {encoding}")
|
|
946
989
|
e.__cause__ = e # stop exception chaining
|
|
947
990
|
raise e
|
|
948
|
-
codecs.lookup = lookup
|
|
949
991
|
|
|
950
992
|
|
|
951
993
|
def register(search_function, add_to_codecs=False):
|
|
@@ -964,6 +1006,7 @@ def register(search_function, add_to_codecs=False):
|
|
|
964
1006
|
except LookupError:
|
|
965
1007
|
l = __codecs_registry
|
|
966
1008
|
l.append(search_function)
|
|
1009
|
+
__registry_changed()
|
|
967
1010
|
if add_to_codecs:
|
|
968
1011
|
__orig_register(search_function)
|
|
969
1012
|
|
|
@@ -974,37 +1017,69 @@ def __register(search_function):
|
|
|
974
1017
|
codecs.register = __register
|
|
975
1018
|
|
|
976
1019
|
|
|
1020
|
+
def __names():
|
|
1021
|
+
""" Get the table of names used for searching encodings, as tuples (names to be matched, resulting name, search
|
|
1022
|
+
function), built once per state of the registry. """
|
|
1023
|
+
if len(__names_table) == 0:
|
|
1024
|
+
for search_function in CODECS_OVERWRITTEN + __codecs_registry:
|
|
1025
|
+
n = search_function.__name__
|
|
1026
|
+
__names_table.append(((n, n.replace("_", "-")), n.replace("_", "-"), search_function))
|
|
1027
|
+
for alias, name in ALIASES.items():
|
|
1028
|
+
__names_table.append(((alias, name), name, None))
|
|
1029
|
+
return __names_table
|
|
1030
|
+
|
|
1031
|
+
|
|
1032
|
+
def __registry_changed():
|
|
1033
|
+
""" Clear the caches depending on the state of the registry. """
|
|
1034
|
+
__guess_lists.clear()
|
|
1035
|
+
__lookup_cache.clear()
|
|
1036
|
+
__names_table.clear()
|
|
1037
|
+
|
|
1038
|
+
|
|
1039
|
+
def _sample_name(search_function):
|
|
1040
|
+
""" Get the encoding name generated from the pattern of the given search function (used when looking up a codec by
|
|
1041
|
+
its name) ; it is cached on the search function, as generating strings from a regex is costly. """
|
|
1042
|
+
try:
|
|
1043
|
+
return search_function.__sample__
|
|
1044
|
+
except AttributeError:
|
|
1045
|
+
search_function.__sample__ = s = generate_string_from_regex(getattr(search_function, "__pattern__", None))
|
|
1046
|
+
return s
|
|
1047
|
+
|
|
1048
|
+
|
|
1049
|
+
def _sample_rounds(search_function):
|
|
1050
|
+
""" Get 5 sets of encoding names generated from the pattern of the given search function, for the extended search ;
|
|
1051
|
+
they are cached on the search function, as generating strings from a regex is costly. """
|
|
1052
|
+
try:
|
|
1053
|
+
return search_function.__samples__
|
|
1054
|
+
except AttributeError:
|
|
1055
|
+
p = getattr(search_function, "__pattern__", None)
|
|
1056
|
+
search_function.__samples__ = r = [list(generate_strings_from_regex(p)) for _ in range(5)]
|
|
1057
|
+
return r
|
|
1058
|
+
|
|
1059
|
+
|
|
977
1060
|
def search(encoding_regex, extended=True):
|
|
978
1061
|
""" Function similar to lookup but allows to search for an encoding based on a regex instead. It searches this way
|
|
979
1062
|
into the local registry but also tries a simple lookup with the original lookup function. """
|
|
980
|
-
matches = []
|
|
981
|
-
for search_function in
|
|
982
|
-
n
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
matches.append(n.replace("_", "-"))
|
|
986
|
-
continue
|
|
987
|
-
if extended:
|
|
1063
|
+
matches, rx = [], re.compile(encoding_regex)
|
|
1064
|
+
for names, name, search_function in __names():
|
|
1065
|
+
if any(rx.search(n) for n in names):
|
|
1066
|
+
matches.append(name)
|
|
1067
|
+
if extended and search_function is not None:
|
|
988
1068
|
# in some cases, encoding_regex can match a generated string that uses a particular portion of its
|
|
989
1069
|
# generating pattern ; e.g. we expect encoding_regex="uu_" to find "uu" and "uu_codec" while it can also
|
|
990
1070
|
# find "morse" or "atbash" very rarely because of their dynamic patterns and the limited number of randomly
|
|
991
1071
|
# generated strings
|
|
992
1072
|
# so, we can use a qualified majority voting to ensure we do not get a "junk" encoding in the list of
|
|
993
|
-
# matches ;
|
|
1073
|
+
# matches ; using 5 sets of generated strings for a given codec but adding the codec to the list of
|
|
994
1074
|
# matches only if we get at least 3 matches ensures that we consider up to 2 failures that could be
|
|
995
1075
|
# stochastic, therefore drastically decreasing the probability to get a "junk" encoding in the matches list
|
|
996
1076
|
c = 0
|
|
997
|
-
for
|
|
998
|
-
for s in
|
|
999
|
-
|
|
1000
|
-
|
|
1077
|
+
for strings in _sample_rounds(search_function):
|
|
1078
|
+
if any(rx.search(s) for s in strings):
|
|
1079
|
+
c += 1
|
|
1080
|
+
if c >= 3:
|
|
1081
|
+
matches.append(search_function.__name__)
|
|
1001
1082
|
break
|
|
1002
|
-
if c >= 3:
|
|
1003
|
-
matches.append(n)
|
|
1004
|
-
break
|
|
1005
|
-
for s, n in ALIASES.items():
|
|
1006
|
-
if re.search(encoding_regex, s) or re.search(encoding_regex, n):
|
|
1007
|
-
matches.append(n)
|
|
1008
1083
|
return sorted(list(set(matches)), key=_human_keys)
|
|
1009
1084
|
codecs.search = search
|
|
1010
1085
|
|
|
@@ -1136,7 +1211,14 @@ def generate_strings_from_regex(regex, star_plus_max=STAR_PLUS_MAX, repeat_max=R
|
|
|
1136
1211
|
|
|
1137
1212
|
|
|
1138
1213
|
# guess feature objects
|
|
1139
|
-
__module_exists
|
|
1214
|
+
def __module_exists(name):
|
|
1215
|
+
""" Check if a top-level module is installed, without importing it. """
|
|
1216
|
+
try:
|
|
1217
|
+
return find_spec(name) is not None
|
|
1218
|
+
except (ImportError, ValueError):
|
|
1219
|
+
return False
|
|
1220
|
+
|
|
1221
|
+
|
|
1140
1222
|
stopfunc = ModuleType("stopfunc", """
|
|
1141
1223
|
Predefined stop functions
|
|
1142
1224
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
@@ -1149,7 +1231,7 @@ stopfunc = ModuleType("stopfunc", """
|
|
|
1149
1231
|
- `regex`: takes one argument, the regular expression, for checking a string against the given pattern
|
|
1150
1232
|
- `text`: checks for printables and an entropy less than 4.6 (empirically determined)
|
|
1151
1233
|
""")
|
|
1152
|
-
stopfunc.printables = lambda s:
|
|
1234
|
+
stopfunc.printables = lambda s: PRINTABLES.issuperset(ensure_str(s))
|
|
1153
1235
|
stopfunc.printables.__name__ = stopfunc.printables.__qualname__ = "printables"
|
|
1154
1236
|
stopfunc.regex = lambda p: lambda s: re.search(p, ensure_str(s)) is not None
|
|
1155
1237
|
stopfunc.regex.__name__ = stopfunc.regex.__qualname__ = "regex"
|
|
@@ -1270,6 +1352,20 @@ def __guess(prev_input, input, stop_func, depth, max_depth, min_depth, encodings
|
|
|
1270
1352
|
stop, show, scoring_heuristic, extended, debug)
|
|
1271
1353
|
|
|
1272
1354
|
|
|
1355
|
+
def __guess_list(encoding):
|
|
1356
|
+
""" Get the list of encoding names to be tried when guessing for the given encoding, cached per state of the
|
|
1357
|
+
registry. """
|
|
1358
|
+
try:
|
|
1359
|
+
return __guess_lists[encoding]
|
|
1360
|
+
except KeyError:
|
|
1361
|
+
try:
|
|
1362
|
+
l = lookup(encoding, False).parameters['guess']
|
|
1363
|
+
except Exception:
|
|
1364
|
+
l = []
|
|
1365
|
+
__guess_lists[encoding] = l
|
|
1366
|
+
return l
|
|
1367
|
+
|
|
1368
|
+
|
|
1273
1369
|
def __make_encodings_dict(include, exclude):
|
|
1274
1370
|
""" Process encodings inclusion and exclusion lists, listing categories and developping codecs' lists of possible
|
|
1275
1371
|
encoding names. It also creates a cache with the CodecInfo objects for improving performance. """
|
|
@@ -1281,11 +1377,7 @@ def __make_encodings_dict(include, exclude):
|
|
|
1281
1377
|
for enc in ((list_encodings(*cc) if (len(cc) > 0 or keep) and len(sc) == 0 else []) + sc):
|
|
1282
1378
|
g = []
|
|
1283
1379
|
for e in (search(enc, False) or [enc]):
|
|
1284
|
-
|
|
1285
|
-
ci = lookup(e, False)
|
|
1286
|
-
g.extend(ci.parameters['guess'])
|
|
1287
|
-
except:
|
|
1288
|
-
pass
|
|
1380
|
+
g.extend(__guess_list(e))
|
|
1289
1381
|
if enc in g: # e.g. "rot-1" => ["rot-1", "rot-2", ...] ; only "rot-1" is to be selected
|
|
1290
1382
|
l.append(enc)
|
|
1291
1383
|
else: # e.g. "rot" => ["rot-1", "rot-2", ...] ; all the "rot-N" shall be selected
|
|
@@ -1298,7 +1390,8 @@ def __make_encodings_dict(include, exclude):
|
|
|
1298
1390
|
|
|
1299
1391
|
def __rank(prev_input, input, prev_encoding, encodings, heuristic=False, extended=False, yield_score=False):
|
|
1300
1392
|
""" Filter valid encodings and rank them by relevance. """
|
|
1301
|
-
|
|
1393
|
+
# characteristics of the input text, computed once per padding character (see __score)
|
|
1394
|
+
ranking, texts = {}, {}
|
|
1302
1395
|
for e in encodings:
|
|
1303
1396
|
try:
|
|
1304
1397
|
codec = CODECS_CACHE[e]
|
|
@@ -1307,7 +1400,7 @@ def __rank(prev_input, input, prev_encoding, encodings, heuristic=False, extende
|
|
|
1307
1400
|
CODECS_CACHE[e] = codec = lookup(e, False)
|
|
1308
1401
|
except LookupError:
|
|
1309
1402
|
continue
|
|
1310
|
-
t = __score(prev_input, input, prev_encoding, e, codec, heuristic, extended)
|
|
1403
|
+
t = __score(prev_input, input, prev_encoding, e, codec, heuristic, extended, texts)
|
|
1311
1404
|
if t:
|
|
1312
1405
|
ranking[e] = t
|
|
1313
1406
|
for encoding, result in sorted(ranking.items(), key=lambda x: (-x[1][0], x[0])):
|
|
@@ -1326,13 +1419,12 @@ class _Text(object):
|
|
|
1326
1419
|
text = text.rstrip(b(pad_char) if isinstance(text, bytes) else pad_char)
|
|
1327
1420
|
self.len = len(self.text)
|
|
1328
1421
|
self.lcharset = len(set(self.text))
|
|
1329
|
-
self.printables = float(
|
|
1422
|
+
self.printables = float(sum(map(PRINTABLES.__contains__, self.text))) / self.len
|
|
1330
1423
|
self.entropy = entropy(self.text)
|
|
1331
1424
|
|
|
1332
1425
|
|
|
1333
|
-
def __score(prev_input, input, prev_encoding, encoding, codec, heuristic=False, extended=False):
|
|
1426
|
+
def __score(prev_input, input, prev_encoding, encoding, codec, heuristic=False, extended=False, texts=None):
|
|
1334
1427
|
""" Score relevant encodings given an input. """
|
|
1335
|
-
obj = None
|
|
1336
1428
|
sc = codec.parameters.get('scoring', {})
|
|
1337
1429
|
no_error, transitive = codec.parameters.get('no_error', False), sc.get('transitive', False)
|
|
1338
1430
|
# ignore encodings that fail to decode with their default errors handling value
|
|
@@ -1349,10 +1441,12 @@ def __score(prev_input, input, prev_encoding, encoding, codec, heuristic=False,
|
|
|
1349
1441
|
ci_prev = lookup(prev_encoding, False)
|
|
1350
1442
|
if ci_prev.parameters['name'] == codec.parameters['name']:
|
|
1351
1443
|
return
|
|
1352
|
-
# compute input's characteristics only once and only if the control flow reaches this point
|
|
1353
|
-
pad = sc.get('padding_char')
|
|
1354
|
-
|
|
1355
|
-
obj =
|
|
1444
|
+
# compute input's characteristics only once per padding character and only if the control flow reaches this point
|
|
1445
|
+
pad, texts = sc.get('padding_char'), {} if texts is None else texts
|
|
1446
|
+
try:
|
|
1447
|
+
obj = texts[pad]
|
|
1448
|
+
except KeyError:
|
|
1449
|
+
obj = texts[pad] = _Text(input, pad)
|
|
1356
1450
|
if heuristic:
|
|
1357
1451
|
# from here, the goal (e.g. if the input is Base32) is to rank candidate encodings (e.g. multiple base
|
|
1358
1452
|
# codecs) so that we can put the right one as early as possible and eventually exclude bad candidates
|
|
@@ -1488,7 +1582,7 @@ def guess(input, stop_func=stopfunc.default, min_depth=0, max_depth=5, include=N
|
|
|
1488
1582
|
break
|
|
1489
1583
|
except KeyboardInterrupt:
|
|
1490
1584
|
pass
|
|
1491
|
-
CODECS_CACHE
|
|
1585
|
+
CODECS_CACHE.clear()
|
|
1492
1586
|
return result
|
|
1493
1587
|
codecs.guess = guess
|
|
1494
1588
|
|
|
@@ -1505,6 +1599,7 @@ def rank(input, extended=False, limit=-1, include=None, exclude=None):
|
|
|
1505
1599
|
encodings = __make_encodings_dict(include if isinstance(include, dict) else {-1: include or CODECS_CATEGORIES},
|
|
1506
1600
|
exclude if isinstance(exclude, dict) else {-1: exclude or []})
|
|
1507
1601
|
r = list(__rank(None, input, "", encodings[-1], True, extended, True))
|
|
1602
|
+
CODECS_CACHE.clear()
|
|
1508
1603
|
return r[:limit] if len(r) > 1 else r
|
|
1509
1604
|
codecs.rank = rank
|
|
1510
1605
|
|