codext 1.16.3__tar.gz → 1.16.5__tar.gz

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.
Files changed (140) hide show
  1. {codext-1.16.3 → codext-1.16.5}/PKG-INFO +2 -1
  2. {codext-1.16.3 → codext-1.16.5}/README.md +1 -0
  3. codext-1.16.5/docs/hakin9-codext.pdf +0 -0
  4. {codext-1.16.3 → codext-1.16.5}/docs/pages/enc/crypto.md +19 -1
  5. codext-1.16.5/src/codext/VERSION.txt +1 -0
  6. {codext-1.16.3 → codext-1.16.5}/src/codext/base/_base.py +6 -4
  7. {codext-1.16.3 → codext-1.16.5}/src/codext/checksums/crc.py +2 -0
  8. {codext-1.16.3 → codext-1.16.5}/src/codext/crypto/__init__.py +1 -1
  9. {codext-1.16.3 → codext-1.16.5}/src/codext/crypto/phillips.py +28 -50
  10. codext-1.16.5/src/codext/crypto/playfair.py +143 -0
  11. {codext-1.16.3 → codext-1.16.5}/src/codext/crypto/polybius.py +6 -7
  12. {codext-1.16.3 → codext-1.16.5}/src/codext/crypto/vic.py +1 -1
  13. {codext-1.16.3 → codext-1.16.5}/src/codext/hashing/crypt.py +2 -2
  14. {codext-1.16.3 → codext-1.16.5}/src/codext/languages/ipsum.py +8 -8
  15. {codext-1.16.3 → codext-1.16.5}/src/codext/others/uuencode.py +7 -7
  16. {codext-1.16.3 → codext-1.16.5}/src/codext/stegano/hexagram.py +4 -3
  17. {codext-1.16.3 → codext-1.16.5}/src/codext/stegano/whitespace.py +22 -21
  18. {codext-1.16.3 → codext-1.16.5}/src/codext.egg-info/PKG-INFO +2 -1
  19. {codext-1.16.3 → codext-1.16.5}/src/codext.egg-info/SOURCES.txt +2 -0
  20. {codext-1.16.3 → codext-1.16.5}/tests/test_base.py +24 -1
  21. {codext-1.16.3 → codext-1.16.5}/tests/test_manual.py +20 -2
  22. codext-1.16.3/src/codext/VERSION.txt +0 -1
  23. {codext-1.16.3 → codext-1.16.5}/.coveragerc +0 -0
  24. {codext-1.16.3 → codext-1.16.5}/.github/ISSUE_TEMPLATE/add-encoding.yml +0 -0
  25. {codext-1.16.3 → codext-1.16.5}/.github/copilot-instructions.md +0 -0
  26. {codext-1.16.3 → codext-1.16.5}/.github/prompts/add_codec.prompt.md +0 -0
  27. {codext-1.16.3 → codext-1.16.5}/.github/pull_request_template.md +0 -0
  28. {codext-1.16.3 → codext-1.16.5}/.github/workflows/python-package.yml +0 -0
  29. {codext-1.16.3 → codext-1.16.5}/.gitignore +0 -0
  30. {codext-1.16.3 → codext-1.16.5}/.readthedocs.yml +0 -0
  31. {codext-1.16.3 → codext-1.16.5}/LICENSE +0 -0
  32. {codext-1.16.3 → codext-1.16.5}/docs/coverage.svg +0 -0
  33. {codext-1.16.3 → codext-1.16.5}/docs/mkdocs.yml +0 -0
  34. {codext-1.16.3 → codext-1.16.5}/docs/pages/cli.md +0 -0
  35. {codext-1.16.3 → codext-1.16.5}/docs/pages/css/extra.css +0 -0
  36. {codext-1.16.3 → codext-1.16.5}/docs/pages/demos/using-bases.gif +0 -0
  37. {codext-1.16.3 → codext-1.16.5}/docs/pages/demos/using-codext.gif +0 -0
  38. {codext-1.16.3 → codext-1.16.5}/docs/pages/demos/using-debase.gif +0 -0
  39. {codext-1.16.3 → codext-1.16.5}/docs/pages/enc/base.md +0 -0
  40. {codext-1.16.3 → codext-1.16.5}/docs/pages/enc/binary.md +0 -0
  41. {codext-1.16.3 → codext-1.16.5}/docs/pages/enc/common.md +0 -0
  42. {codext-1.16.3 → codext-1.16.5}/docs/pages/enc/compressions.md +0 -0
  43. {codext-1.16.3 → codext-1.16.5}/docs/pages/enc/hashing.md +0 -0
  44. {codext-1.16.3 → codext-1.16.5}/docs/pages/enc/languages.md +0 -0
  45. {codext-1.16.3 → codext-1.16.5}/docs/pages/enc/others.md +0 -0
  46. {codext-1.16.3 → codext-1.16.5}/docs/pages/enc/stegano.md +0 -0
  47. {codext-1.16.3 → codext-1.16.5}/docs/pages/enc/web.md +0 -0
  48. {codext-1.16.3 → codext-1.16.5}/docs/pages/features.md +0 -0
  49. {codext-1.16.3 → codext-1.16.5}/docs/pages/guessing.md +0 -0
  50. {codext-1.16.3 → codext-1.16.5}/docs/pages/howto.md +0 -0
  51. {codext-1.16.3 → codext-1.16.5}/docs/pages/img/banner.png +0 -0
  52. {codext-1.16.3 → codext-1.16.5}/docs/pages/img/icon.png +0 -0
  53. {codext-1.16.3 → codext-1.16.5}/docs/pages/img/logo.png +0 -0
  54. {codext-1.16.3 → codext-1.16.5}/docs/pages/index.md +0 -0
  55. {codext-1.16.3 → codext-1.16.5}/docs/pages/manipulations.md +0 -0
  56. {codext-1.16.3 → codext-1.16.5}/docs/requirements.txt +0 -0
  57. {codext-1.16.3 → codext-1.16.5}/pyproject.toml +0 -0
  58. {codext-1.16.3 → codext-1.16.5}/pytest.ini +0 -0
  59. {codext-1.16.3 → codext-1.16.5}/requirements.txt +0 -0
  60. {codext-1.16.3 → codext-1.16.5}/setup.cfg +0 -0
  61. {codext-1.16.3 → codext-1.16.5}/src/codext/__common__.py +0 -0
  62. {codext-1.16.3 → codext-1.16.5}/src/codext/__info__.py +0 -0
  63. {codext-1.16.3 → codext-1.16.5}/src/codext/__init__.py +0 -0
  64. {codext-1.16.3 → codext-1.16.5}/src/codext/base/__init__.py +0 -0
  65. {codext-1.16.3 → codext-1.16.5}/src/codext/base/_base2n.py +0 -0
  66. {codext-1.16.3 → codext-1.16.5}/src/codext/base/base100.py +0 -0
  67. {codext-1.16.3 → codext-1.16.5}/src/codext/base/base122.py +0 -0
  68. {codext-1.16.3 → codext-1.16.5}/src/codext/base/base45.py +0 -0
  69. {codext-1.16.3 → codext-1.16.5}/src/codext/base/base85.py +0 -0
  70. {codext-1.16.3 → codext-1.16.5}/src/codext/base/base91.py +0 -0
  71. {codext-1.16.3 → codext-1.16.5}/src/codext/base/baseN.py +0 -0
  72. {codext-1.16.3 → codext-1.16.5}/src/codext/binary/__init__.py +0 -0
  73. {codext-1.16.3 → codext-1.16.5}/src/codext/binary/baudot.py +0 -0
  74. {codext-1.16.3 → codext-1.16.5}/src/codext/binary/bcd.py +0 -0
  75. {codext-1.16.3 → codext-1.16.5}/src/codext/binary/excess3.py +0 -0
  76. {codext-1.16.3 → codext-1.16.5}/src/codext/binary/gray.py +0 -0
  77. {codext-1.16.3 → codext-1.16.5}/src/codext/binary/manchester.py +0 -0
  78. {codext-1.16.3 → codext-1.16.5}/src/codext/binary/rotate.py +0 -0
  79. {codext-1.16.3 → codext-1.16.5}/src/codext/checksums/__init__.py +0 -0
  80. {codext-1.16.3 → codext-1.16.5}/src/codext/checksums/adler.py +0 -0
  81. {codext-1.16.3 → codext-1.16.5}/src/codext/checksums/luhn.py +0 -0
  82. {codext-1.16.3 → codext-1.16.5}/src/codext/common/__init__.py +0 -0
  83. {codext-1.16.3 → codext-1.16.5}/src/codext/common/a1z26.py +0 -0
  84. {codext-1.16.3 → codext-1.16.5}/src/codext/common/cases.py +0 -0
  85. {codext-1.16.3 → codext-1.16.5}/src/codext/common/dummy.py +0 -0
  86. {codext-1.16.3 → codext-1.16.5}/src/codext/common/octal.py +0 -0
  87. {codext-1.16.3 → codext-1.16.5}/src/codext/common/ordinal.py +0 -0
  88. {codext-1.16.3 → codext-1.16.5}/src/codext/compressions/__init__.py +0 -0
  89. {codext-1.16.3 → codext-1.16.5}/src/codext/compressions/gzipp.py +0 -0
  90. {codext-1.16.3 → codext-1.16.5}/src/codext/compressions/lz77.py +0 -0
  91. {codext-1.16.3 → codext-1.16.5}/src/codext/compressions/lz78.py +0 -0
  92. {codext-1.16.3 → codext-1.16.5}/src/codext/compressions/pkzip.py +0 -0
  93. {codext-1.16.3 → codext-1.16.5}/src/codext/crypto/affine.py +0 -0
  94. {codext-1.16.3 → codext-1.16.5}/src/codext/crypto/atbash.py +0 -0
  95. {codext-1.16.3 → codext-1.16.5}/src/codext/crypto/bacon.py +0 -0
  96. {codext-1.16.3 → codext-1.16.5}/src/codext/crypto/barbie.py +0 -0
  97. {codext-1.16.3 → codext-1.16.5}/src/codext/crypto/citrix.py +0 -0
  98. {codext-1.16.3 → codext-1.16.5}/src/codext/crypto/railfence.py +0 -0
  99. {codext-1.16.3 → codext-1.16.5}/src/codext/crypto/rot.py +0 -0
  100. {codext-1.16.3 → codext-1.16.5}/src/codext/crypto/scytale.py +0 -0
  101. {codext-1.16.3 → codext-1.16.5}/src/codext/crypto/shift.py +0 -0
  102. {codext-1.16.3 → codext-1.16.5}/src/codext/crypto/vigenere.py +0 -0
  103. {codext-1.16.3 → codext-1.16.5}/src/codext/crypto/xor.py +0 -0
  104. {codext-1.16.3 → codext-1.16.5}/src/codext/hashing/__init__.py +0 -0
  105. {codext-1.16.3 → codext-1.16.5}/src/codext/hashing/blake.py +0 -0
  106. {codext-1.16.3 → codext-1.16.5}/src/codext/hashing/md.py +0 -0
  107. {codext-1.16.3 → codext-1.16.5}/src/codext/hashing/mmh3.py +0 -0
  108. {codext-1.16.3 → codext-1.16.5}/src/codext/hashing/sha.py +0 -0
  109. {codext-1.16.3 → codext-1.16.5}/src/codext/hashing/shake.py +0 -0
  110. {codext-1.16.3 → codext-1.16.5}/src/codext/languages/__init__.py +0 -0
  111. {codext-1.16.3 → codext-1.16.5}/src/codext/languages/braille.py +0 -0
  112. {codext-1.16.3 → codext-1.16.5}/src/codext/languages/galactic.py +0 -0
  113. {codext-1.16.3 → codext-1.16.5}/src/codext/languages/leetspeak.py +0 -0
  114. {codext-1.16.3 → codext-1.16.5}/src/codext/languages/morse.py +0 -0
  115. {codext-1.16.3 → codext-1.16.5}/src/codext/languages/navajo.py +0 -0
  116. {codext-1.16.3 → codext-1.16.5}/src/codext/languages/radio.py +0 -0
  117. {codext-1.16.3 → codext-1.16.5}/src/codext/languages/southpark.py +0 -0
  118. {codext-1.16.3 → codext-1.16.5}/src/codext/languages/tap.py +0 -0
  119. {codext-1.16.3 → codext-1.16.5}/src/codext/languages/tomtom.py +0 -0
  120. {codext-1.16.3 → codext-1.16.5}/src/codext/macros.json +0 -0
  121. {codext-1.16.3 → codext-1.16.5}/src/codext/others/__init__.py +0 -0
  122. {codext-1.16.3 → codext-1.16.5}/src/codext/others/dna.py +0 -0
  123. {codext-1.16.3 → codext-1.16.5}/src/codext/others/kbshift.py +0 -0
  124. {codext-1.16.3 → codext-1.16.5}/src/codext/others/letters.py +0 -0
  125. {codext-1.16.3 → codext-1.16.5}/src/codext/others/markdown.py +0 -0
  126. {codext-1.16.3 → codext-1.16.5}/src/codext/stegano/__init__.py +0 -0
  127. {codext-1.16.3 → codext-1.16.5}/src/codext/stegano/klopf.py +0 -0
  128. {codext-1.16.3 → codext-1.16.5}/src/codext/stegano/resistor.py +0 -0
  129. {codext-1.16.3 → codext-1.16.5}/src/codext/stegano/rick.py +0 -0
  130. {codext-1.16.3 → codext-1.16.5}/src/codext/stegano/sms.py +0 -0
  131. {codext-1.16.3 → codext-1.16.5}/src/codext/web/__init__.py +0 -0
  132. {codext-1.16.3 → codext-1.16.5}/src/codext/web/html.py +0 -0
  133. {codext-1.16.3 → codext-1.16.5}/src/codext/web/url.py +0 -0
  134. {codext-1.16.3 → codext-1.16.5}/src/codext.egg-info/dependency_links.txt +0 -0
  135. {codext-1.16.3 → codext-1.16.5}/src/codext.egg-info/entry_points.txt +0 -0
  136. {codext-1.16.3 → codext-1.16.5}/src/codext.egg-info/requires.txt +0 -0
  137. {codext-1.16.3 → codext-1.16.5}/src/codext.egg-info/top_level.txt +0 -0
  138. {codext-1.16.3 → codext-1.16.5}/tests/__init__.py +0 -0
  139. {codext-1.16.3 → codext-1.16.5}/tests/test_common.py +0 -0
  140. {codext-1.16.3 → codext-1.16.5}/tests/test_generated.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codext
