Add email encoder
This commit is contained in:
parent
a02e16fd47
commit
c63beef741
4 changed files with 110 additions and 9 deletions
98
demo/fw/tools/create_age_email.py
Normal file
98
demo/fw/tools/create_age_email.py
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import os, sys
|
||||
from pathlib import Path
|
||||
import math
|
||||
import subprocess
|
||||
from email.message import EmailMessage
|
||||
from email.utils import make_msgid
|
||||
import textwrap
|
||||
import io
|
||||
import binascii
|
||||
|
||||
import click
|
||||
import bdfparser
|
||||
from PIL import Image
|
||||
import numpy as np
|
||||
|
||||
import tb_data_encoder
|
||||
|
||||
|
||||
def age_encrypt(content, keys):
|
||||
age_binary = os.environ.get('AGE', 'age')
|
||||
proc = subprocess.run([age_binary, '-e', *(elem for key in keys for elem in ('-r', key))],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=False,
|
||||
input=content.encode('utf-8'))
|
||||
return proc.stdout
|
||||
|
||||
def pack_html(payload_img_cid, hint_img_cid):
|
||||
return textwrap.dedent(f'''\
|
||||
<html style="margin: 0; padding: 0; border: 0; width: 100%; height: 100%">
|
||||
<body style="border-style: solid; border-width: 2px; border-color: #162329; margin: 0; padding: 0; background-color: #d61ca1; width: calc(100% - 4px); height: calc(100% - 4px); overflow: hidden">
|
||||
<img src="cid:{payload_img_cid}" style="margin: 0; padding: 0"/>
|
||||
<br/>
|
||||
<img src="cid:{hint_img_cid}" style="margin: 0; padding: 0"/>
|
||||
</body>
|
||||
</html>
|
||||
''').strip()
|
||||
|
||||
def create_hint_img(text, max_w=200, fgcolor='#e1e31b', bgcolor='#d61ca1'):
|
||||
relative_path = (Path(__file__).parent / '../../../upstream/terminus-font-4.49.1/ter-u16b.bdf').resolve()
|
||||
font_file = os.getenv('HINT_FONT', str(relative_path))
|
||||
font = bdfparser.Font(font_file)
|
||||
hex_to_np = lambda s: np.frombuffer(binascii.unhexlify(s.lstrip('#')), dtype=np.uint8)
|
||||
colors = np.array([hex_to_np(bgcolor), hex_to_np(fgcolor)])
|
||||
|
||||
img = font.draw(text, linelimit=max_w)
|
||||
return Image.fromarray(colors[img.todata(2)])
|
||||
|
||||
# because inexplicably, pillow doesn't have this already
|
||||
def img_to_bytes(img, fmt='png'):
|
||||
bio = io.BytesIO()
|
||||
img.save(bio, fmt)
|
||||
return bio.getvalue()
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.option('-f', '--from', 'sender')
|
||||
@click.option('-t', '--to', 'recipients')
|
||||
@click.option('-c', '--cc', 'cc')
|
||||
@click.option('-b', '--bcc', 'bcc')
|
||||
@click.option('-k', '--key', 'keys', multiple=True, required=True)
|
||||
@click.option('-s', '--subject', default='tachibana/age encrypted message')
|
||||
@click.argument('content', type=click.File('r'))
|
||||
def create_age_email(content, sender, recipients, cc, bcc, keys, subject):
|
||||
|
||||
encrypted = age_encrypt(content.read(), keys)
|
||||
|
||||
msg = EmailMessage()
|
||||
msg['Subject'] = subject
|
||||
if sender:
|
||||
msg['From'] = sender
|
||||
if recipients:
|
||||
msg['To'] = ', '.join(recipients)
|
||||
if cc:
|
||||
msg['Cc'] = ', '.join(cc)
|
||||
if bcc:
|
||||
msg['Bcc'] = ', '.join(bcc)
|
||||
|
||||
payload_img_cid, hint_img_cid = make_msgid(), make_msgid()
|
||||
# remove <...> angle brackets from cids in <img> tags
|
||||
msg.set_content(pack_html(payload_img_cid[1:-1], hint_img_cid[1:-1]), subtype='html')
|
||||
|
||||
w = max(200, math.ceil(math.sqrt(len(encrypted))))
|
||||
h = math.ceil((len(encrypted) + 64) / w)
|
||||
img_bytes = img_to_bytes(tb_data_encoder.data_encode(encrypted, w, h))
|
||||
msg.add_related(img_bytes, 'image', 'png', cid=payload_img_cid)
|
||||
|
||||
img_bytes = img_to_bytes(create_hint_img('tachibana/age\nencrypted email', max_w=200))
|
||||
msg.add_related(img_bytes, 'image', 'png', cid=hint_img_cid)
|
||||
|
||||
sys.stdout.buffer.write(bytes(msg))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
#print(pack_html('foobar'))
|
||||
create_age_email()
|
||||
|
|
@ -21,13 +21,7 @@ WINDOW_MAGIC = np.array([
|
|||
def tb_int_to_px(val):
|
||||
return np.array([(val//0x10000)&0xff, (val//0x100)&0xff, val&0xff], dtype=np.uint8)
|
||||
|
||||
@click.command()
|
||||
@click.option('-w', '--width', type=int, default=400)
|
||||
@click.option('-h', '--height', type=int, default=300)
|
||||
@click.argument('input_file', type=click.File('rb'))
|
||||
@click.argument('output_file', type=click.Path(dir_okay=False, writable=True))
|
||||
def tb_data_encode(width, height, input_file, output_file):
|
||||
data = input_file.read()
|
||||
def data_encode(data, width, height):
|
||||
data = struct.pack('>I', len(data)) + data
|
||||
data = np.frombuffer(data, dtype=np.uint8)
|
||||
|
||||
|
|
@ -46,9 +40,18 @@ def tb_data_encode(width, height, input_file, output_file):
|
|||
win[0, 10] = tb_int_to_px(width)
|
||||
win[0, 11] = tb_int_to_px(height)
|
||||
|
||||
print('out:', ' '.join(f'{x:02x}' for x in win.flatten()[:256]))
|
||||
#print('out:', ' '.join(f'{x:02x}' for x in win.flatten()[:256]))
|
||||
return Image.fromarray(win)
|
||||
|
||||
Image.fromarray(win).save(output_file)
|
||||
@click.command()
|
||||
@click.option('-w', '--width', type=int, default=400)
|
||||
@click.option('-h', '--height', type=int, default=300)
|
||||
@click.argument('input_file', type=click.File('rb'))
|
||||
@click.argument('output_file', type=click.Path(dir_okay=False, writable=True))
|
||||
def tb_data_encode(width, height, input_file, output_file):
|
||||
data = input_file.read()
|
||||
img = data_encode(data, width, height)
|
||||
img.save(output_file)
|
||||
|
||||
if __name__ == '__main__':
|
||||
tb_data_encode()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue