codext 1.16.2__tar.gz → 1.16.4__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 (141) hide show
  1. {codext-1.16.2 → codext-1.16.4}/PKG-INFO +3 -1
  2. {codext-1.16.2 → codext-1.16.4}/README.md +2 -0
  3. codext-1.16.4/docs/hakin9-codext.pdf +0 -0
  4. {codext-1.16.2 → codext-1.16.4}/docs/pages/enc/crypto.md +43 -1
  5. codext-1.16.4/src/codext/VERSION.txt +1 -0
  6. {codext-1.16.2 → codext-1.16.4}/src/codext/crypto/__init__.py +2 -1
  7. codext-1.16.4/src/codext/crypto/phillips.py +108 -0
  8. codext-1.16.4/src/codext/crypto/playfair.py +143 -0
  9. {codext-1.16.2 → codext-1.16.4}/src/codext/crypto/polybius.py +6 -7
  10. codext-1.16.4/src/codext/crypto/vic.py +142 -0
  11. {codext-1.16.2 → codext-1.16.4}/src/codext/hashing/crypt.py +2 -2
  12. {codext-1.16.2 → codext-1.16.4}/src/codext/languages/ipsum.py +8 -8
  13. {codext-1.16.2 → codext-1.16.4}/src/codext/others/uuencode.py +7 -7
  14. {codext-1.16.2 → codext-1.16.4}/src/codext/stegano/hexagram.py +4 -3
  15. {codext-1.16.2 → codext-1.16.4}/src/codext/stegano/whitespace.py +22 -21
  16. {codext-1.16.2 → codext-1.16.4}/src/codext.egg-info/PKG-INFO +3 -1
  17. {codext-1.16.2 → codext-1.16.4}/src/codext.egg-info/SOURCES.txt +3 -0
  18. {codext-1.16.2 → codext-1.16.4}/tests/test_manual.py +1 -2
  19. codext-1.16.2/src/codext/VERSION.txt +0 -1
  20. codext-1.16.2/src/codext/crypto/phillips.py +0 -128
  21. {codext-1.16.2 → codext-1.16.4}/.coveragerc +0 -0
  22. {codext-1.16.2 → codext-1.16.4}/.github/ISSUE_TEMPLATE/add-encoding.yml +0 -0
  23. {codext-1.16.2 → codext-1.16.4}/.github/copilot-instructions.md +0 -0
  24. {codext-1.16.2 → codext-1.16.4}/.github/prompts/add_codec.prompt.md +0 -0
  25. {codext-1.16.2 → codext-1.16.4}/.github/pull_request_template.md +0 -0
  26. {codext-1.16.2 → codext-1.16.4}/.github/workflows/python-package.yml +0 -0
  27. {codext-1.16.2 → codext-1.16.4}/.gitignore +0 -0
  28. {codext-1.16.2 → codext-1.16.4}/.readthedocs.yml +0 -0
  29. {codext-1.16.2 → codext-1.16.4}/LICENSE +0 -0
  30. {codext-1.16.2 → codext-1.16.4}/docs/coverage.svg +0 -0
  31. {codext-1.16.2 → codext-1.16.4}/docs/mkdocs.yml +0 -0
  32. {codext-1.16.2 → codext-1.16.4}/docs/pages/cli.md +0 -0
  33. {codext-1.16.2 → codext-1.16.4}/docs/pages/css/extra.css +0 -0
  34. {codext-1.16.2 → codext-1.16.4}/docs/pages/demos/using-bases.gif +0 -0
  35. {codext-1.16.2 → codext-1.16.4}/docs/pages/demos/using-codext.gif +0 -0
  36. {codext-1.16.2 → codext-1.16.4}/docs/pages/demos/using-debase.gif +0 -0
  37. {codext-1.16.2 → codext-1.16.4}/docs/pages/enc/base.md +0 -0
  38. {codext-1.16.2 → codext-1.16.4}/docs/pages/enc/binary.md +0 -0
  39. {codext-1.16.2 → codext-1.16.4}/docs/pages/enc/common.md +0 -0
  40. {codext-1.16.2 → codext-1.16.4}/docs/pages/enc/compressions.md +0 -0
  41. {codext-1.16.2 → codext-1.16.4}/docs/pages/enc/hashing.md +0 -0
  42. {codext-1.16.2 → codext-1.16.4}/docs/pages/enc/languages.md +0 -0
  43. {codext-1.16.2 → codext-1.16.4}/docs/pages/enc/others.md +0 -0
  44. {codext-1.16.2 → codext-1.16.4}/docs/pages/enc/stegano.md +0 -0
  45. {codext-1.16.2 → codext-1.16.4}/docs/pages/enc/web.md +0 -0
  46. {codext-1.16.2 → codext-1.16.4}/docs/pages/features.md +0 -0
  47. {codext-1.16.2 → codext-1.16.4}/docs/pages/guessing.md +0 -0
  48. {codext-1.16.2 → codext-1.16.4}/docs/pages/howto.md +0 -0
  49. {codext-1.16.2 → codext-1.16.4}/docs/pages/img/banner.png +0 -0
  50. {codext-1.16.2 → codext-1.16.4}/docs/pages/img/icon.png +0 -0
  51. {codext-1.16.2 → codext-1.16.4}/docs/pages/img/logo.png +0 -0
  52. {codext-1.16.2 → codext-1.16.4}/docs/pages/index.md +0 -0
  53. {codext-1.16.2 → codext-1.16.4}/docs/pages/manipulations.md +0 -0
  54. {codext-1.16.2 → codext-1.16.4}/docs/requirements.txt +0 -0
  55. {codext-1.16.2 → codext-1.16.4}/pyproject.toml +0 -0
  56. {codext-1.16.2 → codext-1.16.4}/pytest.ini +0 -0
  57. {codext-1.16.2 → codext-1.16.4}/requirements.txt +0 -0
  58. {codext-1.16.2 → codext-1.16.4}/setup.cfg +0 -0
  59. {codext-1.16.2 → codext-1.16.4}/src/codext/__common__.py +0 -0
  60. {codext-1.16.2 → codext-1.16.4}/src/codext/__info__.py +0 -0
  61. {codext-1.16.2 → codext-1.16.4}/src/codext/__init__.py +0 -0
  62. {codext-1.16.2 → codext-1.16.4}/src/codext/base/__init__.py +0 -0
  63. {codext-1.16.2 → codext-1.16.4}/src/codext/base/_base.py +0 -0
  64. {codext-1.16.2 → codext-1.16.4}/src/codext/base/_base2n.py +0 -0
  65. {codext-1.16.2 → codext-1.16.4}/src/codext/base/base100.py +0 -0
  66. {codext-1.16.2 → codext-1.16.4}/src/codext/base/base122.py +0 -0
  67. {codext-1.16.2 → codext-1.16.4}/src/codext/base/base45.py +0 -0
  68. {codext-1.16.2 → codext-1.16.4}/src/codext/base/base85.py +0 -0
  69. {codext-1.16.2 → codext-1.16.4}/src/codext/base/base91.py +0 -0
  70. {codext-1.16.2 → codext-1.16.4}/src/codext/base/baseN.py +0 -0
  71. {codext-1.16.2 → codext-1.16.4}/src/codext/binary/__init__.py +0 -0
  72. {codext-1.16.2 → codext-1.16.4}/src/codext/binary/baudot.py +0 -0
  73. {codext-1.16.2 → codext-1.16.4}/src/codext/binary/bcd.py +0 -0
  74. {codext-1.16.2 → codext-1.16.4}/src/codext/binary/excess3.py +0 -0
  75. {codext-1.16.2 → codext-1.16.4}/src/codext/binary/gray.py +0 -0
  76. {codext-1.16.2 → codext-1.16.4}/src/codext/binary/manchester.py +0 -0
  77. {codext-1.16.2 → codext-1.16.4}/src/codext/binary/rotate.py +0 -0
  78. {codext-1.16.2 → codext-1.16.4}/src/codext/checksums/__init__.py +0 -0
  79. {codext-1.16.2 → codext-1.16.4}/src/codext/checksums/adler.py +0 -0
  80. {codext-1.16.2 → codext-1.16.4}/src/codext/checksums/crc.py +0 -0
  81. {codext-1.16.2 → codext-1.16.4}/src/codext/checksums/luhn.py +0 -0
  82. {codext-1.16.2 → codext-1.16.4}/src/codext/common/__init__.py +0 -0
  83. {codext-1.16.2 → codext-1.16.4}/src/codext/common/a1z26.py +0 -0
  84. {codext-1.16.2 → codext-1.16.4}/src/codext/common/cases.py +0 -0
  85. {codext-1.16.2 → codext-1.16.4}/src/codext/common/dummy.py +0 -0
  86. {codext-1.16.2 → codext-1.16.4}/src/codext/common/octal.py +0 -0
  87. {codext-1.16.2 → codext-1.16.4}/src/codext/common/ordinal.py +0 -0
  88. {codext-1.16.2 → codext-1.16.4}/src/codext/compressions/__init__.py +0 -0
  89. {codext-1.16.2 → codext-1.16.4}/src/codext/compressions/gzipp.py +0 -0
  90. {codext-1.16.2 → codext-1.16.4}/src/codext/compressions/lz77.py +0 -0
  91. {codext-1.16.2 → codext-1.16.4}/src/codext/compressions/lz78.py +0 -0
  92. {codext-1.16.2 → codext-1.16.4}/src/codext/compressions/pkzip.py +0 -0
  93. {codext-1.16.2 → codext-1.16.4}/src/codext/crypto/affine.py +0 -0
  94. {codext-1.16.2 → codext-1.16.4}/src/codext/crypto/atbash.py +0 -0
  95. {codext-1.16.2 → codext-1.16.4}/src/codext/crypto/bacon.py +0 -0
  96. {codext-1.16.2 → codext-1.16.4}/src/codext/crypto/barbie.py +0 -0
  97. {codext-1.16.2 → codext-1.16.4}/src/codext/crypto/citrix.py +0 -0
  98. {codext-1.16.2 → codext-1.16.4}/src/codext/crypto/railfence.py +0 -0
  99. {codext-1.16.2 → codext-1.16.4}/src/codext/crypto/rot.py +0 -0
  100. {codext-1.16.2 → codext-1.16.4}/src/codext/crypto/scytale.py +0 -0
  101. {codext-1.16.2 → codext-1.16.4}/src/codext/crypto/shift.py +0 -0
  102. {codext-1.16.2 → codext-1.16.4}/src/codext/crypto/vigenere.py +0 -0
  103. {codext-1.16.2 → codext-1.16.4}/src/codext/crypto/xor.py +0 -0
  104. {codext-1.16.2 → codext-1.16.4}/src/codext/hashing/__init__.py +0 -0
  105. {codext-1.16.2 → codext-1.16.4}/src/codext/hashing/blake.py +0 -0
  106. {codext-1.16.2 → codext-1.16.4}/src/codext/hashing/md.py +0 -0
  107. {codext-1.16.2 → codext-1.16.4}/src/codext/hashing/mmh3.py +0 -0
  108. {codext-1.16.2 → codext-1.16.4}/src/codext/hashing/sha.py +0 -0
  109. {codext-1.16.2 → codext-1.16.4}/src/codext/hashing/shake.py +0 -0
  110. {codext-1.16.2 → codext-1.16.4}/src/codext/languages/__init__.py +0 -0
  111. {codext-1.16.2 → codext-1.16.4}/src/codext/languages/braille.py +0 -0
  112. {codext-1.16.2 → codext-1.16.4}/src/codext/languages/galactic.py +0 -0
  113. {codext-1.16.2 → codext-1.16.4}/src/codext/languages/leetspeak.py +0 -0
  114. {codext-1.16.2 → codext-1.16.4}/src/codext/languages/morse.py +0 -0
  115. {codext-1.16.2 → codext-1.16.4}/src/codext/languages/navajo.py +0 -0
  116. {codext-1.16.2 → codext-1.16.4}/src/codext/languages/radio.py +0 -0
  117. {codext-1.16.2 → codext-1.16.4}/src/codext/languages/southpark.py +0 -0
  118. {codext-1.16.2 → codext-1.16.4}/src/codext/languages/tap.py +0 -0
  119. {codext-1.16.2 → codext-1.16.4}/src/codext/languages/tomtom.py +0 -0
  120. {codext-1.16.2 → codext-1.16.4}/src/codext/macros.json +0 -0
  121. {codext-1.16.2 → codext-1.16.4}/src/codext/others/__init__.py +0 -0
  122. {codext-1.16.2 → codext-1.16.4}/src/codext/others/dna.py +0 -0
  123. {codext-1.16.2 → codext-1.16.4}/src/codext/others/kbshift.py +0 -0
  124. {codext-1.16.2 → codext-1.16.4}/src/codext/others/letters.py +0 -0
  125. {codext-1.16.2 → codext-1.16.4}/src/codext/others/markdown.py +0 -0
  126. {codext-1.16.2 → codext-1.16.4}/src/codext/stegano/__init__.py +0 -0
  127. {codext-1.16.2 → codext-1.16.4}/src/codext/stegano/klopf.py +0 -0
  128. {codext-1.16.2 → codext-1.16.4}/src/codext/stegano/resistor.py +0 -0
  129. {codext-1.16.2 → codext-1.16.4}/src/codext/stegano/rick.py +0 -0
  130. {codext-1.16.2 → codext-1.16.4}/src/codext/stegano/sms.py +0 -0
  131. {codext-1.16.2 → codext-1.16.4}/src/codext/web/__init__.py +0 -0
  132. {codext-1.16.2 → codext-1.16.4}/src/codext/web/html.py +0 -0
  133. {codext-1.16.2 → codext-1.16.4}/src/codext/web/url.py +0 -0
  134. {codext-1.16.2 → codext-1.16.4}/src/codext.egg-info/dependency_links.txt +0 -0
  135. {codext-1.16.2 → codext-1.16.4}/src/codext.egg-info/entry_points.txt +0 -0
  136. {codext-1.16.2 → codext-1.16.4}/src/codext.egg-info/requires.txt +0 -0
  137. {codext-1.16.2 → codext-1.16.4}/src/codext.egg-info/top_level.txt +0 -0
  138. {codext-1.16.2 → codext-1.16.4}/tests/__init__.py +0 -0
  139. {codext-1.16.2 → codext-1.16.4}/tests/test_base.py +0 -0
  140. {codext-1.16.2 → codext-1.16.4}/tests/test_common.py +0 -0
  141. {codext-1.16.2 → codext-1.16.4}/tests/test_generated.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codext