3
- Version: 1.16.3
3
+ Version: 1.16.5
4
4
  Summary: Native codecs extension
5
5
  Author-email: Alexandre D'Hondt <alexandre.dhondt@gmail.com>
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -1034,6 +1034,7 @@ This category also contains `ascii85`, `adobe`, `[x]btoa`, `zeromq` with the `ba
1034
1034
  - [X] `barbie-N`: aka Barbie Typewriter (*N* belongs to [1, 4])
1035
1035
  - [X] `beaufort`: aka Beaufort Cipher (variant of Vigenere Cipher)
1036
1036
  - [X] `citrix`: aka Citrix CTX1 password encoding
1037
+ - [X] `playfair`: aka Playfair Cipher
1037
1038
  - [X] `phillips`: aka Phillips Cipher (polyalphabetic block cipher with 8 key squares)
1038
1039
  - [X] `polybius`: aka Polybius Square Cipher
1039
1040
  - [X] `railfence`: aka Rail Fence Cipher
@@ -336,6 +336,7 @@ This category also contains `ascii85`, `adobe`, `[x]btoa`, `zeromq` with the `ba
336
336
  - [X] `barbie-N`: aka Barbie Typewriter (*N* belongs to [1, 4])
337
337
  - [X] `beaufort`: aka Beaufort Cipher (variant of Vigenere Cipher)
