From 753f9806cb80c6c314d1e5e285bac012f44ffcea Mon Sep 17 00:00:00 2001 From: Tres Finocchiaro Date: Fri, 3 May 2024 11:58:08 -0400 Subject: [PATCH] Oh no, got hit with another big refactor --- src/qz/common/AboutInfo.java | 6 +- src/qz/common/TrayManager.java | 19 ++- src/qz/installer/Installer.java | 22 +-- src/qz/installer/WindowsInstaller.java | 3 +- .../provision/ProvisionInstaller.java | 26 +-- src/qz/ui/AboutDialog.java | 2 +- src/qz/utils/PrefsSearch.java | 5 +- src/qz/ws/PrintSocketServer.java | 65 ++------ src/qz/ws/WebsocketPorts.java | 157 ++++++++++++++++++ 9 files changed, 196 insertions(+), 109 deletions(-) create mode 100644 src/qz/ws/WebsocketPorts.java diff --git a/src/qz/common/AboutInfo.java b/src/qz/common/AboutInfo.java index 85086c9fc..f369c29e6 100644 --- a/src/qz/common/AboutInfo.java +++ b/src/qz/common/AboutInfo.java @@ -18,6 +18,7 @@ import qz.utils.StringUtilities; import qz.utils.SystemUtilities; import qz.ws.PrintSocketServer; +import qz.ws.WebsocketPorts; import java.io.BufferedReader; import java.io.IOException; @@ -68,6 +69,7 @@ private static JSONObject product() throws JSONException { private static JSONObject socket(CertificateManager certificateManager, String domain) throws JSONException { JSONObject socket = new JSONObject(); String sanitizeDomain = StringUtilities.escapeHtmlEntities(domain); + WebsocketPorts websocketPorts = PrintSocketServer.getWebsocketPorts(); // Gracefully handle XSS per https://github.com/qzind/tray/issues/1099 if(sanitizeDomain.contains("<") || sanitizeDomain.contains(">")) { @@ -78,9 +80,9 @@ private static JSONObject socket(CertificateManager certificateManager, String d socket .put("domain", sanitizeDomain) .put("secureProtocol", "wss") - .put("securePort", certificateManager.isSslActive() ? PrintSocketServer.getSecurePortInUse() : "none") + .put("securePort", certificateManager.isSslActive() ? websocketPorts.getSecurePort() : "none") .put("insecureProtocol", "ws") - .put("insecurePort", PrintSocketServer.getInsecurePortInUse()); + .put("insecurePort", websocketPorts.getInsecurePort()); return socket; } diff --git a/src/qz/common/TrayManager.java b/src/qz/common/TrayManager.java index a96506e5d..a377c43fe 100644 --- a/src/qz/common/TrayManager.java +++ b/src/qz/common/TrayManager.java @@ -26,6 +26,7 @@ import qz.utils.*; import qz.ws.PrintSocketServer; import qz.ws.SingleInstanceChecker; +import qz.ws.WebsocketPorts; import javax.swing.*; import java.awt.*; @@ -534,10 +535,9 @@ private void blackList(Certificate cert) { } } - public void setServer(Server server, int insecurePortInUse, int securePortInUse) { + public void setServer(Server server, WebsocketPorts websocketPorts) { if (server != null && server.getConnectors().length > 0) { - singleInstanceCheck(PrintSocketServer.insecurePorts, insecurePortInUse, false); - singleInstanceCheck(PrintSocketServer.securePorts, securePortInUse, true); + singleInstanceCheck(websocketPorts); displayInfoMessage("Server started on port(s) " + PrintSocketServer.getPorts(server)); @@ -633,11 +633,14 @@ private void displayMessage(final String caption, final String text, final TrayI } } - public void singleInstanceCheck(java.util.List ports, Integer portInUse, boolean usingSecure) { - for(int port : ports) { - if (portInUse == -1 || port != ports.get(portInUse)) { - new SingleInstanceChecker(this, port, usingSecure); - } + public void singleInstanceCheck(WebsocketPorts websocketPorts) { + // Secure + for(int port : websocketPorts.getUnusedSecurePorts()) { + new SingleInstanceChecker(this, port, true); + } + // Insecure + for(int port : websocketPorts.getUnusedInsecurePorts()) { + new SingleInstanceChecker(this, port, false); } } diff --git a/src/qz/installer/Installer.java b/src/qz/installer/Installer.java index 5587c10bb..7c085594c 100644 --- a/src/qz/installer/Installer.java +++ b/src/qz/installer/Installer.java @@ -20,6 +20,7 @@ import qz.installer.provision.ProvisionInstaller; import qz.utils.FileUtilities; import qz.utils.SystemUtilities; +import qz.ws.WebsocketPorts; import java.io.*; import java.nio.file.*; @@ -42,8 +43,7 @@ public abstract class Installer { public static boolean IS_SILENT = "1".equals(System.getenv(DATA_DIR + "_silent")); public static String JRE_LOCATION = SystemUtilities.isMac() ? "Contents/PlugIns/Java.runtime/Contents/Home" : "runtime"; - private List securePorts = Arrays.asList(DEFAULT_WSS_PORTS); - private List insecurePorts = Arrays.asList(DEFAULT_WSS_PORTS); + WebsocketPorts websocketPorts; public enum PrivilegeLevel { USER, @@ -365,7 +365,7 @@ public Installer invokeProvisioning(Phase phase) { // Special case for custom websocket ports if(phase == Phase.INSTALL) { - provisionInstaller.setCustomPorts(this); + websocketPorts = WebsocketPorts.parseFromSteps(provisionInstaller.getSteps()); } } catch(Exception e) { log.warn("An error occurred invoking provision \"phase\": \"{}\"", phase, e); @@ -385,22 +385,6 @@ public Installer removeProvisioning() { return this; } - public void setSecurePorts(List securePorts) { - this.securePorts = securePorts; - } - - public void setInsecurePorts(List insecurePorts) { - this.insecurePorts = insecurePorts; - } - - public List getSecurePorts() { - return securePorts; - } - - public List getInsecurePorts() { - return insecurePorts; - } - public static Properties persistProperties(File oldFile, Properties newProps) { if(oldFile.exists()) { Properties oldProps = new Properties(); diff --git a/src/qz/installer/WindowsInstaller.java b/src/qz/installer/WindowsInstaller.java index c027d10da..aaa4d5274 100644 --- a/src/qz/installer/WindowsInstaller.java +++ b/src/qz/installer/WindowsInstaller.java @@ -148,10 +148,9 @@ public Installer addSystemSettings() { WindowsUtilities.addNumberedRegValue(HKEY_LOCAL_MACHINE, "SOFTWARE\\Policies\\Google\\Chrome\\URLWhitelist", String.format("%s://*", DATA_DIR)); // Firewall rules - String ports = StringUtils.join(getSecurePorts(), ",") + "," + StringUtils.join(getInsecurePorts(), ","); ShellUtilities.execute("netsh.exe", "advfirewall", "firewall", "delete", "rule", String.format("name=%s", ABOUT_TITLE)); ShellUtilities.execute("netsh.exe", "advfirewall", "firewall", "add", "rule", String.format("name=%s", ABOUT_TITLE), - "dir=in", "action=allow", "profile=any", String.format("localport=%s", ports), "localip=any", "protocol=tcp"); + "dir=in", "action=allow", "profile=any", String.format("localport=%s", websocketPorts.allPortsAsString()), "localip=any", "protocol=tcp"); return this; } diff --git a/src/qz/installer/provision/ProvisionInstaller.java b/src/qz/installer/provision/ProvisionInstaller.java index 302b531d0..a2aa3aa72 100644 --- a/src/qz/installer/provision/ProvisionInstaller.java +++ b/src/qz/installer/provision/ProvisionInstaller.java @@ -136,30 +136,8 @@ private boolean invokeStep(Step step) throws Exception { return invoker.invoke(); } - /** - * Loops through steps searching for a property which sets a custom websocket ports - */ - public void setCustomPorts(Installer installer) { - for(Step step : steps) { - if(step.getType() == Type.PROPERTY) { - HashMap pairs = PropertyInvoker.parsePropertyPairs(step); - String foundPorts; - if((foundPorts = pairs.get(ArgValue.WEBSOCKET_SECURE_PORTS.getMatch())) != null) { - List securePorts = PrefsSearch.parseIntegerArray(foundPorts); - if(!securePorts.isEmpty()) { - installer.setSecurePorts(securePorts); - log.info("Picked up custom secure ports from {}: [{}]", PROVISION_FILE, StringUtils.join(securePorts, ",")); - } - } - if((foundPorts = pairs.get(ArgValue.WEBSOCKET_INSECURE_PORTS.getMatch())) != null) { - List insecurePorts = PrefsSearch.parseIntegerArray(foundPorts); - if(!insecurePorts.isEmpty()) { - installer.setSecurePorts(insecurePorts); - log.info("Picked up custom insecure ports from {}: [{}]", PROVISION_FILE, StringUtils.join(insecurePorts, ",")); - } - } - } - } + public ArrayList getSteps() { + return steps; } private static ArrayList parse(JSONArray jsonArray, Object relativeObject) throws JSONException { diff --git a/src/qz/ui/AboutDialog.java b/src/qz/ui/AboutDialog.java index df1bce22b..3ef61537a 100644 --- a/src/qz/ui/AboutDialog.java +++ b/src/qz/ui/AboutDialog.java @@ -68,7 +68,7 @@ public void initComponents() { // 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(); + int port = uri == null ? PrintSocketServer.getWebsocketPorts().getInsecurePort(): uri.getPort(); linkLibrary.setLinkLocation(String.format("%s://%s:%s", scheme, AboutInfo.getPreferredHostname(), port)); } Box versionBox = Box.createHorizontalBox(); diff --git a/src/qz/utils/PrefsSearch.java b/src/qz/utils/PrefsSearch.java index 0eb6fa1d7..3c03b6fc8 100644 --- a/src/qz/utils/PrefsSearch.java +++ b/src/qz/utils/PrefsSearch.java @@ -92,9 +92,8 @@ public static int getInt(ArgValue argValue, Properties ... propsArray) { return getInt(argValue, true, propsArray); } - public static Integer[] getIntegerArray(ArgValue argValue, Properties ... propsArray) { - List parsed = parseIntegerArray(getString(argValue, propsArray)); - return parsed.toArray(new Integer[parsed.size()]); + public static List getIntegerArray(ArgValue argValue, Properties ... propsArray) { + return parseIntegerArray(getString(argValue, propsArray)); } public static List parseIntegerArray(String commaSeparated) { diff --git a/src/qz/ws/PrintSocketServer.java b/src/qz/ws/PrintSocketServer.java index be5d4294c..71cf2be81 100644 --- a/src/qz/ws/PrintSocketServer.java +++ b/src/qz/ws/PrintSocketServer.java @@ -20,7 +20,6 @@ import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer; import org.eclipse.jetty.websocket.servlet.WebSocketUpgradeFilter; import qz.App; -import qz.common.Constants; import qz.common.TrayManager; import qz.installer.certificate.CertificateManager; import qz.utils.ArgValue; @@ -33,7 +32,6 @@ import java.time.Duration; import java.util.*; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; /** * Created by robert on 9/9/2014. @@ -44,13 +42,9 @@ public class PrintSocketServer { private static final Logger log = LogManager.getLogger(PrintSocketServer.class); private static final int MAX_MESSAGE_SIZE = Integer.MAX_VALUE; - public static List securePorts; - public static List insecurePorts; - - private static final AtomicInteger securePortIndex = new AtomicInteger(0); - private static final AtomicInteger insecurePortIndex = new AtomicInteger(0); private static final AtomicBoolean running = new AtomicBoolean(false); + private static WebsocketPorts websocketPorts; private static TrayManager trayManager; private static Server server; private static boolean httpsOnly; @@ -65,7 +59,7 @@ public static void runServer(CertificateManager certManager, boolean headless) t wssHost = PrefsSearch.getString(ArgValue.SECURITY_WSS_HOST, certManager.getProperties()); httpsOnly = PrefsSearch.getBoolean(ArgValue.SECURITY_WSS_HTTPSONLY, certManager.getProperties()); sniStrict = PrefsSearch.getBoolean(ArgValue.SECURITY_WSS_SNISTRICT, certManager.getProperties()); - parseWebSocketPorts(); + websocketPorts = WebsocketPorts.parseFromProperties(); server = findAvailableSecurePort(certManager); @@ -79,10 +73,10 @@ public static void runServer(CertificateManager certManager, boolean headless) t return; } - while(!running.get() && insecurePortIndex.get() < insecurePorts.size()) { + while(!running.get() && websocketPorts.insecureBoundsCheck()) { try { ServerConnector connector = new ServerConnector(server); - connector.setPort(getInsecurePortInUse()); + connector.setPort(websocketPorts.getInsecurePort()); if(httpsOnly) { server.setConnectors(new Connector[] {secureConnector}); } else if (secureConnector != null) { @@ -117,8 +111,7 @@ public static void runServer(CertificateManager certManager, boolean headless) t try { trayManager.setDangerIcon(); running.set(false); - securePortIndex.set(0); - insecurePortIndex.set(0); + websocketPorts.resetIndices(); server.stop(); } catch(Exception e) { @@ -130,15 +123,15 @@ public static void runServer(CertificateManager certManager, boolean headless) t running.set(true); log.info("Server started on port(s) " + getPorts(server)); - int insecurePort = httpsOnly ? -1 : insecurePortIndex.get(); - int securePort = secureConnector == null ? -1 : securePortIndex.get(); - trayManager.setServer(server, insecurePort, securePort); + websocketPorts.setHttpsOnly(httpsOnly); + websocketPorts.setHttpOnly(secureConnector == null); + trayManager.setServer(server, websocketPorts); server.join(); } catch(IOException | MultiException e) { //order of getConnectors is the order we added them -> insecure first if (server.isFailed()) { - insecurePortIndex.incrementAndGet(); + websocketPorts.nextInsecureIndex(); } //explicitly stop the server, because if only 1 port has an exception the other will still be opened @@ -157,7 +150,7 @@ private static Server findAvailableSecurePort(CertificateManager certManager) { if (certManager != null) { final AtomicBoolean runningSecure = new AtomicBoolean(false); - while(!runningSecure.get() && securePortIndex.get() < securePorts.size()) { + while(!runningSecure.get() && websocketPorts.secureBoundsCheck()) { try { // Bind the secure socket on the proper port number (i.e. 8181), add it as an additional connector SslConnectionFactory sslConnection = new SslConnectionFactory(certManager.configureSslContextFactory(), HttpVersion.HTTP_1_1.asString()); @@ -172,11 +165,11 @@ private static Server findAvailableSecurePort(CertificateManager certManager) { ServerConnector secureConnector = new ServerConnector(server, sslConnection, httpConnection); secureConnector.setHost(wssHost); - secureConnector.setPort(getSecurePortInUse()); + secureConnector.setPort(websocketPorts.getSecurePort()); server.setConnectors(new Connector[] {secureConnector}); server.start(); - log.trace("Established secure WebSocket on port {}", getSecurePortInUse()); + log.trace("Established secure WebSocket on port {}", websocketPorts.getSecurePort()); //only starting to test port availability; insecure port will actually start server.stop(); @@ -184,7 +177,7 @@ private static Server findAvailableSecurePort(CertificateManager certManager) { } catch(IOException | MultiException e) { if (server.isFailed()) { - securePortIndex.incrementAndGet(); + websocketPorts.nextSecureIndex(); } try { server.stop(); }catch(Exception stopEx) { stopEx.printStackTrace(); } @@ -225,37 +218,9 @@ public static void main(String ... args) { App.main(args); } - /** - * Parses WebSocket ports from preferences or fallback to defaults is a problem is found - */ - public static void parseWebSocketPorts() { - Integer[] secure = PrefsSearch.getIntegerArray(ArgValue.WEBSOCKET_SECURE_PORTS); - Integer[] insecure = PrefsSearch.getIntegerArray(ArgValue.WEBSOCKET_INSECURE_PORTS); - boolean fallback = false; - if(secure.length == 0 || insecure.length == 0) { - log.warn("One or more WebSocket ports is empty, falling back to defaults"); - fallback = true; - } - if(secure.length != insecure.length) { - log.warn("Secure ({}) and insecure ({}) WebSocket port counts mismatch, falling back to defaults", secure, insecure); - fallback = true; - } - if(fallback) { - log.warn("Falling back to default WebSocket ports: ({}), ({})", secure, insecure); - secure = Constants.DEFAULT_WSS_PORTS; - insecure = Constants.DEFAULT_WS_PORTS; - } - - securePorts = Collections.unmodifiableList(Arrays.asList(secure)); - insecurePorts = Collections.unmodifiableList(Arrays.asList(insecure)); - } - - public static int getSecurePortInUse() { - return securePorts.get(securePortIndex.get()); - } - public static int getInsecurePortInUse() { - return insecurePorts.get(insecurePortIndex.get()); + public static WebsocketPorts getWebsocketPorts() { + return websocketPorts; } /** diff --git a/src/qz/ws/WebsocketPorts.java b/src/qz/ws/WebsocketPorts.java new file mode 100644 index 000000000..2e0433ba5 --- /dev/null +++ b/src/qz/ws/WebsocketPorts.java @@ -0,0 +1,157 @@ +package qz.ws; + +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import qz.build.provision.Step; +import qz.build.provision.params.Type; +import qz.common.Constants; +import qz.installer.provision.invoker.PropertyInvoker; +import qz.utils.ArgValue; +import qz.utils.PrefsSearch; + +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; + +import static qz.common.Constants.PROVISION_FILE; + +public class WebsocketPorts { + private static final Logger log = LogManager.getLogger(WebsocketPorts.class); + private List securePorts; + private List insecurePorts; + + public List getUnusedSecurePorts() { + List unused = new ArrayList<>(securePorts); + unused.remove(securePortIndex.get()); + return unused; + } + + public List getUnusedInsecurePorts() { + List unused = new ArrayList<>(insecurePorts); + unused.remove(insecurePortIndex.get()); + return unused; + } + + public void setHttpsOnly(boolean httpsOnly) { + if(httpsOnly) { + insecurePortIndex.set(-1); + } + } + + public void setHttpOnly(boolean httpOnly) { + if(httpOnly) { + securePortIndex.set(-1); + } + } + + private static final AtomicInteger securePortIndex = new AtomicInteger(0); + private static final AtomicInteger insecurePortIndex = new AtomicInteger(0); + + private WebsocketPorts(List securePorts, List insecurePorts) { + this.securePorts = securePorts; + this.insecurePorts = insecurePorts; + } + + public int getSecurePort() { + return securePorts.get(securePortIndex.get()); + } + + public int getInsecurePort() { + return insecurePorts.get(insecurePortIndex.get()); + } + + public int getSecureIndex() { + return securePortIndex.get(); + } + + public int getInsecureIndex() { + return insecurePortIndex.get(); + } + + public int nextSecureIndex() { + return securePortIndex.incrementAndGet(); + } + + public int nextInsecureIndex() { + return insecurePortIndex.incrementAndGet(); + } + + public void resetIndices() { + securePortIndex.set(0); + insecurePortIndex.set(0); + } + + public boolean secureBoundsCheck() { + return securePortIndex.get() < securePorts.size(); + } + + public boolean insecureBoundsCheck() { + return insecurePortIndex.get() < insecurePorts.size(); + } + + /** + * Parses WebSocket ports from preferences or fallback to defaults is a problem is found + */ + public static WebsocketPorts parseFromProperties() { + return fromList(PrefsSearch.getIntegerArray(ArgValue.WEBSOCKET_SECURE_PORTS), + PrefsSearch.getIntegerArray(ArgValue.WEBSOCKET_INSECURE_PORTS)); + } + + /** + * Loops through steps searching for a property which sets a custom websocket ports + */ + public static WebsocketPorts parseFromSteps(List steps) { + List secure = new ArrayList<>(); + List insecure = new ArrayList<>(); + for(Step step : steps) { + if(step.getType() == Type.PROPERTY) { + HashMap pairs = PropertyInvoker.parsePropertyPairs(step); + String foundPorts; + if((foundPorts = pairs.get(ArgValue.WEBSOCKET_SECURE_PORTS.getMatch())) != null) { + secure = PrefsSearch.parseIntegerArray(foundPorts); + if(!secure.isEmpty()) { + log.info("Picked up custom secure ports from {}: [{}]", PROVISION_FILE, StringUtils.join(secure, ",")); + } + } + if((foundPorts = pairs.get(ArgValue.WEBSOCKET_INSECURE_PORTS.getMatch())) != null) { + insecure = PrefsSearch.parseIntegerArray(foundPorts); + if(!insecure.isEmpty()) { + log.info("Picked up custom insecure ports from {}: [{}]", PROVISION_FILE, StringUtils.join(insecure, ",")); + } + } + } + } + return fromList(secure, insecure); + } + + /** + * Constructs a new instance of WebsocketPorts with the specified secure and + * insecure port ranges, falling back to Constants.DEFAULT_WSS_PORTS, + * Constants.DEFAULT_WS_PORTS if a problem occurred. + * + * @param secure Port listing with at least one element. Size must match insecure + * @param insecure Port listing with at least one element. Size must match secure + */ + public static WebsocketPorts fromList(List secure, List insecure) { + boolean fallback = false; + if(secure.isEmpty() || insecure.isEmpty()) { + log.warn("One or more WebSocket ports is empty, falling back to defaults"); + fallback = true; + } + if(secure.size() != insecure.size()) { + log.warn("Secure ({}) and insecure ({}) WebSocket port counts mismatch, falling back to defaults", secure, insecure); + fallback = true; + } + if(fallback) { + log.warn("Falling back to default WebSocket ports: ({}), ({})", secure, insecure); + secure = Arrays.asList(Constants.DEFAULT_WSS_PORTS); + insecure = Arrays.asList(Constants.DEFAULT_WS_PORTS); + } + + return new WebsocketPorts(Collections.unmodifiableList(secure), Collections.unmodifiableList(insecure)); + } + + public String allPortsAsString() { + return StringUtils.join(securePorts, ",") + "," + StringUtils.join(insecurePorts, ","); + } +}