3
- Version: 1.16.2
3
+ Version: 1.16.4
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
@@ -1041,6 +1042,7 @@ This category also contains `ascii85`, `adobe`, `[x]btoa`, `zeromq` with the `ba
1041
1042
  - [X] `scytaleN`: encrypts using the number of letters on the rod (*N* belongs to [1,[)
1042
1043
  - [X] `shiftN`: shift ordinals (*N* belongs to [1,255])
1043
1044
  - [X] `trithemius`: aka Trithemius Cipher (variant of Vigenere Cipher)
1045
+ - [X] `vic`: aka VIC Cipher
1044
1046
  - [X] `vigenere`: aka Vigenere Cipher
1045
1047
  - [X] `xorN`: XOR with a single byte (*N* belongs to [1,255])
1046
1048
 
@@ -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
@@ -343,6 +344,7 @@ This category also contains `ascii85`, `adobe`, `[x]btoa`, `zeromq` with the `ba
343
344
  - [X] `scytaleN`: encrypts using the number of letters on the rod (*N* belongs to [1,[)
344
345
  - [X] `shiftN`: shift ordinals (*N* belongs to [1,255])
345
346
  - [X] `trithemius`: aka Trithemius Cipher (variant of Vigenere Cipher)
347
+ - [X] `vic`: aka VIC Cipher
346
348
  - [X] `vigenere`: aka Vigenere Cipher
347
349
  - [X] `xorN`: XOR with a single byte (*N* belongs to [1,255])
348
350
 
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"`.
@@ -274,6 +293,30 @@ This is a variant of the [Vigenere Cipher](#vigenere-cipher) with key `"ABCDEFGH
274
293
 
275
294
  -----
276
295
 
296
+ ### VIC Cipher
297
+
298
+ The VIC cipher combines a straddling checkerboard substitution (converting letters to a stream of digits) and an over-encryption based on a modulo-10 sum with eventually a conversion from digits to text.
299
+
300
+ **Codec** | **Conversions** | **Aliases** | **Comment**
301
+ :---: | :---: | --- | ---
302
+ `vic` | text <-> VIC digit/text ciphertext | `vic-keyword`, `vic-key-12`, `vic-^-26-0248`, `vic-*-73-534T` | optional checkerboard keyword or alphabet marker, blank positions (exactly 2 distinct digits), over-encryption key (digits), "`T`" marker for text conversion
303
+
304
+ Alphabet markers:
305
+ - `*`: uses "`ABC[...]XYZ./`" (uppercase letters + "`./`")
306
+ - `^`: uses "`./ZYX[...]CBA`" ("`./`" + reversed uppercase letters)
307
+
308
+ ```python
309
+ >>> import codext
310
+ >>> codext.encode("VICTOR", "vic-^-26-0248T")
311
+ 'VVXYWYY.XYJ'
312
+ >>> codext.decode("VVXYWYY.XYJ", "vic-^-26-0248T")
313
+ 'VICTOR2' # trailing superfluous letter because of text conversion
314
+ >>> codext.encode("LONGTESTSTRING", "vic-^-85-72564")
315
+ '5031237844273727064454072378'
316
+ ```
317
+
318
+ -----
319
+
277
320
  ### Vigenere Cipher
278
321
 
279
322
  This is a dynamic encoding, that is, it holds the key. There is no default key, meaning that `vigenere` as the encoding scheme throws a `LookupError` indicating that the _key must be a non-empty alphabetic string_.
@@ -311,4 +354,3 @@ This is a dynamic encoding, that is, it can be called with an integer to define
311
354
  >>> codext.encode("~bcy*cy*k*~oy~", "xor-10")
312
355
  'this is a test'
313
356
  ```
314
-
@@ -0,0 +1 @@
1
+ 1.16.4
@@ -4,12 +4,13 @@ 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 *
10
11
  from .rot import *
11
12
  from .scytale import *
12
13
  from .shift import *
14
+ from .vic import *
13
15
  from .vigenere import *
14
16
  from .xor import *
15
-
@@ -0,0 +1,108 @@
1
+ # -*- coding: UTF-8 -*-
2
+ """Phillips Cipher Codec - phillips content encoding.
3
+
4
+ The Phillips cipher is a polyalphabetic substitution cipher using 8 key squares. The first square is a 5×5 grid built
5
+ from a keyword (I and J share one cell). Seven additional squares are derived by sequentially swapping adjacent rows
6
+ in a descending bubble pattern. Plaintext is divided into blocks of T letters (default 5); each letter is individually
7
+ enciphered by shifting its grid position right by DH columns and down by DV rows (both default to 1), wrapping
8
+ around with toroidal topology. J is treated as I.
9
+
10
+ Parameters:
11
+ key -- keyword used to seed the initial 5×5 grid (required)
12
+ block_size -- number of letters per grid-cycle block, 1–25 (default 5)
13
+ dh -- horizontal (column) shift for encryption, 1–4 (default 1)
14
+ dv -- vertical (row) shift for encryption, 1–4 (default 1)
15
+
16
+ This codec:
17
+ - en/decodes strings from str to str
18
+ - en/decodes strings from bytes to bytes
19
+ - decodes file content to str (read)
20
+ - encodes file content from str to bytes (write)
21
+
22
+ Reference: https://www.dcode.fr/phillips-cipher
23
+ """
24
+ from ..__common__ import *
25
+
26
+
27
+ __examples__ = {
28
+ 'dec(phillips)': None,
29
+ 'enc(phillips)': None,
30
+ 'enc(phillips-key)': {'ATTACK1': None},
31
+ 'enc(phillips-key)': {'ATTACK': "HUUHLL", 'TESTME': "UFZUSM", 'ABCDEF': "HCLMFA"},
32
+ 'enc(phillips-key-5-1-2)': {'This is a Test String': "KPVBVHTCRHCHCGQBT"},
33
+ 'dec(phillips-key-5-1-2)': {'KPVBVHTCRHCHCGQBT': "THISISATESTSTRING"},
34
+ 'enc-dec(phillips-key)': ["ATTACK", "TESTME", "ABCDEF"],
35
+ 'enc-dec(phillips-secret)': ["HELLOWORLD", "ATTACKATDAWN"],
36
+ 'enc-dec(phillips-key-2)': ["ATTACK", "TESTME"],
37
+ 'enc-dec(phillips-key-5-2)': ["ATTACK"],
38
+ 'enc-dec(phillips-key-5-1-2)': ["ATTACK"],
39
+ }
40
+ __guess__ = ["phillips-key", "phillips-secret", "phillips-password"]
41
+
42
+
43
+ _ALPHABET = "ABCDEFGHIKLMNOPQRSTUVWXYZ"
44
+
45
+
46
+ def __make_grids(key):
47
+ """Return all 8 grids built by a descending bubble-swap row permutation."""
48
+ seen, letters = set(), []
49
+ for c in key.upper().replace("J", "I") + _ALPHABET:
50
+ if c in set(_ALPHABET) and c not in seen:
51
+ letters.append(c)
52
+ seen.add(c)
53
+ grid = [letters[i * 5:(i + 1) * 5] for i in range(5)]
54
+ grids = [grid]
55
+ for k in range(7):
56
+ r = k % 4
57
+ grid = [row[:] for row in grid]
58
+ grid[r], grid[r + 1] = grid[r + 1], grid[r]
59
+ grids.append(grid)
60
+ return grids
61
+
62
+
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(" ", "")
74
+ _h = handle_error("phillips", errors, decode=decode)
75
+ for pos, c in enumerate(t):
76
+ if c not in set(_ALPHABET):
77
+ r += _h(c, pos, r)
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)
85
+
86
+
87
+ def phillips_encode(key, block_size=None, dh=None, dv=None):
88
+ _key, block_size, dh, dv, grids = __set_params(key, block_size, dh, dv)
89
+ def encode(text, errors="strict"):
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)
93
+ return encode
94
+
95
+
96
+ def phillips_decode(key, block_size=None, dh=None, dv=None):
97
+ _key, block_size, dh, dv, grids = __set_params(key, block_size, dh, dv)
98
+ def decode(text, errors="strict"):
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)
102
+ return decode
103
+
104
+
105
+ add("phillips", phillips_encode, phillips_decode,
106
+ r"^phillips(?:[-_]cipher)?(?:[-_]([a-zA-Z]+))?(?:[-_]([1-9]|1[0-9]|2[0-5]))?(?:[-_]([1-4]))?(?:[-_]([1-4]))?$",
107
+ printables_rate=1., penalty=.1)
108
+
@@ -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
 
@@ -0,0 +1,142 @@
1
+ # -*- coding: UTF-8 -*-
2
+ """Vic Cipher Codec - vic content encoding.
3
+
4
+ This codec:
5
+ - en/decodes strings from str to str
6
+ - en/decodes strings from bytes to bytes
7
+ - decodes file content to str (read)
8
+ - encodes file content from str to bytes (write)
9
+ """
10
+ from itertools import cycle
11
+ from string import ascii_uppercase as UC
12
+
13
+ from ..__common__ import *
14
+
15
+
16
+ __examples__ = {
17
+ 'dec(vic-test-33)': None,
18
+ 'dec(vic-*-12-1234T': {'BBXMBDMBCBBHEMC': "THISISATEST"},
19
+ 'dec(vic-^-26-0248T)': {'VVXYWYY.XYJ': "VICTOR2"}, # '2' because of superfluous extra letter from substitution
20
+ 'enc(vic-test-12-ABC)': None,
21
+ 'enc(vic-*-12-1234T': {'This is a Test': "BBXMBDMBCBBHEMC"},
22
+ 'enc(vic-^-26-0248)': {'VICTOR': "88547440546"},
23
+ 'enc(vic-^-26-0248T)': {'VICTOR': "VVXYWYY.XYJ"},
24
+ 'enc-dec(vic-^-26)': ["TEST", "LONGTESTSTRING", "VICTOR"],
25
+ 'enc-dec(vic-^-85-72564)': ["TEST", "LONGTESTSTRING", "VICTOR"],
26
+ 'enc-dec(vic-^-85-54321T)': ["TEST", "LONGTESTSTRING", "VICTOR"],
27
+ }
28
+ __guess__ = ["vic"]
29
+
30
+
31
+ def __build_alphabet(key, alphabet=UC, reverse=False):
32
+ """Return the 28-letter alphabet derived from the input key."""
33
+ seen, result = set(), ""
34
+ for c in (key or "").upper():
35
+ if c.isalpha() and c not in seen:
36
+ result += c
37
+ seen.add(c)
38
+ for c in (alphabet[::-1] if reverse else alphabet):
39
+ if c not in seen:
40
+ result += c
41
+ return "./" + result if reverse else result + "./"
42
+
43
+
44
+ def __build_checkerboard(alphabet, blank1=1, blank2=2):
45
+ """Build encode/decode lookup tables for the straddling checkerboard.
46
+
47
+ Layout with digit1=2 and digit2=6:
48
+ 0 1 [2] 3 4 5 [6] 7 8 9
49
+ 0: * * * * * * * *
50
+ 2: * * * * * * * * * *
51
+ 6: * * * * * * * * * *
52
+ """
53
+ if blank1 == blank2:
54
+ raise LookupError(f"Bad parameter for encoding 'vic': blank1 and blank2 cannot be identical")
55
+ enc, dec, i = {}, {}, 0
56
+ # top row
57
+ for col in range(10):
58
+ if col not in (blank1, blank2):
59
+ enc[alphabet[i]] = str(col)
60
+ i += 1
61
+ # second row ; header digit is 'blank1'
62
+ for col in range(10):
63
+ enc[alphabet[i]] = str(blank1) + str(col)
64
+ i += 1
65
+ # third row ; header digit is 'blank2'
66
+ for col in range(10):
67
+ enc[alphabet[i]] = str(blank2) + str(col)
68
+ i += 1
69
+ return enc, {v: k for k, v in enc.items()}
70
+
71
+
72
+ def __set_params(key, blanks, numeric_key):
73
+ return (UC if key == "*" else UC[::-1] if key == "^" else key).upper(), \
74
+ tuple(map(int, str(blanks) or "12")), \
75
+ str(numeric_key or "").rstrip("T"), \
76
+ key == "^", \
77
+ str(numeric_key or " ")[-1] == "T"
78
+
79
+
80
+ def vic_encode(key=None, blanks=None, numeric_key=None):
81
+ key, blanks, numeric_key, rev, txt = __set_params(key, blanks, numeric_key)
82
+ enc_map, dec_map = __build_checkerboard(__build_alphabet(key, reverse=rev), blanks[0], blanks[1])
83
+ def _encode(text, errors="strict"):
84
+ _h = handle_error("vic", errors)
85
+ digits, nk_i, nk_l = [], 0, len(numeric_key or "")
86
+ for pos, c in enumerate(ensure_str(text).upper().replace(" ", "")):
87
+ # 1) encode with the straddling checkerboard
88
+ c = enc_map[c] if c in enc_map else _h(c, pos, "".join(digits))
89
+ # 2) if numeric_key is defined, over-encrypt digits
90
+ if numeric_key and c.isdigit():
91
+ for ci in c:
92
+ digits.append(str((int(ci) + int(numeric_key[nk_i % nk_l])) % 10))
93
+ nk_i += 1
94
+ else:
95
+ digits.append(c)
96
+ r = "".join(d for d in digits if d)
97
+ # 3) if text mode, convert digits to text
98
+ if txt:
99
+ i, r0, l, r = 0, r, len(r), ""
100
+ while i < len(r0):
101
+ if int(r0[i]) in blanks:
102
+ r += dec_map[r0[i] + ("0" if i == l - 1 else r0[i+1])]
103
+ i += 1
104
+ else:
105
+ r += dec_map[r0[i]]
106
+ i += 1
107
+ return r, len(r)
108
+ return _encode
109
+
110
+
111
+ def vic_decode(key=None, blanks=None, numeric_key=None):
112
+ key, blanks, numeric_key, rev, txt = __set_params(key, blanks, numeric_key)
113
+ enc_map, dec_map = __build_checkerboard(__build_alphabet(key, reverse=rev), blanks[0], blanks[1])
114
+ def _decode(text, errors="strict"):
115
+ _h = handle_error("vic", errors, decode=True)
116
+ # 1) if text mode, convert text to digits
117
+ text = "".join(enc_map[c] for c in ensure_str(text)) if txt else ensure_str(text)
118
+ # 2) if numeric_key is defined, over-decrypt
119
+ digits, nk_i, nk_l = [], 0, len(numeric_key or "")
120
+ for pos, c in enumerate(text):
121
+ if numeric_key and c.isdigit():
122
+ for ci in c:
123
+ digits.append(str((int(c) - int(numeric_key[nk_i % nk_l])) % 10))
124
+ nk_i += 1
125
+ else:
126
+ digits.append(c if c.isdigit() else _h(c, pos, "".join(digits)))
127
+ # 3) decode with the straddling checkerboard
128
+ i, r, r0 = 0, "", "".join(d for d in digits if d)
129
+ while i < (l := len(r0)):
130
+ if int(r0[i]) in blanks:
131
+ r += r0[i] if i == l - 1 else dec_map[r0[i] + r0[i+1]]
132
+ i += 1
133
+ else:
134
+ r += dec_map[r0[i]] if r0[i] in dec_map else _h(r0[i], i, r)
135
+ i += 1
136
+ return r, len(r)
137
+ return _decode
138
+
139
+
140
+ add("vic", vic_encode, vic_decode, r"vic(?:[-_]([\^*]|[./a-zA-Z]+))?(?:[-_]([0-9]{2}))?(?:[-_]([0-9]+T?))?$",
141
+ printables_rate=1., penalty=.1)
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)?$",