-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathghost.py
More file actions
153 lines (122 loc) · 4.67 KB
/
Copy pathghost.py
File metadata and controls
153 lines (122 loc) · 4.67 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
from __future__ import annotations
import argparse
import sys
from pathlib import Path
SENSITIVE_METADATA_KEYS = (
"/Author",
"/Producer",
"/Title",
"/Subject",
"/Creator",
"/Keywords",
"/CreationDate",
"/ModDate",
"/Trapped",
"/PTEX.Fullbanner",
)
class SanitizationError(Exception):
"""Raised when a PDF cannot be sanitized."""
def default_output_path(input_path: Path) -> Path:
return input_path.with_name(f"{input_path.stem}_SANITIZED{input_path.suffix}")
def sanitize_pdf(
input_file: str | Path,
output_file: str | Path | None = None,
*,
verbose: bool = False,
overwrite: bool = False,
) -> Path:
"""Copy a PDF to a new file with metadata values removed."""
input_path = Path(input_file)
output_path = Path(output_file) if output_file else default_output_path(input_path)
if not input_path.is_file():
raise SanitizationError(f"The file '{input_path}' does not exist.")
if input_path.suffix.lower() != ".pdf":
raise SanitizationError(f"The file '{input_path}' is not a PDF.")
if output_path.suffix.lower() != ".pdf":
raise SanitizationError(f"The output file '{output_path}' must use a .pdf extension.")
if input_path.resolve() == output_path.resolve(strict=False):
raise SanitizationError("The output file must be different from the input file.")
if output_path.exists() and not overwrite:
raise SanitizationError(
f"The output file '{output_path}' already exists. Use --overwrite to replace it."
)
try:
from PyPDF2 import PdfReader, PdfWriter
from PyPDF2.errors import PdfReadError
except ImportError as exc:
raise SanitizationError(
"Missing dependency 'PyPDF2'. Install it with: python3 -m pip install -r requirements.txt"
) from exc
try:
print("[+]\tReading the PDF file...")
with input_path.open("rb") as source:
reader = PdfReader(source)
if reader.is_encrypted:
try:
decrypt_result = reader.decrypt("")
except Exception as exc: # PyPDF2 raises several encryption-specific exceptions.
raise SanitizationError(
"The PDF is encrypted and could not be opened with an empty password."
) from exc
if decrypt_result == 0:
raise SanitizationError(
"The PDF is encrypted and could not be opened with an empty password."
)
writer = PdfWriter()
for page in reader.pages:
writer.add_page(page)
sanitized_metadata = {key: "" for key in SENSITIVE_METADATA_KEYS}
if reader.metadata:
for key, value in reader.metadata.items():
if verbose:
print(f"[i]\t{key}: {value}")
sanitized_metadata[str(key)] = ""
else:
print("[i]\tNo metadata found to sanitize.")
writer.add_metadata(sanitized_metadata)
print(f"[+]\tWriting sanitized PDF to '{output_path}'")
with output_path.open("wb") as target:
writer.write(target)
except PdfReadError as exc:
raise SanitizationError(f"Could not read '{input_path}' as a valid PDF.") from exc
except OSError as exc:
raise SanitizationError(str(exc)) from exc
print("[+]\tSanitization complete.")
return output_path
def parse_args(argv: list[str]) -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Remove metadata values from a PDF by writing a sanitized copy."
)
parser.add_argument("input_pdf", help="Path to the PDF to sanitize.")
parser.add_argument(
"-o",
"--output",
help="Path for the sanitized PDF. Defaults to '<name>_SANITIZED.pdf'.",
)
parser.add_argument(
"--overwrite",
action="store_true",
help="Replace the output file if it already exists.",
)
parser.add_argument(
"-v",
"--verbose",
action="store_true",
help="Print metadata keys and values before they are removed.",
)
return parser.parse_args(argv)
def main(argv: list[str] | None = None) -> int:
args = parse_args(sys.argv[1:] if argv is None else argv)
try:
sanitize_pdf(
args.input_pdf,
args.output,
verbose=args.verbose,
overwrite=args.overwrite,
)
except SanitizationError as exc:
print(f"Error: {exc}", file=sys.stderr)
return 1
return 0
if __name__ == "__main__":
raise SystemExit(main())