Skip to content

Commit 2f9066b

Browse files
committed
Added new codec: whitespaces_after_before
1 parent 32e8773 commit 2f9066b

4 files changed

Lines changed: 110 additions & 16 deletions

File tree

README.md

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ This library extends the native `codecs` library and provides some new encodings
2525
`markdown` | markdown --> HTML | unidirectional
2626
`morse` | morse <-> text | uses whitespace as a separator
2727
`nokia3310` | Nokia 3310 keystrokes <-> text | uses "`-`" as a separator for encoding, "`-`" or "`_`" or whitespace for decoding
28-
`octal` | Octal <-> text | dummy octal conversion
29-
`ordinal` | Ordinal <-> text | dummy character ordinals conversion
28+
`octal` | Octal <-> text | dummy octal conversion (converts to 3-digits groups)
29+
`ordinal` | Ordinal <-> text | dummy character ordinals conversion (converts to 3-digits groups)
3030
`radio` | Radio <-> text | aka NATO or radio phonetic alphabet
3131
`resistor` | Resistor <-> text | aka resistor color codes
3232
`rot-N` | ROT(N) <-> text | aka Caesar cipher (N belongs to [1,25])
@@ -35,6 +35,14 @@ This library extends the native `codecs` library and provides some new encodings
3535
`xor-N` | XOR(N) <-> text | XOR with a single byte (N belongs to [1,255])
3636
`whitespace` | Whitespaces <-> text | replaces bits with whitespaces and tabs
3737

38+
A few variants are also implemented.
39+
40+
**Codec** | **Conversions** | **Comment**
41+
:---: | :---: | ---
42+
`octal-spaced` | Octal (whitespace-separated) <-> text | dummy octal conversion
43+
`ordinal-spaced` | Ordinal (whitespace-separated) <-> text | dummy character ordinals conversion
44+
`whitespace_after_before` | Whitespaces[letter]whitespaces <-> text | encodes characters as new characters with whitespaces before and after according to an equation described in the codec name (e.g. "`whitespace+2*after-3*before`")
45+
3846

3947
## Setup
4048

