Initial commit

This commit is contained in:
Micah Lee 2020-01-06 14:40:09 -08:00
parent deba465a5b
commit 0b9823a34e
No known key found for this signature in database
GPG key ID: 403C2657CD994F73
10 changed files with 276 additions and 2 deletions

3
.gitignore vendored
View file

@ -127,3 +127,6 @@ dmypy.json
# Pyre type checker # Pyre type checker
.pyre/ .pyre/
# Other
.vscode

14
Pipfile Normal file
View file

@ -0,0 +1,14 @@
[[source]]
url = "https://pypi.python.org/simple"
verify_ssl = true
name = "pypi"
[packages]
PyQt5 = "*"
click = "*"
[dev-packages]
black = "*"
[requires]
python_version = "3.7"

View file

@ -1,2 +1,7 @@
# bastion # dangerzone
Taking arbitrary untrusted PDFs, office documents, or images and convert them to a trusted PDF
Take arbitrary untrusted PDFs, office documents, or images and convert them to a trusted PDF.
## Development environment
You need [podman](https://podman.io/getting-started/installation) ([these instructions](https://kushaldas.in/posts/podman-on-debian-buster.html) are useful for installing Debian/Ubuntu).

44
dangerzone/__init__.py Normal file
View file

@ -0,0 +1,44 @@
from PyQt5 import QtCore, QtWidgets
import sys
import signal
import click
from .common import Common
from .main_window import MainWindow
dangerzone_version = "0.1.0"
@click.command()
@click.option("--filename", default="", help="Document filename")
def main(filename):
print(f"dangerzone {dangerzone_version}")
# Allow Ctrl-C to smoothly quit the program instead of throwing an exception
signal.signal(signal.SIGINT, signal.SIG_DFL)
# Create the Qt app
app = QtWidgets.QApplication(sys.argv)
app.setQuitOnLastWindowClosed(False)
# Common object
common = Common()
# Main window
main_window = MainWindow(app, common)
# If a filename wasn't passed in, get with with a dialog
if filename == "":
filename = QtWidgets.QFileDialog.getOpenFileName(
main_window,
"Open document",
filter="Documents (*.pdf *.docx *.doc *.xlsx *.xls *.pptx *.ppt *.odt *.fodt *.ods *.fods *.odp *.fodp *.odg *.fodg *.odf)",
)
if filename[0] == "":
print("No document was not selected")
return
filename = filename[0]
main_window.start(filename)
sys.exit(app.exec_())

29
dangerzone/common.py Normal file
View file

@ -0,0 +1,29 @@
import sys
import os
import inspect
class Common(object):
"""
The Common class is a singleton of shared functionality throughout the app
"""
def __init__(self):
pass
def get_resource_path(self, filename):
if getattr(sys, "dangerzone_dev", False):
# Look for resources directory relative to python file
prefix = os.path.join(
os.path.dirname(
os.path.dirname(
os.path.abspath(inspect.getfile(inspect.currentframe()))
)
),
"share",
)
else:
print("Error, can only run in dev mode so far")
resource_path = os.path.join(prefix, filename)
return resource_path

75
dangerzone/main_window.py Normal file
View file

@ -0,0 +1,75 @@
from PyQt5 import QtCore, QtGui, QtWidgets
from .tasks import PullImageTask, BuildContainerTask
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, app, common):
super(MainWindow, self).__init__()
self.app = app
self.common = common
self.setWindowTitle("dangerzone")
self.setMinimumWidth(600)
self.setMinimumHeight(500)
self.task_label = QtWidgets.QLabel()
self.task_label.setAlignment(QtCore.Qt.AlignCenter)
self.task_label.setStyleSheet("QLabel { font-weight: bold; font-size: 20px; }")
font = QtGui.QFontDatabase.systemFont(QtGui.QFontDatabase.FixedFont)
self.task_details = QtWidgets.QLabel()
self.task_details.setStyleSheet(
"QLabel { background-color: #ffffff; font-size: 12px; padding: 10px; }"
)
self.task_details.setFont(font)
self.task_details.setAlignment(QtCore.Qt.AlignTop)
self.details_scrollarea = QtWidgets.QScrollArea()
self.details_scrollarea.setWidgetResizable(True)
self.details_scrollarea.setWidget(self.task_details)
self.details_scrollarea.verticalScrollBar().rangeChanged.connect(
self.scroll_to_bottom
)
layout = QtWidgets.QVBoxLayout()
layout.addWidget(self.task_label)
layout.addWidget(self.details_scrollarea, stretch=1)
central_widget = QtWidgets.QWidget()
central_widget.setLayout(layout)
self.setCentralWidget(central_widget)
self.tasks = [PullImageTask, BuildContainerTask]
def start(self, filename):
print(f"Input document: {filename}")
self.document_filename = filename
self.show()
self.next_task()
def next_task(self):
if len(self.tasks) == 0:
print("Tasks finished")
return
self.current_task = self.tasks.pop(0)(self.common)
self.current_task.update_label.connect(self.update_label)
self.current_task.update_details.connect(self.update_details)
self.current_task.thread_finished.connect(self.next_task)
self.current_task.start()
def update_label(self, s):
self.task_label.setText(s)
def update_details(self, s):
self.task_details.setText(s)
def scroll_to_bottom(self, minimum, maximum):
self.details_scrollarea.verticalScrollBar().setValue(maximum)
def closeEvent(self, e):
print("closing")
e.accept()
self.app.quit()

