Skip to content

Commit

Permalink
Allow JSON data substitutions client-side (#1258)
Browse files Browse the repository at this point in the history
Add support for JSON substitutions
---------

Co-authored-by: Vzor- <[email protected]>
  • Loading branch information
tresf and Vzor- authored May 2, 2024
1 parent 6e0adba commit 2ab63ba
Show file tree
Hide file tree
Showing 13 changed files with 813 additions and 40 deletions.
3 changes: 3 additions & 0 deletions src/qz/App.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import qz.utils.*;
import qz.ws.PrintSocketServer;
import qz.ws.SingleInstanceChecker;
import qz.ws.substitutions.Substitutions;

import java.io.File;
import java.security.cert.X509Certificate;
Expand All @@ -43,6 +44,8 @@ public static void main(String ... args) {
log.info(Constants.ABOUT_TITLE + " vendor: {}", Constants.ABOUT_COMPANY);
log.info("Java version: {}", Constants.JAVA_VERSION.toString());
log.info("Java vendor: {}", Constants.JAVA_VENDOR);
Substitutions.setEnabled(PrefsSearch.getBoolean(ArgValue.SECURITY_SUBSTITUTIONS_ENABLE));
Substitutions.setStrict(PrefsSearch.getBoolean(ArgValue.SECURITY_SUBSTITUTIONS_STRICT));

CertificateManager certManager = null;
try {
Expand Down
4 changes: 4 additions & 0 deletions src/qz/common/TrayManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import qz.utils.*;
import qz.ws.PrintSocketServer;
import qz.ws.SingleInstanceChecker;
import qz.ws.substitutions.Substitutions;

import javax.swing.*;
import java.awt.*;
Expand Down Expand Up @@ -98,6 +99,9 @@ public TrayManager(boolean isHeadless) {
// Set strict certificate mode preference
Certificate.setTrustBuiltIn(!getPref(TRAY_STRICTMODE));

// Configures JSON websocket messages
Substitutions.getInstance();

// Set FileIO security
FileUtilities.setFileIoEnabled(getPref(SECURITY_FILE_ENABLED));
FileUtilities.setFileIoStrict(getPref(SECURITY_FILE_STRICT));
Expand Down
211 changes: 172 additions & 39 deletions src/qz/ui/AboutDialog.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,27 @@
import qz.ui.component.EmLabel;
import qz.ui.component.IconCache;
import qz.ui.component.LinkLabel;
import qz.utils.FileUtilities;
import qz.utils.SystemUtilities;
import qz.ws.PrintSocketServer;
import qz.ws.substitutions.Substitutions;

import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.font.TextAttribute;
import java.awt.datatransfer.DataFlavor;
import java.awt.dnd.*;
import java.io.File;
import java.net.URI;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicBoolean;

/**
* Created by Tres on 2/26/2015.
Expand All @@ -29,16 +38,17 @@
public class AboutDialog extends BasicDialog implements Themeable {

private static final Logger log = LogManager.getLogger(AboutDialog.class);

private final boolean limitedDisplay;
private Server server;

private boolean limitedDisplay;

private JLabel lblUpdate;
private JButton updateButton;

private JPanel contentPanel;
private JToolBar headerBar;
private Border dropBorder;

// Use <html> allows word wrapping on a standard JLabel
class TextWrapLabel extends JLabel {
static class TextWrapLabel extends JLabel {
TextWrapLabel(String text) {
super("<html>" + text + "</html>");
}
Expand All @@ -63,18 +73,10 @@ public void initComponents() {
JPanel infoPanel = new JPanel();
infoPanel.setLayout(new BoxLayout(infoPanel, BoxLayout.Y_AXIS));

LinkLabel linkLibrary = new LinkLabel("Detailed library information");
if(server != null && server.isRunning() && !server.isStopping()) {
// Some OSs (e.g. FreeBSD) return null for server.getURI(), fallback to sane values
URI uri = server.getURI();
String scheme = uri == null ? "http" : uri.getScheme();
int port = uri == null ? PrintSocketServer.getInsecurePortInUse(): uri.getPort();
linkLibrary.setLinkLocation(String.format("%s://%s:%s", scheme, AboutInfo.getPreferredHostname(), port));
}
LinkLabel linkLibrary = getLinkLibrary();
Box versionBox = Box.createHorizontalBox();
versionBox.setAlignmentX(Component.LEFT_ALIGNMENT);
versionBox.add(new JLabel(String.format("%s (Java)", Constants.VERSION.toString())));

versionBox.add(new JLabel(String.format("%s (Java)", Constants.VERSION)));

JPanel aboutPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
JLabel logo = new JLabel(getIcon(IconCache.Icon.LOGO_ICON));
Expand Down Expand Up @@ -126,43 +128,133 @@ public void initComponents() {

aboutPanel.add(infoPanel);

JPanel panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
panel.add(aboutPanel);
panel.add(new JSeparator());
contentPanel = new JPanel();
contentPanel.setLayout(new BoxLayout(contentPanel, BoxLayout.PAGE_AXIS));
contentPanel.add(aboutPanel);
contentPanel.add(new JSeparator());

if (!limitedDisplay) {
LinkLabel lblLicensing = new LinkLabel("Licensing Information", 0.9f, false);
lblLicensing.setLinkLocation(Constants.ABOUT_LICENSING_URL);
contentPanel.add(getSupportPanel());
}

LinkLabel lblSupport = new LinkLabel("Support Information", 0.9f, false);
lblSupport.setLinkLocation(Constants.ABOUT_SUPPORT_URL);
setContent(contentPanel, true);
contentPanel.setDropTarget(createDropTarget());
setHeader(headerBar = getHeaderBar());
refreshHeader();
}

private static JPanel getSupportPanel() {
LinkLabel lblLicensing = new LinkLabel("Licensing Information", 0.9f, false);
lblLicensing.setLinkLocation(Constants.ABOUT_LICENSING_URL);

LinkLabel lblPrivacy = new LinkLabel("Privacy Policy", 0.9f, false);
lblPrivacy.setLinkLocation(Constants.ABOUT_PRIVACY_URL);
LinkLabel lblSupport = new LinkLabel("Support Information", 0.9f, false);
lblSupport.setLinkLocation(Constants.ABOUT_SUPPORT_URL);

JPanel supportPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 80, 10));
supportPanel.add(lblLicensing);
supportPanel.add(lblSupport);
supportPanel.add(lblPrivacy);
LinkLabel lblPrivacy = new LinkLabel("Privacy Policy", 0.9f, false);
lblPrivacy.setLinkLocation(Constants.ABOUT_PRIVACY_URL);

panel.add(supportPanel);
JPanel supportPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 80, 10));
supportPanel.add(lblLicensing);
supportPanel.add(lblSupport);
supportPanel.add(lblPrivacy);
return supportPanel;
}

private LinkLabel getLinkLibrary() {
LinkLabel linkLibrary = new LinkLabel("Detailed library information");
if(server != null && server.isRunning() && !server.isStopping()) {
// Some OSs (e.g. FreeBSD) return null for server.getURI(), fallback to sane values
URI uri = server.getURI();
String scheme = uri == null ? "http" : uri.getScheme();
int port = uri == null ? PrintSocketServer.getInsecurePortInUse(): uri.getPort();
linkLibrary.setLinkLocation(String.format("%s://%s:%s", scheme, AboutInfo.getPreferredHostname(), port));
}
return linkLibrary;
}

private JToolBar getHeaderBar() {
JToolBar headerBar = new JToolBar();
headerBar.setBorderPainted(false);
headerBar.setLayout(new FlowLayout());
headerBar.setOpaque(true);
headerBar.setFloatable(false);

LinkLabel substitutionsLabel = new LinkLabel("Substitutions are in effect for this machine");
JButton refreshButton = new JButton("", getIcon(IconCache.Icon.RELOAD_ICON));
refreshButton.setOpaque(false);
refreshButton.addActionListener(e -> {
Substitutions.getInstance(true);
refreshHeader();
});

substitutionsLabel.setLinkLocation(FileUtilities.SHARED_DIR.toFile());

headerBar.add(substitutionsLabel);
headerBar.add(refreshButton);
return headerBar;
}

setContent(panel, true);
private DropTarget createDropTarget() {
return new DropTarget() {
public synchronized void drop(DropTargetDropEvent evt) {
processDroppedFile(evt);
}

@Override
public synchronized void dragEnter(DropTargetDragEvent dtde) {
super.dragEnter(dtde);
setDropBorder(true);
}

@Override
public synchronized void dragExit(DropTargetEvent dte) {
super.dragExit(dte);
setDropBorder(false);
}
};
}

private void processDroppedFile(DropTargetDropEvent evt) {
try {
evt.acceptDrop(DnDConstants.ACTION_COPY);
Object dropped = evt.getTransferable().getTransferData(DataFlavor.javaFileListFlavor);
if(dropped instanceof List) {
List<File> droppedFiles = (List<File>)dropped;
for (File file : droppedFiles) {
if(file.getName().equals(Substitutions.FILE_NAME)) {
blinkDropBorder(true);
log.info("File drop accepted: {}", file);
Path source = file.toPath();
Path dest = FileUtilities.SHARED_DIR.resolve(file.getName());
Files.copy(source, dest, StandardCopyOption.REPLACE_EXISTING);
FileUtilities.inheritParentPermissions(dest);
Substitutions.getInstance(true);
refreshHeader();
break;
} else {
blinkDropBorder(false);
break;
}
}
}
evt.dropComplete(true);
} catch (Exception ex) {
log.warn(ex);
}
setDropBorder(false);
}

private void checkForUpdate() {
Version latestVersion = AboutInfo.findLatestVersion();
if (latestVersion.greaterThan(Constants.VERSION)) {
lblUpdate.setText("An update is available:");

updateButton.setText("Download " + latestVersion.toString());
updateButton.setText("Download " + latestVersion);
updateButton.setVisible(true);
} else if (latestVersion.lessThan(Constants.VERSION)) {
lblUpdate.setText("You are on a beta release.");

updateButton.setText("Revert to stable " + latestVersion.toString());
updateButton.setText("Revert to stable " + latestVersion);
updateButton.setVisible(true);
} else {
lblUpdate.setText("You have the latest version.");
Expand All @@ -171,6 +263,43 @@ private void checkForUpdate() {
}
}

private void setDropBorder(boolean isShown) {
if(isShown) {
if(contentPanel.getBorder() == null) {
dropBorder = BorderFactory.createDashedBorder(Constants.TRUSTED_COLOR, 3, 5, 5, true);
contentPanel.setBorder(dropBorder);
}
} else {
contentPanel.setBorder(null);
}
}

private void blinkDropBorder(boolean success) {
Color borderColor = success ? Color.GREEN : Constants.WARNING_COLOR;
dropBorder = BorderFactory.createDashedBorder(borderColor, 3, 5, 5, true);
AtomicBoolean toggled = new AtomicBoolean(true);
int blinkCount = 3;
int blinkDelay = 100; // ms
for(int i = 0; i < blinkCount * 2; i++) {
Timer timer = new Timer("blink" + i);
timer.schedule(new TimerTask() {
@Override
public void run() {
SwingUtilities.invokeLater(() -> {
contentPanel.setBorder(toggled.getAndSet(!toggled.get())? dropBorder:null);
});
}
}, i * blinkDelay);
}
}

private void refreshHeader() {
headerBar.setBackground(SystemUtilities.isDarkDesktop() ?
Constants.TRUSTED_COLOR.darker().darker() : Constants.TRUSTED_COLOR_DARK);
headerBar.setVisible(Substitutions.areActive());
pack();
}


@Override
public void setVisible(boolean visible) {
Expand All @@ -181,5 +310,9 @@ public void setVisible(boolean visible) {
super.setVisible(visible);
}


@Override
public void refresh() {
refreshHeader();
super.refresh();
}
}
2 changes: 1 addition & 1 deletion src/qz/utils/ArgParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public int getCode() {

private static final String USAGE_COMMAND = String.format("java -jar %s.jar", PROPS_FILE);
private static final String USAGE_COMMAND_PARAMETER = String.format("java -Dfoo.bar=<value> -jar %s.jar", PROPS_FILE);
private static final int DESCRIPTION_COLUMN = 30;
private static final int DESCRIPTION_COLUMN = 35;
private static final int INDENT_SIZE = 2;

private List<String> args;
Expand Down
7 changes: 7 additions & 0 deletions src/qz/utils/ArgValue.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package qz.utils;

import qz.common.Constants;
import qz.ws.substitutions.Substitutions;

import java.util.ArrayList;
import java.util.Arrays;
Expand Down Expand Up @@ -71,6 +72,12 @@ public enum ArgValue {
"security.file.enabled"),
SECURITY_FILE_STRICT(PREFERENCES, "Enable/disable signing requirements for File Communications features", null, true,
"security.file.strict"),

SECURITY_SUBSTITUTIONS_ENABLE(PREFERENCES, "Enable/disable client-side JSON data substitutions via \"" + Substitutions.FILE_NAME + "\" file", null, true,
"security.substitutions.enable"),
SECURITY_SUBSTITUTIONS_STRICT(PREFERENCES, "Enable/disable restrictions for materially changing JSON substitutions such as \"copies\":, \"data\": { \"data\": ... } blobs", null, true,
"security.substitutions.strict"),

SECURITY_DATA_PROTOCOLS(PREFERENCES, "URL protocols allowed for print, serial, hid, etc", null, "http,https",
"security.data.protocols"),
SECURITY_PRINT_TOFILE(PREFERENCES, "Enable/disable printing directly to file paths", null, false,
Expand Down
19 changes: 19 additions & 0 deletions src/qz/utils/ByteUtilities.java
Original file line number Diff line number Diff line change
Expand Up @@ -272,4 +272,23 @@ public static int[] unwind(int bitwiseCode) {
return matches;
}

public static boolean numberEquals(Object val1, Object val2) {
try {
if(val1 == null || val2 == null) {
return val1 == val2;
} else if(val1.getClass() == val2.getClass()) {
return val1.equals(val2);
} else if(val1 instanceof Long) {
return val1.equals(Long.parseLong(val2.toString()));
} else if(val2 instanceof Long) {
return val2.equals(Long.parseLong(val1.toString()));
} else {
return Double.parseDouble(val1.toString()) == Double.parseDouble(val2.toString());
}
} catch(NumberFormatException nfe) {
log.warn("Cannot not compare [{} = '{}']. Reason: {} {}", val1, val2, nfe.getClass().getName(), nfe.getMessage());
}
return false;
}

}
Loading

0 comments on commit 2ab63ba

Please sign in to comment.