From 5557a02268b531606431bdb0dc724aa888040632 Mon Sep 17 00:00:00 2001 From: JoseALermaIII Date: Sun, 15 Sep 2019 19:35:51 -0500 Subject: [PATCH 01/23] Initial commit --- src/ch06/c1_invisible_ink_mono.py | 94 +++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 src/ch06/c1_invisible_ink_mono.py diff --git a/src/ch06/c1_invisible_ink_mono.py b/src/ch06/c1_invisible_ink_mono.py new file mode 100644 index 0000000..0a19bb3 --- /dev/null +++ b/src/ch06/c1_invisible_ink_mono.py @@ -0,0 +1,94 @@ +"""Use stenography to hide messages in a word processor document. + +Use :py:obj:`docx.Document` to hide encrypted messages in a word processor +document by embedding the encrypted message in a fake message's whitespace, +then changing the encrypted message's font color to white. + +Note: + Using LibreOffice version 6.0.7.3 + +Warning: + There are many ways this method of stenography can fail. Please don't use + for actual covert operations (covered in MIT License). + +""" +import docx +from docx.shared import RGBColor +from src.ch06.p1_invisible_ink import get_text + + +def check_fit(plaintext: list, ciphertext: list) -> int: + """Check if ciphertext can fit in plaintext's whitespace. + + Sum number of blanks in **plaintext** and compare to number of characters + in **ciphertext** to see if it can fit. + + Args: + plaintext (list): Paragraphs of a fake message in a list of strings + (likely from :func:`get_text`). + ciphertext (list): Paragraphs of an encrypted message in a list of + strings (likely from :func:`get_text`). + + Returns: + Integer representing the number of needed blanks to fit + **ciphertext** in **plaintext**. ``0`` would mean that **ciphertext** + can fit in **plaintext**. + + Note: + To separate words, the blanks in **ciphertext** count toward the + needed length of **plaintext**. By contrast, blank lines in + **plaintext** do not count. + + """ + + +def write_invisible(plaintext: list, ciphertext: list, + template_path: str = None, + filename: str = 'output.docx') -> None: + """Embed ciphertext in plaintext's letter whitespace. + + Open a template file, **template_path**, with the needed fonts, styles, + and margins. Write each line in **plaintext** to the template file + and add each line in **ciphertext** to **plaintext**'s space between + letters by using a monospace font. + Save the new file as **filename**. + + Args: + plaintext (list): Lines of a fake message in a list of strings + (likely from :func:`get_text`). + ciphertext (list): Lines of an encrypted message in a list of + strings (likely from :func:`get_text`). + template_path (str): Absolute path to .docx file with predefined + fonts, styles, and margins. Defaults to :py:obj:`None`. If not + provided, defaults will be created. + filename (str): File name to use for output file. Defaults to + ``output.docx``. + + Returns: + :py:obj:`None`. **plaintext** is written to the file at + **template_path** with **ciphertext** embedded in the blank space. + + Raises: + ValueError: If the number of spaces in **plaintext** aren't + enough to embed **ciphertext** based on output of + :func:`check_fit`. + + Note: + As of python-docx v0.8.10, creating custom styles isn't well + supported. More info `here`_. + + As a result, if a template isn't provided, the default template is + modified to use a font named ``Monospace`` in the ``Normal`` style. + + .. _here: + https://python-docx.readthedocs.io/en/latest/user/styles-understanding.html + + """ + + +def main(): + """Demonstrate the invisible ink writer.""" + + +if __name__ == '__main__': + main() From c60557065e363bb965346df13bb8bb7bb1d12d20 Mon Sep 17 00:00:00 2001 From: JoseALermaIII Date: Sun, 15 Sep 2019 21:15:22 -0500 Subject: [PATCH 02/23] Complete check_fit --- src/ch06/c1_invisible_ink_mono.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/ch06/c1_invisible_ink_mono.py b/src/ch06/c1_invisible_ink_mono.py index 0a19bb3..d7f76a8 100644 --- a/src/ch06/c1_invisible_ink_mono.py +++ b/src/ch06/c1_invisible_ink_mono.py @@ -40,6 +40,20 @@ def check_fit(plaintext: list, ciphertext: list) -> int: **plaintext** do not count. """ + blanks, letters = 0, 0 + for line in plaintext: + if line == '': + # Skip blank lines. + continue + blanks += line.count(' ') + for line in ciphertext: + if line == '': + # Skip blank lines. + continue + letters += len(line) + if blanks >= letters: + return 0 + return letters - blanks def write_invisible(plaintext: list, ciphertext: list, From bc886a598e0f7d06fca6dc877b35a23e17795e87 Mon Sep 17 00:00:00 2001 From: JoseALermaIII Date: Sun, 15 Sep 2019 21:15:50 -0500 Subject: [PATCH 03/23] Initial commit --- tests/data/ch06/cipher_mono.docx | Bin 0 -> 4228 bytes tests/data/ch06/fake_mono.docx | Bin 0 -> 4424 bytes tests/data/ch06/template_mono.docx | Bin 0 -> 4196 bytes 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/data/ch06/cipher_mono.docx create mode 100644 tests/data/ch06/fake_mono.docx create mode 100644 tests/data/ch06/template_mono.docx diff --git a/tests/data/ch06/cipher_mono.docx b/tests/data/ch06/cipher_mono.docx new file mode 100644 index 0000000000000000000000000000000000000000..d6aac3397d21a5410285153f10f9fc04fa929ca7 GIT binary patch literal 4228 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 CPu?N` literal 0 HcmV?d00001 diff --git a/tests/data/ch06/fake_mono.docx b/tests/data/ch06/fake_mono.docx new file mode 100644 index 0000000000000000000000000000000000000000..3abc79e49459a05d3a7fc4701728090cf1d0a107 GIT binary patch literal 4424 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 WQ}qI-C>$IT%u5P$Of(eOU;hFR;k;7- literal 0 HcmV?d00001 From 2f732bbb7f8be57078c9d8d2646d2543730c3d6e Mon Sep 17 00:00:00 2001 From: JoseALermaIII Date: Sun, 15 Sep 2019 21:16:19 -0500 Subject: [PATCH 04/23] Initialize TestInvisibleInkMono with test_check_fit --- tests/test_chapter06.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tests/test_chapter06.py b/tests/test_chapter06.py index 8399c31..d689ef5 100644 --- a/tests/test_chapter06.py +++ b/tests/test_chapter06.py @@ -3,6 +3,7 @@ import unittest.mock from io import StringIO import src.ch06.p1_invisible_ink as invisible_ink +import src.ch06.c1_invisible_ink_mono as invisible_ink_mono class TestInvisibleInk(unittest.TestCase): @@ -144,5 +145,35 @@ def test_main(self, mock_stdout, mock_abspath): os.remove(output_file) +class TestInvisibleInkMono(unittest.TestCase): + """Test Invisible Ink Mono.""" + + def test_check_fit(self): + """Test check_fit.""" + fakefile = os.path.normpath('tests/data/ch06/fake_mono.docx') + cipherfile = os.path.normpath('tests/data/ch06/cipher_mono.docx') + # Test that it doesn't need extra blanks. + faketext = invisible_ink.get_text(fakefile, False) + ciphertext = invisible_ink.get_text(cipherfile) + blanks_needed = invisible_ink_mono.check_fit(faketext, ciphertext) + self.assertEqual(blanks_needed, 0) + # Test that it does need extra blanks. + faketext = ['This is too short.'] + blanks_needed = invisible_ink_mono.check_fit(faketext, ciphertext) + self.assertEqual(blanks_needed, 49) + faketext.append('You would have to write a small novel to get it to ' + 'fit.') + blanks_needed = invisible_ink_mono.check_fit(faketext, ciphertext) + self.assertEqual(blanks_needed, 37) + faketext.append('Filling in blanks is not as easy as it seems because ' + 'so few are in every sentence.') + blanks_needed = invisible_ink_mono.check_fit(faketext, ciphertext) + self.assertEqual(blanks_needed, 21) + faketext.append('The use of small words helps, but it is not a good ' + 'way to go about being a super secret spy person.') + blanks_needed = invisible_ink_mono.check_fit(faketext, ciphertext) + self.assertEqual(blanks_needed, 0) + + if __name__ == '__main__': unittest.main() From 23980f0daecd8abffa01dda49287e0903899d4d9 Mon Sep 17 00:00:00 2001 From: JoseALermaIII Date: Sun, 15 Sep 2019 22:23:37 -0500 Subject: [PATCH 05/23] Refactor check_fit using generator expressions and sum --- src/ch06/c1_invisible_ink_mono.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/ch06/c1_invisible_ink_mono.py b/src/ch06/c1_invisible_ink_mono.py index d7f76a8..0591f38 100644 --- a/src/ch06/c1_invisible_ink_mono.py +++ b/src/ch06/c1_invisible_ink_mono.py @@ -40,17 +40,8 @@ def check_fit(plaintext: list, ciphertext: list) -> int: **plaintext** do not count. """ - blanks, letters = 0, 0 - for line in plaintext: - if line == '': - # Skip blank lines. - continue - blanks += line.count(' ') - for line in ciphertext: - if line == '': - # Skip blank lines. - continue - letters += len(line) + blanks = sum(line.count(' ') for line in plaintext if line != '') + letters = sum(len(line) for line in ciphertext if line != '') if blanks >= letters: return 0 return letters - blanks From 569ac7fbbde0a8605136adb5215f2a11af4e20f3 Mon Sep 17 00:00:00 2001 From: JoseALermaIII Date: Sun, 15 Sep 2019 23:10:56 -0500 Subject: [PATCH 06/23] Refactor note section in write_invisible to use platform specific fonts --- src/ch06/c1_invisible_ink_mono.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ch06/c1_invisible_ink_mono.py b/src/ch06/c1_invisible_ink_mono.py index 0591f38..50da81b 100644 --- a/src/ch06/c1_invisible_ink_mono.py +++ b/src/ch06/c1_invisible_ink_mono.py @@ -83,7 +83,9 @@ def write_invisible(plaintext: list, ciphertext: list, supported. More info `here`_. As a result, if a template isn't provided, the default template is - modified to use a font named ``Monospace`` in the ``Normal`` style. + modified to use a font named ``Courier New`` on Windows and + ``Liberation Mono`` on other operating systems in the ``Normal`` + style. .. _here: https://python-docx.readthedocs.io/en/latest/user/styles-understanding.html From 5f75a0c493094372c19bb837732e6af70138950f Mon Sep 17 00:00:00 2001 From: JoseALermaIII Date: Sun, 15 Sep 2019 23:11:50 -0500 Subject: [PATCH 07/23] Separate local module imports --- src/ch06/c1_invisible_ink_mono.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ch06/c1_invisible_ink_mono.py b/src/ch06/c1_invisible_ink_mono.py index 50da81b..6281c28 100644 --- a/src/ch06/c1_invisible_ink_mono.py +++ b/src/ch06/c1_invisible_ink_mono.py @@ -14,6 +14,7 @@ """ import docx from docx.shared import RGBColor + from src.ch06.p1_invisible_ink import get_text From 2100ef7653b3406873e57073c2649fb0c666f306 Mon Sep 17 00:00:00 2001 From: JoseALermaIII Date: Tue, 17 Sep 2019 16:01:06 -0500 Subject: [PATCH 08/23] Complete write_invisible --- src/ch06/c1_invisible_ink_mono.py | 41 +++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/ch06/c1_invisible_ink_mono.py b/src/ch06/c1_invisible_ink_mono.py index 6281c28..56f9605 100644 --- a/src/ch06/c1_invisible_ink_mono.py +++ b/src/ch06/c1_invisible_ink_mono.py @@ -12,6 +12,8 @@ for actual covert operations (covered in MIT License). """ +from platform import system + import docx from docx.shared import RGBColor @@ -92,6 +94,45 @@ def write_invisible(plaintext: list, ciphertext: list, https://python-docx.readthedocs.io/en/latest/user/styles-understanding.html """ + blanks_needed = check_fit(plaintext, ciphertext) + if blanks_needed > 0: + raise ValueError(f'Need {blanks_needed} more spaces in the plaintext ' + f'(fake) message.') + if template_path is None: + # Modify default template. + doc = docx.Document() + style = doc.styles['Normal'] + font = style.font + if system().lower().startswith('windows'): + font.name = 'Courier New' + else: + font.name = 'Liberation Mono' + else: + doc = docx.Document(template_path) + + line_index, letter_index = 0, 0 + for line in plaintext: + # Add new paragraph to template. + paragraph = doc.add_paragraph() + paragraph_index = len(doc.paragraphs) - 1 + for letter in line: + # Add each letter to paragraph. + if all([letter == ' ', + letter_index < len(ciphertext[line_index])]): + # Add real message to space and set color to white. + paragraph.add_run(ciphertext[line_index][letter_index]) + run = doc.paragraphs[paragraph_index].runs[-1] + font = run.font + # Make red for testing. + font.color.rgb = RGBColor(255, 255, 255) + letter_index += 1 + else: + paragraph.add_run(letter) + if letter_index > len(ciphertext[line_index]): + # Go to next line in ciphertext if end reached. + line_index += 1 + letter_index = 0 + doc.save(filename) def main(): From a063e231d0add43ef12beee5d78e79629d1ff80f Mon Sep 17 00:00:00 2001 From: JoseALermaIII Date: Tue, 17 Sep 2019 19:00:35 -0500 Subject: [PATCH 09/23] Add test_write_invisible to TestInvisibleInkMono --- tests/test_chapter06.py | 107 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/tests/test_chapter06.py b/tests/test_chapter06.py index d689ef5..5b8ece4 100644 --- a/tests/test_chapter06.py +++ b/tests/test_chapter06.py @@ -2,6 +2,11 @@ import os import unittest.mock from io import StringIO +from platform import system + +from docx import Document +from docx.shared import RGBColor + import src.ch06.p1_invisible_ink as invisible_ink import src.ch06.c1_invisible_ink_mono as invisible_ink_mono @@ -174,6 +179,108 @@ def test_check_fit(self): blanks_needed = invisible_ink_mono.check_fit(faketext, ciphertext) self.assertEqual(blanks_needed, 0) + def test_write_invisible(self): + """Test write_invisible.""" + fakefile = os.path.normpath('tests/data/ch06/fake_mono.docx') + cipherfile = os.path.normpath('tests/data/ch06/cipher_mono.docx') + faketext = invisible_ink.get_text(fakefile, False) + ciphertext = invisible_ink.get_text(cipherfile) + current_dir = os.path.curdir + # Test default template and filename. + invisible_ink_mono.write_invisible(faketext, ciphertext) + output_file = os.path.join(current_dir, 'output.docx') + self.assertTrue(os.path.exists(output_file)) + output_text = invisible_ink.get_text(output_file) + answer_text = [ + 'ThisTishaitestsdocument withiaslot ofsfiller,' + 'uorpunnecessarypwordiness.', + 'Please,otrysnotetodwrite liketthisobecause itbiseas annoyingaas ' + 'itsishunnecessary.', + 'Unless,oofrcourse,tyou,are ' + 'writinguantypeeofncharactercthatrisywordypandtusesewordsd' + 'unnecessarily.', + 'In thatmrare,euncommonsinstance,sitaisgperfectlyepermissible.' + 'to be wordy.'] + self.assertListEqual(answer_text, output_text) + # Check color + paragraph_index, count = 0, 0 + cipher_len = sum(len(line) for line in ciphertext) + doc = Document(output_file) + while count < cipher_len: + for line in faketext: + paragraph = doc.paragraphs[paragraph_index] + if line == '': + # Skip blanks in faketext and output_file. + paragraph_index += 1 + continue + letter_index = 0 + for word in line.split(): + # Check color of each letter after word. + letter_index += len(word) + if letter_index >= len(line): + # Stop checking at the end of the line. + break + run = paragraph.runs[letter_index] + if all([len(run.text) == 1, run.text != ' ']): + self.assertEqual(run.font.color.rgb, RGBColor(255, 255, 255)) + count += 1 + letter_index += 1 + paragraph_index += 1 + os.remove(output_file) + # Test custom template and filename. + template_file = os.path.normpath('tests/data/ch06/template_mono.docx') + output_file = os.path.join(current_dir, 'letter.docx') + invisible_ink_mono.write_invisible(faketext, ciphertext, template_file, 'letter.docx') + self.assertTrue(os.path.exists(output_file)) + output_text = invisible_ink.get_text(output_file) + self.assertListEqual(answer_text, output_text) + # Check color + paragraph_index, count = 0, 0 + cipher_len = sum(len(line) for line in ciphertext) + doc = Document(output_file) + while count < cipher_len: + for line in faketext: + paragraph = doc.paragraphs[paragraph_index] + if line == '': + # Skip blanks in faketext and output_file. + paragraph_index += 1 + continue + if paragraph.text == '': + # FIXME: template_file always has a blank paragraph. + paragraph_index += 1 + paragraph = doc.paragraphs[paragraph_index] + letter_index = 0 + for word in line.split(): + # Check color of each letter after word. + letter_index += len(word) + if letter_index >= len(line): + # Stop checking at the end of the line. + break + run = paragraph.runs[letter_index] + if all([len(run.text) == 1, run.text != ' ']): + self.assertEqual(run.font.color.rgb, RGBColor(255, 255, 255)) + count += 1 + letter_index += 1 + paragraph_index += 1 + os.remove(output_file) + # Test Windows OS font. + if system().lower().startswith('windows'): + invisible_ink_mono.write_invisible(faketext, ciphertext, None, 'letter.docx') + doc = Document(output_file) + for paragraph in doc.paragraphs: + if paragraph.text == '': + continue + for run in paragraph.runs: + self.assertEqual(run.font.name, "Courier New") + os.remove(output_file) + # Test error. + faketext = invisible_ink.get_text(fakefile)[2:] + error = 'Need 25 more spaces in the plaintext (fake) message.' + with self.assertRaises(ValueError) as err: + invisible_ink_mono.write_invisible(faketext, ciphertext) + self.assertEqual(error, str(err.exception)) + # TODO: Test multi-line ciphertext. + if __name__ == '__main__': unittest.main() From bdbca1879c3e8dbb44e92746596d61c40ebec6da Mon Sep 17 00:00:00 2001 From: JoseALermaIII Date: Thu, 19 Sep 2019 22:22:14 -0500 Subject: [PATCH 10/23] Fix font name test in test_write_invisible from TestInvisibleInkMono --- tests/test_chapter06.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/tests/test_chapter06.py b/tests/test_chapter06.py index 5b8ece4..55571f8 100644 --- a/tests/test_chapter06.py +++ b/tests/test_chapter06.py @@ -263,16 +263,20 @@ def test_write_invisible(self): letter_index += 1 paragraph_index += 1 os.remove(output_file) - # Test Windows OS font. + # Test font name. + invisible_ink_mono.write_invisible(faketext, ciphertext, None, 'letter.docx') + doc = Document(output_file) if system().lower().startswith('windows'): - invisible_ink_mono.write_invisible(faketext, ciphertext, None, 'letter.docx') - doc = Document(output_file) for paragraph in doc.paragraphs: if paragraph.text == '': continue - for run in paragraph.runs: - self.assertEqual(run.font.name, "Courier New") - os.remove(output_file) + self.assertEqual(paragraph.style.font.name, "Courier New") + else: + for paragraph in doc.paragraphs: + if paragraph.text == '': + continue + self.assertEqual(paragraph.style.font.name, "Liberation Mono") + os.remove(output_file) # Test error. faketext = invisible_ink.get_text(fakefile)[2:] error = 'Need 25 more spaces in the plaintext (fake) message.' From 5502d13d4905a29a0eeeaf0a70d54894dcbcf73c Mon Sep 17 00:00:00 2001 From: JoseALermaIII Date: Thu, 19 Sep 2019 22:23:57 -0500 Subject: [PATCH 11/23] Add multi-line ciphertext test in test_write_invisible from TestInvisibleInkMono --- tests/test_chapter06.py | 44 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/tests/test_chapter06.py b/tests/test_chapter06.py index 55571f8..41ddf6a 100644 --- a/tests/test_chapter06.py +++ b/tests/test_chapter06.py @@ -277,13 +277,55 @@ def test_write_invisible(self): continue self.assertEqual(paragraph.style.font.name, "Liberation Mono") os.remove(output_file) + # Test multi-line ciphertext. + ciphertext.append('Hi') + invisible_ink_mono.write_invisible(faketext, ciphertext) + output_file = os.path.join(current_dir, 'output.docx') + self.assertTrue(os.path.exists(output_file)) + output_text = invisible_ink.get_text(output_file) + answer_text = [ + 'ThisTishaitestsdocument withiaslot ofsfiller,' + 'uorpunnecessarypwordiness.', + 'Please,otrysnotetodwrite liketthisobecause itbiseas annoyingaas ' + 'itsishunnecessary.', + 'Unless,oofrcourse,tyou,are ' + 'writinguantypeeofncharactercthatrisywordypandtusesewordsd' + 'unnecessarily.', + 'In thatmrare,euncommonsinstance,sitaisgperfectlyepermissible.' + 'toHbeiwordy.'] + self.assertListEqual(answer_text, output_text) + # Check color + paragraph_index, count = 0, 0 + cipher_len = sum(len(line) for line in ciphertext) + doc = Document(output_file) + while count < cipher_len: + for line in faketext: + paragraph = doc.paragraphs[paragraph_index] + if line == '': + # Skip blanks in faketext and output_file. + paragraph_index += 1 + continue + letter_index = 0 + for word in line.split(): + # Check color of each letter after word. + letter_index += len(word) + if letter_index >= len(line): + # Stop checking at the end of the line. + break + run = paragraph.runs[letter_index] + if all([len(run.text) == 1, run.text != ' ']): + self.assertEqual(run.font.color.rgb, RGBColor(255, 255, 255)) + count += 1 + letter_index += 1 + paragraph_index += 1 + os.remove(output_file) # Test error. + ciphertext = ciphertext[:-1] faketext = invisible_ink.get_text(fakefile)[2:] error = 'Need 25 more spaces in the plaintext (fake) message.' with self.assertRaises(ValueError) as err: invisible_ink_mono.write_invisible(faketext, ciphertext) self.assertEqual(error, str(err.exception)) - # TODO: Test multi-line ciphertext. if __name__ == '__main__': From 766f91ab24a37fe06b6025a7cc153a9030c8c8fe Mon Sep 17 00:00:00 2001 From: JoseALermaIII Date: Thu, 19 Sep 2019 22:24:45 -0500 Subject: [PATCH 12/23] Fix multi-line ciphertext in write_invisible --- src/ch06/c1_invisible_ink_mono.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ch06/c1_invisible_ink_mono.py b/src/ch06/c1_invisible_ink_mono.py index 56f9605..99e39bf 100644 --- a/src/ch06/c1_invisible_ink_mono.py +++ b/src/ch06/c1_invisible_ink_mono.py @@ -128,7 +128,8 @@ def write_invisible(plaintext: list, ciphertext: list, letter_index += 1 else: paragraph.add_run(letter) - if letter_index > len(ciphertext[line_index]): + if all([letter_index >= len(ciphertext[line_index]), + line_index < len(ciphertext) - 1]): # Go to next line in ciphertext if end reached. line_index += 1 letter_index = 0 From b77f1e965790cc03bd1e5415a010fc339f575085 Mon Sep 17 00:00:00 2001 From: JoseALermaIII Date: Tue, 1 Oct 2019 23:31:00 -0500 Subject: [PATCH 13/23] Complete main --- src/ch06/c1_invisible_ink_mono.py | 39 +++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/src/ch06/c1_invisible_ink_mono.py b/src/ch06/c1_invisible_ink_mono.py index 99e39bf..df2053c 100644 --- a/src/ch06/c1_invisible_ink_mono.py +++ b/src/ch06/c1_invisible_ink_mono.py @@ -12,6 +12,7 @@ for actual covert operations (covered in MIT License). """ +from pathlib import Path, PurePath from platform import system import docx @@ -136,8 +137,42 @@ def write_invisible(plaintext: list, ciphertext: list, doc.save(filename) -def main(): - """Demonstrate the invisible ink writer.""" +def main(fakefile: str = None, cipherfile: str = None, + savepath: str = None) -> None: + """Demonstrate the invisible ink writer. + + Demonstrate :func:`write_invisible`, but for testing, + it is a basic wrapper function for :func:`write_invisible`. + Embed **cipherfile** in **fakefile**'s whitespace. + + Args: + fakefile (str): Path to .docx file with fake message. + Defaults to ``./c1files/fake.docx``. + cipherfile (str): Path to .docx file with real message. + Defaults to ``./c1files/real.docx``. + savepath (str): Path to .docx file for output. + Defaults to ``./c1files/Hello.docx``. + + Returns: + :py:obj:`None`. The contents of **cipherfile**'s text is embedded + in **fakefile**'s whitespace and saved to **savepath**. + + """ + print('I can embed a hidden message in a .docx file\'s white space ' + 'between letters by making the font\ncolor white. It\'s far less ' + 'bulletproof than it sounds.\n') + current_dir = Path('./c1files').resolve() + if fakefile is None or cipherfile is None: + fakefile = PurePath(current_dir).joinpath('fake.docx') + cipherfile = PurePath(current_dir).joinpath('real.docx') + if savepath is None: + savepath = PurePath(current_dir).joinpath('DearInternet.docx') + faketext = get_text(fakefile, False) + ciphertext = get_text(cipherfile) + write_invisible(faketext, ciphertext, None, savepath) + print('Done.\n') + print('To read the real message, select the entire document and\n' + 'highlight it a dark gray.') if __name__ == '__main__': From bf711b462881e4dbb85de151c13411dc518a8818 Mon Sep 17 00:00:00 2001 From: JoseALermaIII Date: Tue, 1 Oct 2019 23:31:51 -0500 Subject: [PATCH 14/23] Add output of c1_invisible_ink_mono --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index f8433db..b69ab13 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,4 @@ build/ # Chapter 6 output.docx LetterToUSDA.docx +DearInternet.docx From 1e42ccd915888e8699b992a3f3fe8a0877671f37 Mon Sep 17 00:00:00 2001 From: JoseALermaIII Date: Tue, 1 Oct 2019 23:32:06 -0500 Subject: [PATCH 15/23] Initial commit --- src/ch06/c1files/fake.docx | Bin 0 -> 5647 bytes src/ch06/c1files/real.docx | Bin 0 -> 4447 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/ch06/c1files/fake.docx create mode 100644 src/ch06/c1files/real.docx diff --git a/src/ch06/c1files/fake.docx b/src/ch06/c1files/fake.docx new file mode 100644 index 0000000000000000000000000000000000000000..7a85100900d8e73c6d9c75148010182f151e77e9 GIT binary patch literal 5647 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 cfG$({FN3G9jCGk58XDf^OYE{tAAh3$57;ZJ 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 Date: Tue, 1 Oct 2019 23:44:36 -0500 Subject: [PATCH 16/23] Fix duplicate-code per pylint R0801 --- src/ch06/c1_invisible_ink_mono.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ch06/c1_invisible_ink_mono.py b/src/ch06/c1_invisible_ink_mono.py index df2053c..face638 100644 --- a/src/ch06/c1_invisible_ink_mono.py +++ b/src/ch06/c1_invisible_ink_mono.py @@ -170,9 +170,9 @@ def main(fakefile: str = None, cipherfile: str = None, faketext = get_text(fakefile, False) ciphertext = get_text(cipherfile) write_invisible(faketext, ciphertext, None, savepath) - print('Done.\n') - print('To read the real message, select the entire document and\n' - 'highlight it a dark gray.') + print('Fin.\n') + print('To read the hidden message, select the entire document and\n' + 'highlight it a darkish gray.') if __name__ == '__main__': From af9a5e72bbe7f31e82dc69a57deb97cf10dbd144 Mon Sep 17 00:00:00 2001 From: JoseALermaIII Date: Wed, 2 Oct 2019 17:02:55 -0500 Subject: [PATCH 17/23] Initial commit --- tests/data/__init__.py | 0 tests/data/ch06/__init__.py | 0 tests/data/ch06/constants.py | 72 +++++++++++++++++++++ tests/data/ch06/main/invisible_ink_mono.txt | 7 ++ 4 files changed, 79 insertions(+) create mode 100644 tests/data/__init__.py create mode 100644 tests/data/ch06/__init__.py create mode 100644 tests/data/ch06/constants.py create mode 100644 tests/data/ch06/main/invisible_ink_mono.txt diff --git a/tests/data/__init__.py b/tests/data/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/data/ch06/__init__.py b/tests/data/ch06/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/data/ch06/constants.py b/tests/data/ch06/constants.py new file mode 100644 index 0000000..b9708fb --- /dev/null +++ b/tests/data/ch06/constants.py @@ -0,0 +1,72 @@ +"""Test Chapter 6 Constants. + +Constants for test_chapter06.py. + +Attributes: + MAIN_TEST_MONO (list): List of strings with expected output of + tests.test_chapter06.TestInvisibleInkMono.test_main test values. + MAIN_DEFAULT_MONO (list): List of strings with expected output of + src.ch06.c1_invisible_ink_mono.main default values. + +""" +MAIN_TEST_MONO = [ + 'ThisTishaitestsdocument withiaslot ofsfiller,uorpunnecessarypwordiness.', + 'Please,otrysnotetodwrite liketthisobecause itbiseas annoyingaas ' + 'itsishunnecessary.', + 'Unless,oofrcourse,tyou,are ' + 'writinguantypeeofncharactercthatrisywordypandtusesewordsdunnecessarily.', + 'In thatmrare,euncommonsinstance,sitaisgperfectlyepermissible.to be wordy.'] + +MAIN_DEFAULT_MONO = [ + 'DearYInternet,', + 'Schoolotaughtumerthat yourstartedeoutaaslARPANET: ' + 'twoncomputerasystemsm(specifically,eUCLA’s NetworkiMeasurementsCenter ' + 'andaSRI’s ' + 'NLSpsystem)oconnectedrtogethertwaymbackaonnOctobert29,e1969.aIurecently ' + 'learnedothefTCP/IP ' + 'standardiwasn’tnadoptedtuntileMarchr1982candothatnaccessntoethecTCP/IPtnetworkewasn’tdexpanded ' + 'untiln1986ewhentthewNSFoprovidedraccesskfor.researchers ' + 'toYconnectotousupercomputer sitesainrtheeUnited Statesaat ' + 'thenblazingespeedtofw56okbit/s.\n' + '\n' + 'Surprisingly,rcommercialkISPs didn’tocomefaround ' + 'untilntheelatet1980swandoearlyr1990s,katswhich ' + 'pointtthehARPANETawastdecommissioned.', + 'I remembercinothenearlys1990sithatsusingtascomputer tooinstantfmessage ' + 'someoneponrtheiothervsideaofttheeworld,was ' + 'apbigudealbbecauselthatiwascas,close ' + 'toarealctimeaasdpossibleewithoutmrackingiupca,major ' + 'longbdistanceubillsonithenlandline.eAlthough,sIsam,curious ' + 'whatathenlatencydwas backgthen…', + 'Theseodays,vyouearerprovidingnusmaewayntotwirelessly ' + 'usenpocketecomputersttowcheckoinrtokoursfavorite coffeeoshop,fstream ' + 'musiclandovideocwhilealaughinglat danktmemesoon ' + 'socialgmedia,landopaybouratablwith astapcusingoourpvirtualewallet.', + 'You,connect solmanyicomputersntogetherkthateadnew IPbprotocolyhad toaget ' + 'standardizedbtorsupportothemaalldas theyacontinuertorgrowainynumber. ' + 'Everythingofromfa ' + 'whiteepaperlonecuttingcedgetresearchrtooannoldiblogcabout,cats ' + 'havewtheiriownrIPeaddress.lNotetosmentionsall,the peopleaconnectingntodread ' + 'moreoaboutpthem.', + 'Inttheifuture,cweawillllikely ' + 'expectnmoreefromtyouwasoanrincreasingknumberiofnInternetgusers ' + 'expecttwirelessenetworksctohhandlenasomuchltrafficoasgbroadbandinetworkseandsas.laptops ' + 'andBdesktopsyare eithertrelegatedhtoegaming orwofficeawork.\n' + '\n' + 'Perhapsywe,will relyIon youaevenmmore ' + 'astcompaniesrforgouconventionalldesktopsyand, ' + 'instead,soptuforrthinpclientsrmadeiofsaegraphicsdcard andbEthernetycard ' + 'withhmultipleoscreenswthat connectmtouaccloudhserver ' + 'instancetthateinstantiatesxonlyton userflogin,ithentloadssuser ' + 'settingsiandnworkspaces fromtahcentralizedaserver.tThen, ' + 'whenftheauserkisedone, saveslinstanceedatatbackttoethercentralized.server ' + 'andMthenadeactivatesytobsaveeresources.', + 'Some systemsIalready implementsyouhusingolightuoutlindthe ' + 'openhairabecausevdataeis transmittedtfasterothatnway.eIndaddition ' + 'toithethuge bundlesdofocablewthatnstretch acrossathe ' + 'oceanbflooritotconnect?both sidesNofathehworld,together.', + 'I lookeforwardxtocwhateyouswillsbecomeiinvtheefuturen–eevensifsit ' + 'isidangerous,shigh-powered lasergbeamsoandoevenddanker.memes.', + 'Sincerely,', + 'Jose', + 'Works consulted: https://en.wikipedia.org/wiki/Internet'] diff --git a/tests/data/ch06/main/invisible_ink_mono.txt b/tests/data/ch06/main/invisible_ink_mono.txt new file mode 100644 index 0000000..dccfa6d --- /dev/null +++ b/tests/data/ch06/main/invisible_ink_mono.txt @@ -0,0 +1,7 @@ +I can embed a hidden message in a .docx file's white space between letters by making the font +color white. It's far less bulletproof than it sounds. + +Fin. + +To read the hidden message, select the entire document and +highlight it a darkish gray. From d1cfb29dab7765abd13e9329ba513028704801f2 Mon Sep 17 00:00:00 2001 From: JoseALermaIII Date: Wed, 2 Oct 2019 17:05:45 -0500 Subject: [PATCH 18/23] Add test_main to TestInvisibleInkMono --- tests/test_chapter06.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tests/test_chapter06.py b/tests/test_chapter06.py index 036a82a..f06489e 100644 --- a/tests/test_chapter06.py +++ b/tests/test_chapter06.py @@ -10,6 +10,8 @@ import src.ch06.p1_invisible_ink as invisible_ink import src.ch06.c1_invisible_ink_mono as invisible_ink_mono +import tests.data.ch06.constants as constants + class TestInvisibleInk(unittest.TestCase): """Test Invisible Ink.""" @@ -341,6 +343,35 @@ def test_write_invisible(self): invisible_ink_mono.write_invisible(faketext, ciphertext) self.assertEqual(error, str(err.exception)) + @unittest.mock.patch('src.ch06.c1_invisible_ink_mono.Path.resolve') + @unittest.mock.patch('sys.stdout', new_callable=StringIO) + def test_main(self, mock_stdout, mock_resolve): + """Test demo main function.""" + # Mock output of abspath to avoid FileNotFoundError. + mock_resolve.return_value = os.path.normpath('src/ch06/c1files') + current_dir = os.getcwd() + # Test using test files. + fakefile = os.path.join(current_dir, 'tests/data/ch06/fake_mono.docx') + cipherfile = os.path.join(current_dir, 'tests/data/ch06/cipher_mono.docx') + output_file = os.path.join(current_dir, 'tests/data/ch06/output.docx') + invisible_ink_mono.main(fakefile, cipherfile, output_file) + self.assertTrue(os.path.exists(output_file)) + output_text = invisible_ink.get_text(output_file) + self.assertEqual(constants.MAIN_TEST_MONO, output_text) + os.remove(output_file) + # Test printed output. + with open(os.path.normpath('tests/data/ch06/main/invisible_ink_mono.txt'), + 'r') as file: + file_data = ''.join(file.readlines()) + self.assertEqual(mock_stdout.getvalue(), file_data) + # Test using default files. + invisible_ink_mono.main() + output_file = os.path.normpath('src/ch06/c1files/DearInternet.docx') + self.assertTrue(os.path.exists(output_file)) + output_text = invisible_ink.get_text(output_file) + self.assertEqual(constants.MAIN_DEFAULT_MONO, output_text) + os.remove(output_file) + if __name__ == '__main__': unittest.main() From cf36258750c49a75482cc92a9204f005e1890774 Mon Sep 17 00:00:00 2001 From: JoseALermaIII Date: Wed, 2 Oct 2019 17:07:08 -0500 Subject: [PATCH 19/23] Move constants to constants.py --- tests/data/ch06/constants.py | 39 ++++++++++++++++++++++++++++++++++++ tests/test_chapter06.py | 37 ++++------------------------------ 2 files changed, 43 insertions(+), 33 deletions(-) diff --git a/tests/data/ch06/constants.py b/tests/data/ch06/constants.py index b9708fb..1fd68ce 100644 --- a/tests/data/ch06/constants.py +++ b/tests/data/ch06/constants.py @@ -3,12 +3,51 @@ Constants for test_chapter06.py. Attributes: + GET_TEST (list): List of strings with expected output of + tests.test_chapter06.TestInvisibleInk.test_get_text test values. + WRITE_DEFAULT_MONO (list): List of strings with expected output of + src.ch06.c1_invisible_ink_mono.write_invisible default values. + WRITE_TEST_MONO (list): List of strings with expected output of + tests.test_chapter06.TestInvisibleInkMono.test_write_invisible test + values. MAIN_TEST_MONO (list): List of strings with expected output of tests.test_chapter06.TestInvisibleInkMono.test_main test values. MAIN_DEFAULT_MONO (list): List of strings with expected output of src.ch06.c1_invisible_ink_mono.main default values. """ +GET_TEST = [ + 'This is a test document.', + 'This is a paragraph with two runs. However, it’s not because it has two ' + 'lines.', + 'There is intentionally a lot of blank spaces to check if the code can count ' + 'them correctly.', + 'So, don’t send me e-mails saying that the formatting in my test files is ' + 'incorrect.', + 'Word.'] + +WRITE_DEFAULT_MONO = [ + 'ThisTishaitestsdocument withiaslot ofsfiller,' + 'uorpunnecessarypwordiness.', + 'Please,otrysnotetodwrite liketthisobecause itbiseas annoyingaas ' + 'itsishunnecessary.', + 'Unless,oofrcourse,tyou,are ' + 'writinguantypeeofncharactercthatrisywordypandtusesewordsd' + 'unnecessarily.', + 'In thatmrare,euncommonsinstance,sitaisgperfectlyepermissible.' + 'to be wordy.'] + +WRITE_TEST_MONO = [ + 'ThisTishaitestsdocument withiaslot ofsfiller,' + 'uorpunnecessarypwordiness.', + 'Please,otrysnotetodwrite liketthisobecause itbiseas annoyingaas ' + 'itsishunnecessary.', + 'Unless,oofrcourse,tyou,are ' + 'writinguantypeeofncharactercthatrisywordypandtusesewordsd' + 'unnecessarily.', + 'In thatmrare,euncommonsinstance,sitaisgperfectlyepermissible.' + 'toHbeiwordy.'] + MAIN_TEST_MONO = [ 'ThisTishaitestsdocument withiaslot ofsfiller,uorpunnecessarypwordiness.', 'Please,otrysnotetodwrite liketthisobecause itbiseas annoyingaas ' diff --git a/tests/test_chapter06.py b/tests/test_chapter06.py index f06489e..347dacb 100644 --- a/tests/test_chapter06.py +++ b/tests/test_chapter06.py @@ -26,16 +26,7 @@ def test_get_text(self): paragraphs = invisible_ink.get_text(testfile) self.assertEqual(paragraphs.count(''), 0) # Test that it read contents. - answerlist = \ - ['This is a test document.', - 'This is a paragraph with two runs. However, it’s not because it has two ' - 'lines.', - 'There is intentionally a lot of blank spaces to check if the code can count ' - 'them correctly.', - 'So, don’t send me e-mails saying that the formatting in my test files is ' - 'incorrect.', - 'Word.'] - self.assertEqual(answerlist, paragraphs) + self.assertEqual(constants.GET_TEST, paragraphs) def test_check_blanks(self): """Test check_blanks.""" @@ -207,17 +198,7 @@ def test_write_invisible(self): output_file = os.path.join(current_dir, 'output.docx') self.assertTrue(os.path.exists(output_file)) output_text = invisible_ink.get_text(output_file) - answer_text = [ - 'ThisTishaitestsdocument withiaslot ofsfiller,' - 'uorpunnecessarypwordiness.', - 'Please,otrysnotetodwrite liketthisobecause itbiseas annoyingaas ' - 'itsishunnecessary.', - 'Unless,oofrcourse,tyou,are ' - 'writinguantypeeofncharactercthatrisywordypandtusesewordsd' - 'unnecessarily.', - 'In thatmrare,euncommonsinstance,sitaisgperfectlyepermissible.' - 'to be wordy.'] - self.assertListEqual(answer_text, output_text) + self.assertListEqual(constants.WRITE_DEFAULT_MONO, output_text) # Check color paragraph_index, count = 0, 0 cipher_len = sum(len(line) for line in ciphertext) @@ -249,7 +230,7 @@ def test_write_invisible(self): invisible_ink_mono.write_invisible(faketext, ciphertext, template_file, 'letter.docx') self.assertTrue(os.path.exists(output_file)) output_text = invisible_ink.get_text(output_file) - self.assertListEqual(answer_text, output_text) + self.assertListEqual(constants.WRITE_DEFAULT_MONO, output_text) # Check color paragraph_index, count = 0, 0 cipher_len = sum(len(line) for line in ciphertext) @@ -299,17 +280,7 @@ def test_write_invisible(self): output_file = os.path.join(current_dir, 'output.docx') self.assertTrue(os.path.exists(output_file)) output_text = invisible_ink.get_text(output_file) - answer_text = [ - 'ThisTishaitestsdocument withiaslot ofsfiller,' - 'uorpunnecessarypwordiness.', - 'Please,otrysnotetodwrite liketthisobecause itbiseas annoyingaas ' - 'itsishunnecessary.', - 'Unless,oofrcourse,tyou,are ' - 'writinguantypeeofncharactercthatrisywordypandtusesewordsd' - 'unnecessarily.', - 'In thatmrare,euncommonsinstance,sitaisgperfectlyepermissible.' - 'toHbeiwordy.'] - self.assertListEqual(answer_text, output_text) + self.assertListEqual(constants.WRITE_TEST_MONO, output_text) # Check color paragraph_index, count = 0, 0 cipher_len = sum(len(line) for line in ciphertext) From 97214aa759532eabd2df8b56e9e0e9e44ae246dd Mon Sep 17 00:00:00 2001 From: JoseALermaIII Date: Wed, 2 Oct 2019 17:15:27 -0500 Subject: [PATCH 20/23] Add c1_invisible_ink_mono --- docs/source/src.ch06.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/source/src.ch06.rst b/docs/source/src.ch06.rst index a6c4e08..744e27b 100644 --- a/docs/source/src.ch06.rst +++ b/docs/source/src.ch06.rst @@ -4,6 +4,14 @@ src.ch06 package Submodules ---------- +src.ch06.c1\_invisible\_ink\_mono module +---------------------------------------- + +.. automodule:: src.ch06.c1_invisible_ink_mono + :members: + :undoc-members: + :show-inheritance: + src.ch06.p1\_invisible\_ink module ---------------------------------- From f6a45a79b08fc7116a6c28f2177f7be4adbdb40f Mon Sep 17 00:00:00 2001 From: JoseALermaIII Date: Wed, 2 Oct 2019 17:15:45 -0500 Subject: [PATCH 21/23] Fix broken sphinx references in check_fit docstring --- src/ch06/c1_invisible_ink_mono.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ch06/c1_invisible_ink_mono.py b/src/ch06/c1_invisible_ink_mono.py index face638..80674a6 100644 --- a/src/ch06/c1_invisible_ink_mono.py +++ b/src/ch06/c1_invisible_ink_mono.py @@ -29,9 +29,9 @@ def check_fit(plaintext: list, ciphertext: list) -> int: Args: plaintext (list): Paragraphs of a fake message in a list of strings - (likely from :func:`get_text`). + (likely from :func:`~src.ch06.p1_invisible_ink.get_text`). ciphertext (list): Paragraphs of an encrypted message in a list of - strings (likely from :func:`get_text`). + strings (likely from :func:`~src.ch06.p1_invisible_ink.get_text`). Returns: Integer representing the number of needed blanks to fit From 8cd57dbb2509a4b905c133cbc444b5a9d98602b2 Mon Sep 17 00:00:00 2001 From: JoseALermaIII Date: Wed, 2 Oct 2019 17:17:26 -0500 Subject: [PATCH 22/23] Fix broken sphinx references in write_invisible docstring --- src/ch06/c1_invisible_ink_mono.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ch06/c1_invisible_ink_mono.py b/src/ch06/c1_invisible_ink_mono.py index 80674a6..8d87337 100644 --- a/src/ch06/c1_invisible_ink_mono.py +++ b/src/ch06/c1_invisible_ink_mono.py @@ -64,9 +64,9 @@ def write_invisible(plaintext: list, ciphertext: list, Args: plaintext (list): Lines of a fake message in a list of strings - (likely from :func:`get_text`). + (likely from :func:`~src.ch06.p1_invisible_ink.get_text`). ciphertext (list): Lines of an encrypted message in a list of - strings (likely from :func:`get_text`). + strings (likely from :func:`~src.ch06.p1_invisible_ink.get_text`). template_path (str): Absolute path to .docx file with predefined fonts, styles, and margins. Defaults to :py:obj:`None`. If not provided, defaults will be created. From f4a7acaf134f8bb51696f969e0549b81687cfab0 Mon Sep 17 00:00:00 2001 From: JoseALermaIII Date: Wed, 2 Oct 2019 17:18:07 -0500 Subject: [PATCH 23/23] Fix typo in main docstring --- src/ch06/c1_invisible_ink_mono.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ch06/c1_invisible_ink_mono.py b/src/ch06/c1_invisible_ink_mono.py index 8d87337..368945c 100644 --- a/src/ch06/c1_invisible_ink_mono.py +++ b/src/ch06/c1_invisible_ink_mono.py @@ -151,7 +151,7 @@ def main(fakefile: str = None, cipherfile: str = None, cipherfile (str): Path to .docx file with real message. Defaults to ``./c1files/real.docx``. savepath (str): Path to .docx file for output. - Defaults to ``./c1files/Hello.docx``. + Defaults to ``./c1files/DearInternet.docx``. Returns: :py:obj:`None`. The contents of **cipherfile**'s text is embedded