62
dangerzone/tasks.py Normal file
View file

@ -0,0 +1,62 @@
import subprocess
import time
from PyQt5 import QtCore, QtWidgets, QtGui
class TaskBase(QtCore.QThread):
thread_finished = QtCore.pyqtSignal()
update_label = QtCore.pyqtSignal(str)
update_details = QtCore.pyqtSignal(str)
def __init__(self):
super(TaskBase, self).__init__()
def execute_podman(self, args, watch="stdout"):
print(f"Executing: {' '.join(args)}")
output = ""
with subprocess.Popen(
args,
stdin=None,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
bufsize=1,
universal_newlines=True,
) as p:
if watch == "stdout":
pipe = p.stdout
else:
pipe = p.stderr
for line in pipe:
output += line
self.update_details.emit(output)
output += p.stdout.read()
self.update_details.emit(output)
class PullImageTask(TaskBase):
def __init__(self, common):
super(PullImageTask, self).__init__()
self.common = common
def run(self):
self.update_label.emit("Pulling container image")
self.update_details.emit("")
args = ["podman", "pull", "ubuntu:18.04"]
self.execute_podman(args, watch="stderr")
self.thread_finished.emit()
class BuildContainerTask(TaskBase):
def __init__(self, common):
super(BuildContainerTask, self).__init__()
self.common = common
def run(self):
containerfile = self.common.get_resource_path("Containerfile")
self.update_label.emit("Building container")
self.update_details.emit("")
args = ["podman", "build", "-t", "dangerzone", "-f", containerfile]
self.execute_podman(args)
self.thread_finished.emit()

10
dev_scripts/dangerzone Executable file
View file

@ -0,0 +1,10 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Load dangerzone module and resources from the source code tree
import os, sys
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
sys.dangerzone_dev = True
import dangerzone
dangerzone.main()

24
setup.py Normal file
View file

@ -0,0 +1,24 @@
#!/usr/bin/env python3
import setuptools
import os
import sys
from dangerzone import dangerzone_version
setuptools.setup(
name="dangerzone",
version=dangerzone_version,
author="Micah Lee",
author_email="micah.lee@theintercept.com",
license="MIT",
description="Take arbitrary untrusted PDFs, office documents, or images and convert them to a trusted PDF",
url="https://github.com/firstlookmedia/dangerzone",
packages=["dangerzone"],
classifiers=(
"Development Status :: 4 - Beta",
"Programming Language :: Python",
"Intended Audience :: End Users/Desktop",
"Operating System :: OS Independent",
),
entry_points={"console_scripts": ["dangerzone = dangerzone:main"]},
)

8
share/Containerfile Normal file
View file

@ -0,0 +1,8 @@
FROM ubuntu:18.04
RUN apt-get update && \
apt-get install -y poppler-utils imagemagick
RUN useradd -ms /bin/bash user
USER user:user