338
338
  - [X] `citrix`: aka Citrix CTX1 password encoding
339
+ - [X] `playfair`: aka Playfair Cipher
339
340
  - [X] `phillips`: aka Phillips Cipher (polyalphabetic block cipher with 8 key squares)
340
341
  - [X] `polybius`: aka Polybius Square Cipher
341
342
  - [X] `railfence`: aka Rail Fence Cipher
Binary file
@@ -257,6 +257,25 @@ This is a dynamic encoding, that is, it can be called with an integer to define
257
257
 
258
258
  -----
259
259
 
260
+ ### Playfair Cipher
261
+
262
+ The Playfair cipher is a symmetric encryption method using polygram substitution with bigrams (pairs of letters), invented in 1854 by Charles Wheatstone, but popularized by Lord Playfair.
263
+
264
+ **Codec** | **Conversions** | **Aliases** | **Comment**
265
+ :---: | :---: | --- | ---
266
+ `playfair` | text <-> Playfair ciphertext | `playfair`, `playfair-playfairexample`, `playfair-keyword` | Dynamic key parameter ; letters only ; `J` is normalized to `I`
267
+
268
+ ```python
269
+ >>> codext.encode("HIDETHEGOLDINTHETREESTUMP", "playfair-playfairexample")
270
+ 'BMODZBXDNABEKUDMUIXMMOUVIF'
271
+ >>> codext.decode("BMODZBXDNABEKUDMUIXMMOUVIF", "playfair-playfairexample")
272
+ 'HIDETHEGOLDINTHETREXESTUMP'
273
+ >>> codext.decode(codext.encode("INSTRUMENT", "playfair-keyword"), "playfair-keyword")
274
+ 'INSTRUMENT'
275
+ ```
276
+
277
+ -----
278
+
260
279
  ### Trithemius Cipher
261
280
 