@@ -123,3 +131,32 @@ Example with morse:
123131
f.read()
124132
'this is a test'
125133
```
134+
135+
Example with whitespaces before and after:
136+
137+
```python
138+
>>> codext.decode("""
139+
=
140+
X
141+
:
142+
x
143+
n
144+
r
145+
y
146+
Y
147+
y
148+
p
149+
a
150+
`
151+
n
152+
|
153+
a
154+
o
155+
h
156+
`
157+
g
158+
o
159+
z """, "whitespace-after+before")
160+
'CSC{not_so_invisible}'
161+
```
162+

codext/stegano/whitespace.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,51 @@
77
- decodes file content to str (read)
88
- encodes file content from str to bytes (write)
99
"""
10+
import random
11+
import re
12+
from string import printable
13+
1014
from ..__common__ import *
1115

1216

1317
ENCMAP = {r'': {'0': "\t", '1': " "}, r'[-_]inv(erted)?': {'0': " ", '1': "\t"}}
1418

1519

1620
add_map("whitespace", ENCMAP, binary=True, pattern=r"^whitespace(?:s)?([-_]inv(?:erted)?)?$")
21+
22+
23+
def wsba_encode(p):
24+
eq = "ord(c)" + p
25+
def encode(text, errors="strict"):
26+
r = []
27+
for c in text:
28+
enc = "\x00"
29+
offset = random.randint(-10,10)
30+
while enc not in printable[:-6]:
31+
after = random.randint(0, 20)
32+
before = random.randint(0, 20)
33+
enc = chr(eval(eq) % 256)
34+
r.append(" " * before + enc + " " * after)
35+
s = "\n".join(r)
36+
return s, len(s)
37+
return encode
38+
39+
40+
def wsba_decode(p):
41+
eq = "ord(c)" + "".join({'-':"+",'+':"-"}.get(c, c) for c in p)
42+
def decode(text, errors="strict"):
43+
s = ""
44+
for line in text.split("\n"):
45+
if len(line.strip()) == 0:
46+
continue
47+
after = len(line) - len(line.rstrip(" "))
48+
before = len(line) - len(line.lstrip(" "))
49+
c = line[before]
50+
s += chr(eval(eq))
51+
return s, len(s)
52+
return decode
53+
54+
55+
op = r"[+-](?:\d+(?:\.\d+)?[*/])?"
56+
add("whitespace_after_before", wsba_encode, wsba_decode,
57+
pattern=r"(?i)whitespace("+op+r"before"+op+r"after|"+op+r"after"+op+r"before)$")

docs/encodings.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,7 @@ This simple encoding replaces zeros and ones of the binary version of the input
263263
**Codec** | **Conversions** | **Aliases** | **Comment**
264264
:---: | :---: | --- | ---
265265
`whitespace` | Whitespaces <-> text | `whitespaces?-inv(erted)?` | The default encoding uses tabs for zeros and spaces for ones
266+
`whitespace_after_before` | Whitespaces[letter]whitespaces <-> text | | This codec encodes characters as new characters with whitespaces before and after according to an equation described in the codec name (e.g. "`whitespace+2*after-3*before`")
266267

267268
```python
268269
>>> codext.encode("test", "whitespace")
@@ -274,3 +275,10 @@ This simple encoding replaces zeros and ones of the binary version of the input
274275
>>> codext.decode(" \t\t\t \t \t\t \t \t \t\t\t \t\t \t\t\t \t ", "whitespaces_inverted")
275276
'test'
276277
```
278+
279+
```python
280+
>>> codext.encode("test", "whitespace+after-before")
281+
' m \n l \n u \n m '
282+
>>> codext.decode(" m \n l \n u \n m ", "whitespace+after-before")
283+
'test'
284+
```

tests/test_whitespace.py

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,30 @@
33
"""Whitespace codec tests.
44
55
"""
6+
import random
67
from unittest import TestCase
78

89
from codext.__common__ import *
910

1011

11-
if PY3:
12-
class TestCodecWhitespace(TestCase):
13-
def test_codec_whitespace(self):
14-
STR = "test"
15-
WSP1 = "\t \t \t\t\t \t\t \t \t \t\t \t \t \t\t"
16-
WSP2 = " \t\t\t \t \t\t \t \t \t\t\t \t\t \t\t\t \t "
17-
self.assertEqual(codecs.encode(STR, "whitespace"), WSP1)
18-
self.assertEqual(codecs.encode(b(STR), "whitespace"), b(WSP1))
19-
self.assertEqual(codecs.encode(STR, "whitespace-inv"), WSP2)
20-
self.assertEqual(codecs.encode(b(STR), "whitespace_inverted"), b(WSP2))
21-
self.assertEqual(codecs.decode(WSP1, "whitespace"), STR)
22-
self.assertEqual(codecs.decode(b(WSP1), "whitespace"), b(STR))
23-
self.assertEqual(codecs.decode(WSP2, "whitespace_inv"), STR)
24-
self.assertEqual(codecs.decode(b(WSP2), "whitespace-inverted"), b(STR))
12+
class TestCodecWhitespace(TestCase):
13+
def test_codec_whitespace(self):
14+
STR = "test"
15+
WSP1 = "\t \t \t\t\t \t\t \t \t \t\t \t \t \t\t"
16+
WSP2 = " \t\t\t \t \t\t \t \t \t\t\t \t\t \t\t\t \t "
17+
self.assertEqual(codecs.encode(STR, "whitespace"), WSP1)
18+
self.assertEqual(codecs.encode(b(STR), "whitespace"), b(WSP1))
19+
self.assertEqual(codecs.encode(STR, "whitespace-inv"), WSP2)
20+
self.assertEqual(codecs.encode(b(STR), "whitespace_inverted"), b(WSP2))
21+
self.assertEqual(codecs.decode(WSP1, "whitespace"), STR)
22+
self.assertEqual(codecs.decode(b(WSP1), "whitespace"), b(STR))
23+
self.assertEqual(codecs.decode(WSP2, "whitespace_inv"), STR)
24+
self.assertEqual(codecs.decode(b(WSP2), "whitespace-inverted"), b(STR))
25+
26+
def test_codec_whitespace_after_before(self):
27+
STR = "test"
28+
for i in range(100):
29+
c = "whitespace{}{}*after{}{}*before".format("-+"[random.randint(0, 1)], random.randint(1, 3),
30+
"-+"[random.randint(0, 1)], random.randint(1, 3))
31+
self.assertEqual(codecs.decode("\n" + codecs.encode(STR, c) + "\n", c), STR)
32+

0 commit comments

Comments
 (0)