From e6446e8b5e9956aac177944293f7c457313d3741 Mon Sep 17 00:00:00 2001 From: Matthias Valvekens Date: Mon, 12 Jul 2021 13:35:15 +0200 Subject: [PATCH 1/2] Add read-only flag to file open logic - Files are opened read-only by default - Adds a checkbox to the file chooser UI - Read-only mode disables UI elements for editing RES-428 --- src/main/java/com/itextpdf/rups/Rups.java | 2 +- .../rups/controller/PdfReaderController.java | 13 ++- .../rups/controller/RupsController.java | 7 +- .../itextpdf/rups/event/OpenFileEvent.java | 13 ++- .../com/itextpdf/rups/io/FileOpenAction.java | 4 +- .../itextpdf/rups/io/PdfOpenFlagsPanel.java | 100 ++++++++++++++++++ .../java/com/itextpdf/rups/model/PdfFile.java | 52 +++------ .../com/itextpdf/rups/view/RupsMenuBar.java | 17 ++- .../rups/view/itext/PdfObjectPanel.java | 4 + 9 files changed, 166 insertions(+), 46 deletions(-) create mode 100644 src/main/java/com/itextpdf/rups/io/PdfOpenFlagsPanel.java diff --git a/src/main/java/com/itextpdf/rups/Rups.java b/src/main/java/com/itextpdf/rups/Rups.java index c714814b..ea0fc626 100644 --- a/src/main/java/com/itextpdf/rups/Rups.java +++ b/src/main/java/com/itextpdf/rups/Rups.java @@ -84,7 +84,7 @@ public void run() { initApplication(frame, controller, onCloseOperation); rups.setController(controller); if (null != f && f.canRead()) { - rups.loadDocumentFromFile(f, false); + rups.loadDocumentFromFile(f, true); } } }); diff --git a/src/main/java/com/itextpdf/rups/controller/PdfReaderController.java b/src/main/java/com/itextpdf/rups/controller/PdfReaderController.java index a4f603bc..e37da773 100644 --- a/src/main/java/com/itextpdf/rups/controller/PdfReaderController.java +++ b/src/main/java/com/itextpdf/rups/controller/PdfReaderController.java @@ -52,6 +52,7 @@ This file is part of the iText (R) project. import com.itextpdf.rups.event.*; import com.itextpdf.rups.io.listeners.PdfTreeNavigationListener; import com.itextpdf.rups.model.ObjectLoader; +import com.itextpdf.rups.model.PdfFile; import com.itextpdf.rups.model.PdfSyntaxParser; import com.itextpdf.rups.model.TreeNodeFactory; import com.itextpdf.rups.view.DebugView; @@ -273,8 +274,16 @@ public void update(Observable observable, Object obj) { ObjectLoader loader = (ObjectLoader) event.getContent(); nodes = loader.getNodes(); PdfTrailerTreeNode root = pdfTree.getRoot(); - root.setTrailer(loader.getFile().getPdfDocument().getTrailer()); - root.setUserObject("PDF Object Tree (" + loader.getLoaderName() + ")"); + PdfFile pdfFile = loader.getFile(); + boolean readOnly = pdfFile.isReadOnly(); + root.setTrailer(pdfFile.getPdfDocument().getTrailer()); + String loaderName = loader.getLoaderName(); + if (readOnly) { + loaderName += "; read-only"; + } + root.setUserObject("PDF Object Tree (" + loaderName + ")"); + // put the object panel into plugin mode to disable the editing UI as necessary + objectPanel.setPluginMode(readOnly); nodes.expandNode(root); navigationTabs.setSelectedIndex(0); setChanged(); diff --git a/src/main/java/com/itextpdf/rups/controller/RupsController.java b/src/main/java/com/itextpdf/rups/controller/RupsController.java index 726b1d07..009fce51 100644 --- a/src/main/java/com/itextpdf/rups/controller/RupsController.java +++ b/src/main/java/com/itextpdf/rups/controller/RupsController.java @@ -195,7 +195,8 @@ public synchronized void drop(DropTargetDropEvent dtde) { if (files == null || files.size() != 1) { JOptionPane.showMessageDialog(masterComponent, "You can only open one file!", "Error", JOptionPane.ERROR_MESSAGE); } else { - loadFile(files.get(0), false); + // open drag & dropped files in read-only mode + loadFile(files.get(0), true); } } catch (HeadlessException | UnsupportedFlavorException | IOException e) { JOptionPane.showMessageDialog(masterComponent, "Error opening file: " + e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE); @@ -245,7 +246,7 @@ public void update(Observable o, Object arg) { closeRoutine(); break; case RupsEvent.OPEN_FILE_EVENT: - loadFile((File) event.getContent(), false); + loadFile((File) event.getContent(), ((OpenFileEvent) event).getOpenReadOnly()); break; case RupsEvent.SAVE_TO_FILE_EVENT: saveFile((File) event.getContent()); @@ -277,7 +278,7 @@ public void update(Observable o, Object arg) { * @param file the file to load * @param readOnly open the file read only or not */ - public void loadFile(File file, boolean readOnly) { + public final void loadFile(File file, boolean readOnly) { try { byte[] contents = readFileToByteArray(file); loadRawContent(contents, file.getName(), file.getParentFile(), readOnly); diff --git a/src/main/java/com/itextpdf/rups/event/OpenFileEvent.java b/src/main/java/com/itextpdf/rups/event/OpenFileEvent.java index 3b63b804..ece7372f 100644 --- a/src/main/java/com/itextpdf/rups/event/OpenFileEvent.java +++ b/src/main/java/com/itextpdf/rups/event/OpenFileEvent.java @@ -47,10 +47,12 @@ This file is part of the iText (R) project. public class OpenFileEvent extends RupsEvent { - private File file; + private final boolean readOnly; + private final File file; - public OpenFileEvent(File file) { + public OpenFileEvent(File file, boolean readOnly) { this.file = file; + this.readOnly = readOnly; } @Override @@ -59,7 +61,12 @@ public int getType() { } @Override - public Object getContent() { + public File getContent() { return file; } + + public boolean getOpenReadOnly() { + return readOnly; + } + } diff --git a/src/main/java/com/itextpdf/rups/io/FileOpenAction.java b/src/main/java/com/itextpdf/rups/io/FileOpenAction.java index 40e8fdf4..baac41bf 100644 --- a/src/main/java/com/itextpdf/rups/io/FileOpenAction.java +++ b/src/main/java/com/itextpdf/rups/io/FileOpenAction.java @@ -63,11 +63,13 @@ public FileOpenAction(Observer observer, FileFilter filter, Component parent) { @Override protected int showDialog() { + fileChooser.setAccessory(new PdfOpenFlagsPanel()); return fileChooser.showOpenDialog(parent); } @Override protected RupsEvent getEvent() { - return new OpenFileEvent(getFile()); + PdfOpenFlagsPanel pofp = (PdfOpenFlagsPanel) fileChooser.getAccessory(); + return new OpenFileEvent(getFile(), pofp.getOpenReadOnly()); } } diff --git a/src/main/java/com/itextpdf/rups/io/PdfOpenFlagsPanel.java b/src/main/java/com/itextpdf/rups/io/PdfOpenFlagsPanel.java new file mode 100644 index 00000000..d06f532b --- /dev/null +++ b/src/main/java/com/itextpdf/rups/io/PdfOpenFlagsPanel.java @@ -0,0 +1,100 @@ +/* + This file is part of the iText (R) project. + Copyright (c) 1998-2021 iText Group NV + Authors: iText Software. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License version 3 + as published by the Free Software Foundation with the addition of the + following permission added to Section 15 as permitted in Section 7(a): + FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY + ITEXT GROUP. ITEXT GROUP DISCLAIMS THE WARRANTY OF NON INFRINGEMENT + OF THIRD PARTY RIGHTS + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Affero General Public License for more details. + You should have received a copy of the GNU Affero General Public License + along with this program; if not, see http://www.gnu.org/licenses or write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA, 02110-1301 USA, or download the license from the following URL: + http://itextpdf.com/terms-of-use/ + + The interactive user interfaces in modified source and object code versions + of this program must display Appropriate Legal Notices, as required under + Section 5 of the GNU Affero General Public License. + + In accordance with Section 7(b) of the GNU Affero General Public License, + a covered work must retain the producer line in every PDF that is created + or manipulated using iText. + + You can be released from the requirements of the license by purchasing + a commercial license. Buying such a license is mandatory as soon as you + develop commercial activities involving the iText software without + disclosing the source code of your own applications. + These activities include: offering paid services to customers as an ASP, + serving PDFs on the fly in a web application, shipping iText with a closed + source product. + + For more information, please contact iText Software Corp. at this + address: sales@itextpdf.com + */ +package com.itextpdf.rups.io; + +import javax.swing.*; + +public final class PdfOpenFlagsPanel extends JPanel { + + /** + * Panel title + */ + public static final String PANEL_LABEL = "Advanced options"; + + /** + * Label for read-only checkbox + */ + public static final String OPEN_READ_ONLY = "Open read-only"; + + /** + * Tooltip for read-only checkbox. + */ + public static final String OPEN_READ_ONLY_TOOLTIP = "If checked, the document will be opened read-only." + + "This disables modification and re-saving. If unchecked, the document will be opened in update mode. "; + + /** + * Open PDF documents in read-only mode. + */ + private final JCheckBox openReadOnly; + + /** + * Construct a flags panel with default initial states of 'true' for both checkboxes. + */ + public PdfOpenFlagsPanel() { + this(true); + } + + /** + * Construct a flags panel for use as an accessory pane in a file chooser component. + * + * @param initReadOnly + * Initial flag state. + */ + public PdfOpenFlagsPanel(boolean initReadOnly) { + super(); + this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); + this.add(new JLabel(PANEL_LABEL)); + this.openReadOnly = new JCheckBox(OPEN_READ_ONLY, initReadOnly); + this.openReadOnly.setToolTipText(OPEN_READ_ONLY_TOOLTIP); + this.add(this.openReadOnly); + } + + /** + * @return the state of the "Read-only" checkbox. + */ + public boolean getOpenReadOnly() { + return this.openReadOnly.isSelected(); + } + + +} diff --git a/src/main/java/com/itextpdf/rups/model/PdfFile.java b/src/main/java/com/itextpdf/rups/model/PdfFile.java index 6dfa6ed8..318ad863 100644 --- a/src/main/java/com/itextpdf/rups/model/PdfFile.java +++ b/src/main/java/com/itextpdf/rups/model/PdfFile.java @@ -51,6 +51,8 @@ This file is part of the iText (R) project. import com.ibm.icu.text.StringPrepParseException; import com.ibm.icu.text.StringPrep; +import com.itextpdf.kernel.pdf.StampingProperties; + import javax.swing.*; import java.io.*; import java.nio.charset.StandardCharsets; @@ -76,11 +78,6 @@ public class PdfFile { */ protected PdfDocument document = null; - /** - * The file permissions - */ - protected Permissions permissions = null; - /** * Raw content */ @@ -121,6 +118,7 @@ public PdfFile(File file) throws IOException, PdfException { */ public PdfFile(byte[] file, boolean readOnly) throws IOException, PdfException { rawContent = file; + this.readOnly = readOnly; try { readFile(new ByteArrayInputStream(file), false, readOnly); @@ -173,46 +171,26 @@ public void selectInitialValue() { * @throws IOException an I/O exception * @throws PdfException a PDF exception */ - protected void readFile(InputStream fis, boolean checkPass, boolean readOnly) throws IOException, PdfException { + protected final void readFile(InputStream fis, boolean checkPass, boolean readOnly) + throws IOException, PdfException { // reading the file into PdfReader - PdfReader reader; - PdfWriter writer; - permissions = new Permissions(); - ReaderProperties readerProps = new ReaderProperties(); - final byte[] password; + final ReaderProperties readerProps = new ReaderProperties(); if (checkPass) { - password = requestPassword(); - readerProps.setPassword(password); - } else { - password = null; + readerProps.setPassword(requestPassword()); } - reader = new PdfReader(fis, readerProps); + final PdfReader reader = new PdfReader(fis, readerProps); baos = new ByteArrayOutputStream(); if (readOnly) { document = new PdfDocument(reader); } else { - writer = new PdfWriter(baos); - document = new PdfDocument(reader, writer); + document = new PdfDocument(reader, new PdfWriter(baos), new StampingProperties().preserveEncryption()); } // we have some extra work to do if the document was encrypted - if(reader.isEncrypted()) { - permissions.setEncrypted(true); - permissions.setCryptoMode(reader.getCryptoMode()); - permissions.setPermissions((int) reader.getPermissions()); - if(password != null) { - if (reader.isOpenedWithFullPermission()) { - permissions.setOwnerPassword(password); - permissions.setUserPassword(reader.computeUserPassword()); - } else { - JOptionPane.showMessageDialog( - null, - "You opened the document using the user password instead of the owner password."); - } - } - } else { - permissions.setEncrypted(false); + if(reader.isEncrypted() && !reader.isOpenedWithFullPermission()) { + JOptionPane.showMessageDialog( + null, + "You opened the document using the user password instead of the owner password."); } - } /** @@ -254,6 +232,10 @@ public void setFilename(String filename) { this.filename = filename; } + public boolean isReadOnly() { + return readOnly; + } + public ByteArrayOutputStream getByteArrayOutputStream() { return baos; } diff --git a/src/main/java/com/itextpdf/rups/view/RupsMenuBar.java b/src/main/java/com/itextpdf/rups/view/RupsMenuBar.java index 32ea4be0..8209af7d 100644 --- a/src/main/java/com/itextpdf/rups/view/RupsMenuBar.java +++ b/src/main/java/com/itextpdf/rups/view/RupsMenuBar.java @@ -43,12 +43,14 @@ This file is part of the iText (R) project. package com.itextpdf.rups.view; import com.itextpdf.rups.controller.RupsController; +import com.itextpdf.rups.event.PostOpenDocumentEvent; import com.itextpdf.rups.event.RupsEvent; import com.itextpdf.rups.io.FileCloseAction; import com.itextpdf.rups.io.FileCompareAction; import com.itextpdf.rups.io.FileOpenAction; import com.itextpdf.rups.io.FileSaveAction; import com.itextpdf.rups.io.filters.PdfFilter; +import com.itextpdf.rups.model.ObjectLoader; import com.itextpdf.rups.model.PdfFile; import javax.swing.*; @@ -178,7 +180,12 @@ public void update(Observable observable, Object obj) { enableItems(false); break; case RupsEvent.OPEN_DOCUMENT_POST_EVENT: - enableItems(true); + ObjectLoader ol = (ObjectLoader) event.getContent(); + if(ol.getFile().isReadOnly()) { + enableReadOnlyItems(); + } else { + enableItems(true); + } break; case RupsEvent.ROOT_NODE_CLICKED_EVENT: fileOpenAction.actionPerformed(null); @@ -221,6 +228,14 @@ protected void enableItems(boolean enabled) { enableItem(NEW_INDIRECT, enabled); } + protected void enableReadOnlyItems() { + enableItem(CLOSE, true); + enableItem(SAVE_AS, false); + enableItem(OPENINVIEWER, true); + enableItem(COMPARE_WITH, true); + enableItem(NEW_INDIRECT, false); + } + /** * Enables/disables a specific menu item * diff --git a/src/main/java/com/itextpdf/rups/view/itext/PdfObjectPanel.java b/src/main/java/com/itextpdf/rups/view/itext/PdfObjectPanel.java index 18076532..1ae803bf 100644 --- a/src/main/java/com/itextpdf/rups/view/itext/PdfObjectPanel.java +++ b/src/main/java/com/itextpdf/rups/view/itext/PdfObjectPanel.java @@ -271,4 +271,8 @@ public void tableChanged(TableModelEvent e) { } } } + + public void setPluginMode(boolean pluginMode) { + this.pluginMode = pluginMode; + } } From 335e66da37c46f901795ceb85e6cd70862319693 Mon Sep 17 00:00:00 2001 From: Matthias Valvekens Date: Mon, 12 Jul 2021 14:05:10 +0200 Subject: [PATCH 2/2] Add env var switch for unethical reading RES-428 --- .../java/com/itextpdf/rups/model/PdfFile.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/itextpdf/rups/model/PdfFile.java b/src/main/java/com/itextpdf/rups/model/PdfFile.java index 318ad863..6d20a364 100644 --- a/src/main/java/com/itextpdf/rups/model/PdfFile.java +++ b/src/main/java/com/itextpdf/rups/model/PdfFile.java @@ -63,6 +63,7 @@ This file is part of the iText (R) project. */ public class PdfFile { + public static final String IGNORE_PERMS_ENV_VAR = "RUPS_IGNORE_PERMS"; /** * The directory where the file can be found (if the PDF was passed as a file). */ @@ -162,6 +163,17 @@ public void selectInitialValue() { return preparePasswordForOpen(passwordString); } + private static boolean checkIgnorePermissions() { + // set unethical reading based on environment variable + final String ignorePermsEnv; + try { + ignorePermsEnv = System.getenv(IGNORE_PERMS_ENV_VAR); + } catch (SecurityException ex) { + return false; + } + return ignorePermsEnv != null && "1".equals(ignorePermsEnv.trim()); + } + /** * Does the actual reading of the file into PdfReader and PDFFile. * @@ -178,7 +190,8 @@ protected final void readFile(InputStream fis, boolean checkPass, boolean readOn if (checkPass) { readerProps.setPassword(requestPassword()); } - final PdfReader reader = new PdfReader(fis, readerProps); + final PdfReader reader = new PdfReader(fis, readerProps) + .setUnethicalReading(checkIgnorePermissions()); baos = new ByteArrayOutputStream(); if (readOnly) { document = new PdfDocument(reader);