262
281
  This is a variant of the [Vigenere Cipher](#vigenere-cipher) with key `"ABCDEFGHIJKLMNOPQRSTUVWXYZ"`.
@@ -335,4 +354,3 @@ This is a dynamic encoding, that is, it can be called with an integer to define
335
354
  >>> codext.encode("~bcy*cy*k*~oy~", "xor-10")
336
355
  'this is a test'
337
356
  ```
338
-
@@ -0,0 +1 @@
1
+ 1.16.5
@@ -125,8 +125,10 @@ def base_encode(input, charset, errors="strict", exc=BaseEncodeError):
125
125
  if i > SIZE_LIMIT:
126
126
  raise InputSizeLimitError("Input exceeded size limit")
127
127
  return i * charset[0]
128
- if n == 10:
129
- return str(i) if charset == digits else "".join(charset[int(x)] for x in str(i))
128
+ # keep this fast-path only for a non-standard 10-character charset ; the digits
129
+ # charset uses the generic loop below (leading zeros, bignums, no str/int round-trip)
130
+ if n == 10 and charset != digits:
131
+ return "".join(charset[int(x)] for x in str(i))
130
132
  while i > 0:
131
133
  i, c = divmod(i, n)
132
134
  r = charset[c] + r
@@ -148,8 +150,8 @@ def base_decode(input, charset, errors="strict", exc=BaseDecodeError):
148
150
  i, n, dec = 0, len(charset), lambda n: base_encode(n, [chr(x) for x in range(256)], errors, exc)
149
151
  if n == 1:
150
152
  return i2s(len(input))
151
- if n == 10:
152
- return i2s(int(input)) if charset == digits else "".join(str(charset.index(c)) for c in input)
153
+ if n == 10 and charset != digits:
154
+ return "".join(str(charset.index(c)) for c in input)
153
155
  for k, c in enumerate(input):
154
156
  try:
155
157
  i = i * n + charset.index(c)
@@ -13,6 +13,7 @@ from ..__common__ import add
13
13
 
14
14
  CRC = {
15
15
  '': {
16
+ '': (0x1021, 0xc6c6, True, True, 0, 0xbf05),
16
17
  'a': (0x1021, 0xc6c6, True, True, 0, 0xbf05),
17
18
  },
18
19
  8: {
@@ -78,6 +79,7 @@ CRC = {
78
79
  'mpt1327': (0x6815, 0, False, False, 1, 0x2566),
79
80
  },
80
81
  16: {
82
+ '': (0x8005, 0, True, True, 0, 0xbb3d),
81
83
  'acorn': (0x1021, 0, False, False, 0, 0x31c3),
82
84
  'arc': (0x8005, 0, True, True, 0, 0xbb3d),
83
85
  'atom': (0x002d, 0, True, True, 0, 0x4287),
@@ -4,6 +4,7 @@ from .atbash import *
4
4
  from .bacon import *
5
5
  from .barbie import *
6
6
  from .citrix import *
7
+ from .playfair import *
7
8
  from .phillips import *
8
9
  from .polybius import *
9
10
  from .railfence import *
@@ -13,4 +14,3 @@ from .shift import *
13
14
  from .vic import *
14
15
  from .vigenere import *
15
16
  from .xor import *
16
-
@@ -25,7 +25,9 @@ from ..__common__ import *
25
25
 
26
26
 
27
27
  __examples__ = {
28
+ 'dec(phillips)': None,
28
29
  'enc(phillips)': None,
30
+ 'enc(phillips-key)': {'ATTACK1': None},
29
31
  'enc(phillips-key)': {'ATTACK': "HUUHLL", 'TESTME': "UFZUSM", 'ABCDEF': "HCLMFA"},
30
32
  'enc(phillips-key-5-1-2)': {'This is a Test String': "KPVBVHTCRHCHCGQBT"},
31
33
  'dec(phillips-key-5-1-2)': {'KPVBVHTCRHCHCGQBT': "THISISATESTSTRING"},
@@ -58,69 +60,45 @@ def __make_grids(key):
58
60
  return grids
59
61
 
60
62
 
61
- def _shift_text(text, grids, block_size, dh, dv, errors, decode=False):
62
- t = ensure_str(text).upper().replace("J", "I")
63
- pos_maps = [
64
- {ch: (r, c) for r, row in enumerate(grid) for c, ch in enumerate(row)}
65
- for grid in grids
66
- ]
63
+ def __set_params(key, block_size, dh, dv):
64
+ return (key or "").strip(), \
65
+ int(block_size) if block_size else 5, \
66
+ int(dh) if dh else 1, \
67
+ int(dv) if dv else 1, \
68
+ __make_grids(key) if key and key.isalpha() else None
69
+
70
+
71
+ def __shift_text(text, grids, block_size, dh, dv, errors, decode=False):
72
+ pos_maps = [{ch: (r, c) for r, row in enumerate(grid) for c, ch in enumerate(row)} for grid in grids]
73
+ r, i, t = "", 0, ensure_str(text).upper().replace("J", "I").replace(" ", "")
67
74
  _h = handle_error("phillips", errors, decode=decode)
68
- r, i = "", 0
69
75
  for pos, c in enumerate(t):
70
- if c == " ":
71
- continue
72
76
  if c not in set(_ALPHABET):
73
77
  r += _h(c, pos, r)
74
- continue
75
- grid_idx = (i // block_size) % 8
76
- grid = grids[grid_idx]
77
- s, col = pos_maps[grid_idx][c]
78
- r += grid[(s + dv) % 5][(col + dh) % 5]
79
- i += 1
80
- return r, len(text)
81
-
82
-
83
- def _make_cipher(key, block_size, dh, dv):
84
- _key = (key or "").strip()
85
- try:
86
- block_size = int(block_size) if block_size else 5
87
- except (ValueError, TypeError):
88
- raise LookupError(f"Bad parameter for encoding 'phillips': block_size must be an integer, got {block_size}")
89
- if not (1 <= block_size <= 25):
90
- raise LookupError("Bad parameter for encoding 'phillips': block_size must be between 1 and 25, got "
91
- f"{block_size}")
92
- try:
93
- dh = int(dh) if dh else 1
94
- except (ValueError, TypeError):
95
- raise LookupError(f"Bad parameter for encoding 'phillips': dh must be an integer, got {dh}")
96
- if not (1 <= dh <= 4):
97
- raise LookupError(f"Bad parameter for encoding 'phillips': dh must be between 1 and 4, got {dh}")
98
- try:
99
- dv = int(dv) if dv else 1
100
- except (ValueError, TypeError):
101
- raise LookupError(f"Bad parameter for encoding 'phillips': dv must be an integer, got {dv}")
102
- if not (1 <= dv <= 4):
103
- raise LookupError(f"Bad parameter for encoding 'phillips': dv must be between 1 and 4, got {dv}")
104
- return _key, block_size, dh, dv, __make_grids(_key) if _key and _key.isalpha() else None
78
+ else:
79
+ grid_idx = (i // block_size) % 8
80
+ grid = grids[grid_idx]
81
+ s, col = pos_maps[grid_idx][c]
82
+ r += grid[(s + dv) % 5][(col + dh) % 5]
83
+ i += 1
84
+ return r, len(r)
105
85
 
106
86
 
107
87
  def phillips_encode(key, block_size=None, dh=None, dv=None):
108
- _key, block_size, dh, dv, grids = _make_cipher(key, block_size, dh, dv)
88
+ _key, block_size, dh, dv, grids = __set_params(key, block_size, dh, dv)
109
89
  def encode(text, errors="strict"):
110
- if grids is None:
111
- raise LookupError("Bad parameter for encoding 'phillips': "
112
- "key must be a non-empty alphabetic string")
113
- return _shift_text(text, grids, block_size, dh, dv, errors)
90
+ if _key == "":
91
+ raise LookupError("Bad parameter for encoding 'phillips': key cannot be empty")
92
+ return __shift_text(text, grids, block_size, dh, dv, errors)
114
93
  return encode
115
94
 
116
95
 
117
96
  def phillips_decode(key, block_size=None, dh=None, dv=None):
118
- _key, block_size, dh, dv, grids = _make_cipher(key, block_size, dh, dv)
97
+ _key, block_size, dh, dv, grids = __set_params(key, block_size, dh, dv)
119
98
  def decode(text, errors="strict"):
120
- if grids is None:
121
- raise LookupError("Bad parameter for decoding 'phillips': "
122
- "key must be a non-empty alphabetic string")
123
- return _shift_text(text, grids, block_size, -dh, -dv, errors, True)
99
+ if _key == "":
100
+ raise LookupError("Bad parameter for encoding 'phillips': key cannot be empty")
101
+ return __shift_text(text, grids, block_size, -dh, -dv, errors, True)
124
102
  return decode
125
103
 
126
104
 
@@ -0,0 +1,143 @@
1
+ # -*- coding: UTF-8 -*-
2
+ """Playfair Cipher Codec - playfair content encoding.
3
+
4
+ The Playfair cipher is a symmetric encryption method using polygram substitution
5
+ with bigrams (pairs of letters), invented in 1854 by Charles Wheatstone, but
6
+ popularized by his friend Lord Playfair.
7
+
8
+ This codec:
9
+ - en/decodes strings from str to str
10
+ - en/decodes strings from bytes to bytes
11
+ - decodes file content to str (read)
12
+ - encodes file content from str to bytes (write)
13
+
14
+ Reference: https://www.dcode.fr/playfair-cipher
15
+ """
16
+ from string import ascii_uppercase as UC, digits as DG
17
+
18
+ from ..__common__ import *
19
+
20
+
21
+ __examples__ = {
22
+ 'dec(playfair_cipher-testkey-4)': None,
23
+ 'dec(playfair)': {'SIHTHTDQCUUTUSHOHW': "THISISATESTSTRINGX", '1a': None, 'a1b': None},
24
+ 'dec(playfair-playfairexample)': {'BMODZBXDNABEKUDMUIXMMOUVIF': 'HIDETHEGOLDINTHETREXESTUMP'},
25
+ 'enc(playfair-*-5-XQ-IJ)': {'UDTUTUTGMH': "TESTSTRJNG"},
26
+ 'enc(playfair_cipher-*-5-XX)': None,
27
+ 'enc(playfair_cipher-*-5-X-JJ)': None,
28
+ 'enc(playfair)': {'String!': None, 'This is a test String': "SIHTHTDQCUUTUSHOHW"},
29
+ 'enc(playfair-*-5-XQ-IJ)': {'TESTSTRING': "UDTUTUTGMH"},
30
+ 'enc(playfair-playfairexample)': {'HIDETHEGOLDINTHETREESTUMP': 'BMODZBXDNABEKUDMUIXMMOUVIF'},
31
+ 'enc-dec(playfair-keyword)': ["TEST"],
32
+ 'enc-dec(playfair-*-5-X-IJ)': ["TEST", "TESTSENTENCE"],
33
+ }
34
+ __guess__ = ["playfair"]
35
+
36
+
37
+ # Standard 5×5 Playfair alphabet (I and J share the same cell)
38
+ _DEFAULT_ALPHABET = {
39
+ 5: UC.replace("J", ""),
40
+ 6: UC + DG,
41
+ }
42
+
43
+
44
+ def __build_grid(key=None, grid_size=5, replace_char=('J', 'I')):
45
+ """Build the Playfair grid from an optional keyword. """
46
+ seen, grid = set(), []
47
+ for c in key.upper():
48
+ if c == replace_char[0]:
49
+ c = replace_char[1]
50
+ if c.isalpha() and c not in seen:
51
+ seen.add(c)
52
+ grid.append(c)
53
+ for c in _DEFAULT_ALPHABET[grid_size]:
54
+ if c not in seen:
55
+ seen.add(c)
56
+ grid.append(c)
57
+ return grid, {grid[i]: (i // grid_size, i % grid_size) for i in range(grid_size ** 2)}
58
+
59
+
60
+ def __set_params(key, grid_size, fill_chars, replace_char):
61
+ if len(fc := str(fill_chars or "XQ").upper()) == 2 and fc[0] == fc[1]:
62
+ raise LookupError("Bad parameter for encoding 'playfair': filling char must be a single letter or a pair of "
63
+ "distinct letters")
64
+ if len(rc := str(replace_char or "JI").upper()) == 2 and rc[0] == rc[1]:
65
+ raise LookupError("Bad parameter for encoding 'playfair': replaced char must be a pair of distinct letters")
66
+ gs = int(grid_size or 5)
67
+ a = _DEFAULT_ALPHABET[gs]
68
+ if len(fc) == 1:
69
+ fc += a[(a.index(fc[0])+1) % len(a)]
70
+ return (a if key == "*" else a[::-1] if key == "^" else key).upper(), gs, tuple(fc), tuple(rc)
71
+
72
+
73
+ def playfair_encode(key=None, grid_size=None, fill_chars=None, replace_char=None):
74
+ key, gsize, fchars, rchar = __set_params(key, grid_size, fill_chars, replace_char)
75
+ grid, pos = __build_grid(key, gsize, rchar)
76
+ def encode(text, errors="strict"):
77
+ _filler = lambda c: fchars[1] if c == fchars[0] else fchars[0]
78
+ chars = [rchar[1] if c == rchar[0] else c for c in ensure_str(text).upper().replace(" ", "")]
79
+ result, i, _h = [], 0, handle_error("playfair", errors)
80
+ while i < len(chars):
81
+ a = chars[i]
82
+ if a not in grid:
83
+ result.append(_h(a, i))
84
+ i += 1
85
+ continue
86
+ if i + 1 < len(chars):
87
+ b = chars[i+1]
88
+ if a == b:
89
+ b = _filler(a)
90
+ else:
91
+ i += 1
92
+ else:
93
+ b = _filler(a)
94
+ r_a, c_a = pos[a]
95
+ r_b, c_b = pos[b]
96
+ if r_a == r_b:
97
+ ea, eb = grid[r_a * 5 + (c_a + 1) % 5], grid[r_b * 5 + (c_b + 1) % 5]
98
+ elif c_a == c_b:
99
+ ea, eb = grid[((r_a + 1) % 5) * 5 + c_a], grid[((r_b + 1) % 5) * 5 + c_b]
100
+ else:
101
+ ea, eb = grid[r_a * 5 + c_b], grid[r_b * 5 + c_a]
102
+ result.extend([ea, eb])
103
+ i += 1
104
+ return (r := "".join(result)), len(r)
105
+ return encode
106
+
107
+
108
+ def playfair_decode(key=None, grid_size=None, fill_chars=None, replace_char=None):
109
+ key, gsize, _, rchar = __set_params(key, grid_size, fill_chars, replace_char)
110
+ grid, pos = __build_grid(key, gsize, rchar)
111
+ def decode(text, errors="strict"):
112
+ chars = [rchar[1] if c == rchar[0] else c for c in ensure_str(text)]
113
+ result, i, _h = [], 0, handle_error("playfair", errors, decode=True)
114
+ while i < len(chars):
115
+ try:
116
+ r_a, c_a = pos[chars[i]]
117
+ except KeyError:
118
+ result.append(_h(chars[i], i, "".join(result)))
119
+ i += 1
120
+ continue
121
+ try:
122
+ r_b, c_b = pos[chars[i+1]]
123
+ except KeyError:
124
+ result.append(chars[i])
125
+ result.append(_h(chars[i+1], i+1, "".join(result)))
126
+ i += 2
127
+ continue
128
+ if r_a == r_b:
129
+ da, db = grid[r_a * 5 + (c_a - 1) % 5], grid[r_b * 5 + (c_b - 1) % 5]
130
+ elif c_a == c_b:
131
+ da, db = grid[((r_a - 1) % 5) * 5 + c_a], grid[((r_b - 1) % 5) * 5 + c_b]
132
+ else:
133
+ da, db = grid[r_a * 5 + c_b], grid[r_b * 5 + c_a]
134
+ result.extend([da, db])
135
+ i += 2
136
+ return (r := "".join(result)), len(r)
137
+ return decode
138
+
139
+
140
+ add("playfair", playfair_encode, playfair_decode,
141
+ r"^playfair(?:[-_]cipher)?(?:[-_]([\^*]|[a-zA-Z]+))?(?:[-_]([56]))?(?:[-_]([A-Z]{1,2}))?(?:[-_]([A-Z]{2}))?$",
142
+ printables_rate=1., penalty=.1)
143
+
@@ -11,6 +11,8 @@ from ..__common__ import *
11
11
 
12
12
 
13
13
  __examples__ = {
14
+ 'enc(polybius-ABCDEFGHIKLMNOPQRSTUVZZZZ)': None,
15
+ 'dec(polybius)': {'4423244 24': None},
14
16
  'enc(polybius|polybius-square|polybius_square)': {'this is a test': "44232443 2443 11 44154344"},
15
17
  'enc(polybius-ABCDEFGHIKLMNOPQRSTUVWXYZ)': {'this is a test': "44232443 2443 11 44154344"},
16
18
  'dec(polybius)': {'44232443 2443 11 44154344': "THIS IS A TEST"},
@@ -59,15 +61,12 @@ def polybius_decode(alphabet=_DEFAULT_ALPHABET):
59
61
  _h = handle_error("polybius", errors, decode=True)
60
62
  r, t, i = "", ensure_str(text), 0
61
63
  while i < len(t):
62
- if t[i] == " ":
63
- r += " "
64
- i += 1
65
- elif i + 1 < len(t):
64
+ if t[i:i+2].isdigit():
66
65
  r += decmap.get(t[i:i+2]) or _h(t[i:i+2], i, r)
67
- i += 2
68
- else:
69
- r += _h(t[i], i, r)
70
66
  i += 1
67
+ else:
68
+ r += " " if t[i] == " " else _h(t[i], i, r)
69
+ i += 1
71
70
  return r, len(t)
72
71
  return decode
73
72
 
@@ -137,6 +137,6 @@ def vic_decode(key=None, blanks=None, numeric_key=None):
137
137
  return _decode
138
138
 
139
139
 
140
- add("vic", vic_encode, vic_decode, r"vic(?:[-_]([\^*./a-zA-Z]+))?(?:[-_]([0-9]{2}))?(?:[-_]([0-9]+T?))?$",
140
+ add("vic", vic_encode, vic_decode, r"vic(?:[-_]([\^*]|[./a-zA-Z]+))?(?:[-_]([0-9]{2}))?(?:[-_]([0-9]+T?))?$",
141
141
  printables_rate=1., penalty=.1)
142
142
 
@@ -13,10 +13,10 @@ from ..__common__ import add, ensure_str, UNIX
13
13
 
14
14
  if UNIX:
15
15
  try:
16
- import crypt
16
+ import legacycrypt as crypt
17
17
  except ImportError:
18
18
  try:
19
- import legacycrypt as crypt
19
+ import crypt
20
20
  except ImportError:
21
21
  crypt = None
22
22
 
@@ -13,8 +13,9 @@ from ..__common__ import *
13
13
 
14
14
 
15
15
  __examples__ = {
16
- 'enc-dec(ipsum|lorem-ipsum)': ["This is a test !"],
16
+ 'dec(lorem_ipsum)': {'orci #': None, 'Auctor babel commodo#': None},
17
17
  'enc(ipsum)': {'Bad test#': None},
18
+ 'enc-dec(ipsum|lorem-ipsum)': ["This is a test !"],
18
19
  }
19
20
 
20
21
 
@@ -59,7 +60,7 @@ SCHARS = "0123456789.,:;!?+=-*/\\"
59
60
 
60
61
 
61
62
  def ipsum_encode(text, errors="strict"):
62
- s, strip = "", False
63
+ s, strip, _h = "", False, handle_error("ipsum", errors, " ")
63
64
  for i, c in enumerate(text):
64
65
  try:
65
66
  if c == " " or c in SCHARS:
@@ -70,13 +71,12 @@ def ipsum_encode(text, errors="strict"):
70
71
  s += (w.capitalize() if c.isupper() else w) + " "
71
72
  strip = True
72
73
  except KeyError:
73
- s += handle_error("ipsum", errors, " ")(c, i)
74
- return s[:-1] if strip else s, len(text)
74
+ s += _h(c, i, s)
75
+ return (s := s[:-1] if strip else s), len(s)
75
76
 
76
77
 
77
78
  def ipsum_decode(text, errors="strict"):
78
- s = ""
79
- words = text.split(" ")
79
+ s, words, _h = "", text.split(" "), handle_error("ipsum", errors, decode=True, item="word")
80
80
  for i, w in enumerate(words[:-1] if words[-1] == "" else words):
81
81
  if w.strip() == "":
82
82
  s += " "
@@ -88,8 +88,8 @@ def ipsum_decode(text, errors="strict"):
88
88
  raise KeyError
89
89
  s += w[:len(w)-len(w.lstrip(SCHARS))] + w.strip(SCHARS)[0] + w[len(w.rstrip(SCHARS)):len(w)]
90
90
  except KeyError:
91
- s += handle_error("ipsum", errors, decode=True, item="word")(w, i)
92
- return s, len(text)
91
+ s += _h(w, i, s)
92
+ return s, len(s)
93
93
 
94
94
 
95
95
  add("ipsum", ipsum_encode, ipsum_decode, pattern=r"^(?:lorem[-_]?)?ipsum$", printables_rate=1.,
@@ -13,10 +13,10 @@ from ..__common__ import *
13
13
 
14
14
 
15
15
  __examples__ = {
16
- 'enc(uu|uu_codec)': {'this is a test': "begin 666 -\n.=&AI<R!I<R!A('1E<W0 \n \nend\n"},
17
- 'dec(uu-encode)': {'.=&AI<R!I<R!A(\'1E<W0 ': "this is a test", '.=&AI<R!I<R!A(\'1E<W0`': "this is a test"},
18
16
  'dec(uu-codec)': {'begin 666 -\n.=&AI<R!I<R!A(\'1E<W0`': None, '.=&AI<R!I<R!A(\'1E<W0`\n\n\n`\nend': None},
19
17
  'dec(uu_codec)': {'begin 777 test.txt\n.=&AI<R!I<R!A(\'1E<W0`\n\n\n`\nend': "this is a test"},
18
+ 'dec(uu-encode)': {'.=&AI<R!I<R!A(\'1E<W0 ': "this is a test", '.=&AI<R!I<R!A(\'1E<W0`': "this is a test"},
19
+ 'enc(uu|uu_codec)': {'this is a test': "begin 666 -\n.=&AI<R!I<R!A('1E<W0 \n \nend\n"},
20
20
  'enc-dec(uu)': ["begin 666 -\n.=&AI<R!I<R!A(\'1E<W0`", "@random{512,1024,2048}"],
21
21
  }
