Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow JSON data substitutions client-side #1258

Merged
merged 17 commits into from
May 2, 2024
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
Loading