mirror of
https://github.com/freedomofpress/dangerzone.git
synced 2025-04-28 18:02:38 +02:00
Initial commit
This commit is contained in:
parent
deba465a5b
commit
0b9823a34e
10 changed files with 276 additions and 2 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -127,3 +127,6 @@ dmypy.json
|
||||||
|
|
||||||
# Pyre type checker
|
# Pyre type checker
|
||||||
.pyre/
|
.pyre/
|
||||||
|
|
||||||
|
# Other
|
||||||
|
.vscode
|
14
Pipfile
Normal file
14
Pipfile
Normal 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"
|
|
@ -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
44
dangerzone/__init__.py
Normal 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
29
dangerzone/common.py
Normal 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
75
dangerzone/main_window.py
Normal 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
62
dangerzone/tasks.py
Normal 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
10
dev_scripts/dangerzone
Executable 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
24
setup.py
Normal 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
8
share/Containerfile
Normal 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
|
||||||
|
|
Loading…
Reference in a new issue