22
22
 
@@ -25,11 +25,11 @@ def uu_encode(text, errors="strict"):
25
25
  r, t = b"begin 666 -\n", b(text)
26
26
  for i in range(0, len(t), 45):
27
27
  r += _enc(t[i:i+45])
28
- return r + b" \nend\n", len(text)
28
+ return (r := r + b" \nend\n"), len(r)
29
29
 
30
30
 
31
31
  def uu_decode(text, errors="strict"):
32
- h = handle_error("uu", "strict", decode=True, kind="token", item="line")
32
+ _h = handle_error("uu", "strict", decode=True, kind="token", item="line")
33
33
  lines = b(text).strip(b" \t\r\n\f").split(b"\n")
34
34
  start, end = re.match(b"^begin [1-7]{3} .*$", lines[0]), re.match(b"^end$", lines[-1])
35
35
  if start and end:
@@ -40,15 +40,15 @@ def uu_decode(text, errors="strict"):
40
40
  if errors == "ignore":
41
41
  lines = lines[1:] if start else lines[:-1]
42
42
  elif end:
43
- h(lines[0], 0)
43
+ _h(lines[0], 0)
44
44
  elif start:
45
- h(lines[-1], len(lines)-1)
45
+ _h(lines[-1], len(lines)-1)
46
46
  while len(lines) > 0 and lines[-1].strip(b" \t\r\n\f") in [b"", b"`"]:
47
47
  lines = lines[:-1]
48
48
  r = b""
49
49
  for l in lines:
50
50
  r += _dec(l.strip(b" \t\r\n\f"))
51
- return r, len(text)
51
+ return r, len(r)
52
52
 
53
53
 
54
54
  add("uu", uu_encode, uu_decode, pattern=r"^uu(?:[-_]?encode|[-_]codec)?$",
@@ -11,6 +11,7 @@ from ..__common__ import *
11
11
 
12
12
 
13
13
  __examples__ = {
14
+ 'dec(hexagram)': {'䷕䷞䷈䷇t': None},
14
15
  'enc(hexagram|iching|i-ching-hexagrams)': {'this is a test': "䷰䷭䷚䷔䷞䷺䷗䷔䷞䷺䷗䷚䷏䷊䷂䷕䷞䷈䷇☯"},
15
16
  }
16
17
 
@@ -25,13 +26,13 @@ def hexagram_encode(input, errors="strict"):
25
26
 
26
27
 
27
28
  def hexagram_decode(input, errors="strict"):
28
- r, ehandler = "", handle_error("hexagram", errors, decode=True)
29
+ r, _h = "", handle_error("hexagram", errors, decode=True)
29
30
  for i, c in enumerate(input):
30
31
  try:
31
32
  r += DECMAP[c]
32
33
  except KeyError:
33
- r += ehandler(c, i, r)
34
- return codecs.decode(r, "base64"), len(input)
34
+ r += _h(c, i, r)
35
+ return (r := codecs.decode(r, "base64")), len(r)
35
36
 
36
37
 
37
38
  add("hexagram", hexagram_encode, hexagram_decode, printables_rate=0.,
@@ -18,6 +18,11 @@ __examples1__ = {
18
18
  'enc(whitespace|whitespaces)': {'test': "\t \t \t\t\t \t\t \t \t \t\t \t \t \t\t"},
19
19
  'enc(whitespace-inv|whitespace_inverted)': {'test': " \t\t\t \t \t\t \t \t \t\t\t \t\t \t\t\t \t "},
20
20
  }
21
+ __examples2__ = {
22
+ 'dec(whitespace+after-before)': {'} \n a \n . v ': None},
23
+ 'enc(whitespace+after-before)': {'Test\r': None},
24
+ 'enc-dec(whitespace+after-before)': ["Test", "TESTSTRING!"],
25
+ }
21
26
  __guess1__ = ["whitespace", "whitespace-inv"]
22
27
  __guess2__ = ["whitespace+after-before", "whitespace-after+before"]
23
28
 
@@ -30,42 +35,38 @@ add_map("whitespace", ENCMAP, intype="bin", pattern=r"^whitespaces?([-_]inv(?:er
30
35
  def wsba_encode(p):
31
36
  eq = "ord(c)" + p
32
37
  def encode(text, errors="strict"):
33
- r = []
38
+ r, _h = [], handle_error("whitespace" + p, errors, repl_char="\x00")
34
39
  for i, c in enumerate(text):
35
40
  if ord(c) < min(ord(c) for c in printable[:-6]):
36
- r.append(handle_error("whitespace" + p, errors, repl_char="\x00")(c, i))
37
- continue
38
- enc = "\x00"
39
- offset = random.randint(-10,10)
40
- while enc not in printable[:-6]:
41
- after = random.randint(0, 20)
42
- before = random.randint(0, 20)
43
- enc = chr(eval(eq) % 256)
44
- r.append(" " * before + enc + " " * after)
45
- s = "\n".join(r)
46
- return s, len(s)
41
+ r.append(_h(c, i))
42
+ else:
43
+ enc, offset = "\x00", random.randint(-10,10)
44
+ while enc not in printable[:-6]:
45
+ after, before = random.randint(0, 20), random.randint(0, 20)
46
+ enc = chr(eval(eq) % 256)
47
+ r.append(" " * before + enc + " " * after)
48
+ return (s := "\n".join(r)), len(s)
47
49
  return encode
48
50
 
49
51
 
50
52
  def wsba_decode(p):
51
53
  eq = "ord(c)" + "".join({'-':"+",'+':"-"}.get(c, c) for c in p)
52
54
  def decode(text, errors="strict"):
53
- s = ""
55
+ s, _h = "", handle_error("whitespace_after_before", errors, decode=True, item="line")
54
56
  for i, l in enumerate(text.split("\n")):
55
- ll = len(l.strip())
56
- if ll == 0:
57
+ if (ll := len(l.strip())) == 0:
57
58
  continue
58
59
  if ll > 1:
59
- s += handle_error("whitespace_after_before", errors, decode=True, item="line")(l, i)
60
- after = len(l) - len(l.rstrip(" "))
61
- before = len(l) - len(l.lstrip(" "))
60
+ s += _h(l, i)
61
+ after, before = len(l) - len(l.rstrip(" ")), len(l) - len(l.lstrip(" "))
62
62
  c = l[before]
63
63
  s += chr(eval(eq))
64
- return s, len(text)
64
+ return s, len(s)
65
65
  return decode
66
66
 
67
67
 
68
68
  op = r"[+-](?:\d+(?:\.\d+)?[*/])?"
69
- add("whitespace_after_before", wsba_encode, wsba_decode, guess=__guess2__, entropy=1., printables_rate=1., penalty=.1,
70
- expansion_factor=(22., 3.), pattern=r"whitespace("+op+r"before"+op+r"after|"+op+r"after"+op+r"before)$")
69
+ add("whitespace_after_before", wsba_encode, wsba_decode, examples=__examples2__, guess=__guess2__,
70
+ printables_rate=1., penalty=.1, expansion_factor=(22., 3.), entropy=1.,
71
+ pattern=r"whitespace("+op+r"before"+op+r"after|"+op+r"after"+op+r"before)$")
71
72
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codext
3
- Version: 1.16.3
3
+ Version: 1.16.5
4
4
  Summary: Native codecs extension
5
5
  Author-email: Alexandre D'Hondt <alexandre.dhondt@gmail.com>
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -1034,6 +1034,7 @@ This category also contains `ascii85`, `adobe`, `[x]btoa`, `zeromq` with the `ba
1034
1034
  - [X] `barbie-N`: aka Barbie Typewriter (*N* belongs to [1, 4])
1035
1035
  - [X] `beaufort`: aka Beaufort Cipher (variant of Vigenere Cipher)
1036
1036
  - [X] `citrix`: aka Citrix CTX1 password encoding
1037
+ - [X] `playfair`: aka Playfair Cipher
1037
1038
  - [X] `phillips`: aka Phillips Cipher (polyalphabetic block cipher with 8 key squares)
1038
1039
  - [X] `polybius`: aka Polybius Square Cipher
1039
1040
  - [X] `railfence`: aka Rail Fence Cipher
@@ -12,6 +12,7 @@ requirements.txt
12
12
  .github/prompts/add_codec.prompt.md
13
13
  .github/workflows/python-package.yml
14
14
  docs/coverage.svg
15
+ docs/hakin9-codext.pdf
15
16
  docs/mkdocs.yml
16
17
  docs/requirements.txt
17
18
  docs/pages/cli.md
@@ -86,6 +87,7 @@ src/codext/crypto/bacon.py
86
87
  src/codext/crypto/barbie.py
87
88
  src/codext/crypto/citrix.py
88
89
  src/codext/crypto/phillips.py
90
+ src/codext/crypto/playfair.py
89
91
  src/codext/crypto/polybius.py
90
92
  src/codext/crypto/railfence.py
91
93
  src/codext/crypto/rot.py
@@ -112,7 +112,30 @@ class TestCodecsBase(TestCase):
112
112
  self.assertEqual(codecs.decode(B8, "base8-01234567"), STR)
113
113
  self.assertRaises(LookupError, codecs.encode, "test", "base8-0123456")
114
114
  self.assertRaises(LookupError, codecs.encode, "test", "base8-012345678")
115
-
115
+
116
+ def test_codec_base10(self):
117
+ B10 = "2361031878030638688519054699098996"
118
+ self.assertEqual(codecs.encode(STR, "base10"), B10)
119
+ self.assertEqual(codecs.encode(b(STR), "base10"), b(B10))
120
+ self.assertEqual(codecs.decode(B10, "base10"), STR)
121
+ self.assertEqual(codecs.decode(b(B10), "base10"), b(STR))
122
+ for alias in ["int", "integer", "dec", "decimal"]:
123
+ self.assertEqual(codecs.encode(STR, alias), B10)
124
+ self.assertEqual(codecs.decode(B10, alias), STR)
125
+ # leading null bytes must be preserved as leading '0' characters
126
+ self.assertEqual(codecs.encode("\x00abc", "base10"), "06382179")
127
+ self.assertEqual(codecs.encode("\x00", "base10"), "0")
128
+ self.assertEqual(codecs.encode("\x00\x00abc", "base10"), "006382179")
129
+ self.assertEqual(codecs.decode("06382179", "base10"), "\x00abc")
130
+ self.assertEqual(codecs.decode("00", "base10"), "\x00\x00")
131
+ self.assertEqual(codecs.encode(b("\x00abc"), "base10"), b("06382179"))
132
+ self.assertEqual(codecs.decode(b("06382179"), "base10"), b("\x00abc"))
133
+ # a value whose big integer ends in a 0xe nibble must survive decoding
134
+ self.assertEqual(codecs.decode(codecs.encode(b"d\xc6\xfe", "base10"), "base10"), b"d\xc6\xfe")
135
+ # large inputs must not hit the int<->str conversion digit limit
136
+ for data in [b"\x00\xff\xfe", b"\x00\x00", b"\x2a" * 2048]:
137
+ self.assertEqual(codecs.decode(codecs.encode(data, "base10"), "base10"), data)
138
+
116
139
  def test_codec_base16(self):
117
140
  B16 = "7468697320697320612074657374"
118
141
  self.assertEqual(codecs.encode(STR, "base16"), B16)
@@ -106,6 +106,25 @@ class ManualTestCase(TestCase):
106
106
  for s, r in [("", ""), ("0", "0"), ("1", "8"), ("7992739871", "3")]:
107
107
  self.assertEqual(codecs.encode(s, "luhn"), r)
108
108
  self.assertEqual(codecs.encode("-", "luhn", errors="ignore"), "")
109
+
110
+ def test_codec_crc_bare_defaults(self):
111
+ # a crc<width> used without a variant suffix must resolve to that family's default ;
112
+ # width 16 and the crc-a family had no default and crashed with KeyError instead
113
+ from codext.checksums.crc import CRC
114
+ for n in CRC.keys():
115
+ name = "crc%d" % n if isinstance(n, int) else "crc"
116
+ expected = "%0{}x".format(round((n or 16)/4+.5)) % CRC[n][""][5]
117
+ self.assertEqual(codecs.encode("123456789", name), expected)
118
+ self.assertEqual(codecs.encode(b"123456789", name), b(expected))
119
+ # bare crc-16 is CRC-16/ARC (poly 0x8005), the same as its arc/ibm/lha aliases
120
+ for name in ["crc16", "crc-16", "crc_16", "crc16-arc", "crc16-ibm", "crc16-lha"]:
121
+ self.assertEqual(codecs.encode("123456789", name), "bb3d")
122
+ # bare crc is CRC-A, the same as the crca alias
123
+ self.assertEqual(codecs.encode("123456789", "crc"), "bf05")
124
+ self.assertEqual(codecs.encode("123456789", "crca"), "bf05")
125
+ # adding a default must not make an unknown variant resolve
126
+ self.assertRaises(LookupError, codecs.encode, "123456789", "crc16-bad")
127
+ self.assertRaises(LookupError, codecs.encode, "123456789", "crc-bad")
109
128
 
110
129
  def test_codec_dummy_str_manips(self):
111
130
  STR = "this is a test"
@@ -152,8 +171,7 @@ class ManualTestCase(TestCase):
152
171
  METHODS = [x[7:].lower() for x in crypt.__dict__ if x.startswith("METHOD_")] \
153
172
  if crypt is not None else []
154
173
  for m in METHODS:
155
- h = "crypt-" + m
156
- self.assertIsNotNone(codecs.encode(STR, h))
174
+ self.assertIsNotNone(codecs.encode(STR, h := f"crypt-{m}"))
157
175
  self.assertRaises(NotImplementedError, codecs.decode, STR, h)
158
176
 
159
177
  def test_codec_markdown(self):
@@ -1 +0,0 @@
1
- 1.16.3
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes