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/Constants.java b/src/qz/common/Constants.java index ad8f7174f..60ab1730a 100644 --- a/src/qz/common/Constants.java +++ b/src/qz/common/Constants.java @@ -1,7 +1,6 @@ package qz.common; import com.github.zafarkhaja.semver.Version; -import qz.utils.ArgValue; import qz.utils.SystemUtilities; import java.awt.*; @@ -98,7 +97,7 @@ public class Constants { public static final String PDF_PRINT = ABOUT_TITLE + " PDF Print"; public static final String HTML_PRINT = ABOUT_TITLE + " HTML Print"; - public static final Integer[] WSS_PORTS = {8181, 8282, 8383, 8484}; - public static final Integer[] WS_PORTS = {8182, 8283, 8384, 8485}; + public static final Integer[] DEFAULT_WSS_PORTS = {8181, 8282, 8383, 8484}; + public static final Integer[] DEFAULT_WS_PORTS = {8182, 8283, 8384, 8485}; public static final Integer[] CUPS_RSS_PORTS = {8586, 8687, 8788, 8889}; } diff --git a/src/qz/common/PropertyHelper.java b/src/qz/common/PropertyHelper.java index aa3fc6f9e..7ba83e3aa 100644 --- a/src/qz/common/PropertyHelper.java +++ b/src/qz/common/PropertyHelper.java @@ -8,6 +8,7 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; +import java.util.Map; import java.util.Properties; /** * Created by Tres on 12/16/2015. @@ -92,4 +93,8 @@ public boolean save() { } return success; } + + public synchronized Object setProperty(Map.Entry pair) { + return super.setProperty(pair.getKey(), pair.getValue()); + } } diff --git a/src/qz/common/TrayManager.java b/src/qz/common/TrayManager.java index ba1ac2b53..6adf180e4 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 qz.ws.substitutions.Substitutions; import javax.swing.*; @@ -538,10 +539,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.INSECURE_PORTS, insecurePortInUse, false); - singleInstanceCheck(PrintSocketServer.SECURE_PORTS, securePortInUse, true); + singleInstanceCheck(websocketPorts); displayInfoMessage("Server started on port(s) " + PrintSocketServer.getPorts(server)); @@ -637,11 +637,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 1c302769d..041f93360 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,6 +43,8 @@ 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"; + WebsocketPorts websocketPorts; + public enum PrivilegeLevel { USER, SYSTEM @@ -103,8 +106,8 @@ public static void install() throws Exception { .addSharedDirectory() .addAppLauncher() .addStartupEntry() - .addSystemSettings() - .invokeProvisioning(Phase.INSTALL); + .invokeProvisioning(Phase.INSTALL) + .addSystemSettings(); } public static void uninstall() { @@ -362,8 +365,13 @@ public Installer invokeProvisioning(Phase phase) { Paths.get(getDestination()).resolve(PROVISION_DIR); ProvisionInstaller provisionInstaller = new ProvisionInstaller(provisionPath); provisionInstaller.invoke(phase); + + // Special case for custom websocket ports + if(phase == Phase.INSTALL) { + websocketPorts = WebsocketPorts.parseFromSteps(provisionInstaller.getSteps()); + } } catch(Exception e) { - log.warn("An error occurred deleting provisioning directory \"phase\": \"{}\" entries", phase, e); + log.warn("An error occurred invoking provision \"phase\": \"{}\"", phase, e); } return this; } diff --git a/src/qz/installer/WindowsInstaller.java b/src/qz/installer/WindowsInstaller.java index 87645882a..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(PrintSocketServer.SECURE_PORTS, ",") + "," + StringUtils.join(PrintSocketServer.INSECURE_PORTS, ","); 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 0996b594a..a2aa3aa72 100644 --- a/src/qz/installer/provision/ProvisionInstaller.java +++ b/src/qz/installer/provision/ProvisionInstaller.java @@ -2,6 +2,7 @@ import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.codehaus.jettison.json.JSONArray; @@ -10,10 +11,14 @@ import qz.build.provision.Step; import qz.build.provision.params.Os; import qz.build.provision.params.Phase; +import qz.build.provision.params.Type; import qz.build.provision.params.types.Script; import qz.build.provision.params.types.Software; import qz.common.Constants; +import qz.installer.Installer; import qz.installer.provision.invoker.*; +import qz.utils.ArgValue; +import qz.utils.PrefsSearch; import qz.utils.ShellUtilities; import qz.utils.SystemUtilities; @@ -23,6 +28,8 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; import static qz.common.Constants.*; import static qz.utils.FileUtilities.*; @@ -129,6 +136,10 @@ private boolean invokeStep(Step step) throws Exception { return invoker.invoke(); } + public ArrayList getSteps() { + return steps; + } + private static ArrayList parse(JSONArray jsonArray, Object relativeObject) throws JSONException { ArrayList steps = new ArrayList<>(); for(int i = 0; i < jsonArray.length(); i++) { diff --git a/src/qz/installer/provision/invoker/PropertyInvoker.java b/src/qz/installer/provision/invoker/PropertyInvoker.java index 75691bdfd..b58d641ad 100644 --- a/src/qz/installer/provision/invoker/PropertyInvoker.java +++ b/src/qz/installer/provision/invoker/PropertyInvoker.java @@ -8,7 +8,8 @@ import java.io.File; import java.util.AbstractMap; -import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; public class PropertyInvoker implements Invokable { private Step step; @@ -20,27 +21,16 @@ public PropertyInvoker(Step step, PropertyHelper properties) { } public boolean invoke() { - if(step.getData() != null && !step.getData().trim().isEmpty()) { - String[] props = step.getData().split("\\|"); - ArrayList> pairs = new ArrayList<>(); - for(String prop : props) { - AbstractMap.SimpleEntry pair = parsePropertyPair(step, prop); - if (pair != null) { - pairs.add(pair); - } + HashMap pairs = parsePropertyPairs(step); + if (!pairs.isEmpty()) { + for(Map.Entry pair : pairs.entrySet()) { + properties.setProperty(pair); } - if (!pairs.isEmpty()) { - for(AbstractMap.SimpleEntry pair : pairs) { - properties.setProperty(pair.getKey(), pair.getValue()); - } - if (properties.save()) { - log.info("Successfully provisioned '{}' '{}'", pairs.size(), step.getType()); - return true; - } - log.error("An error occurred saving properties '{}' to file", step.getData()); + if (properties.save()) { + log.info("Successfully provisioned '{}' '{}'", pairs.size(), step.getType()); + return true; } - } else { - log.error("Skipping Step '{}', Data is null or empty", step.getType()); + log.error("An error occurred saving properties '{}' to file", step.getData()); } return false; } @@ -63,6 +53,26 @@ public static PropertyHelper getPreferences(Step step) { return new PropertyHelper(FileUtilities.USER_DIR + File.separator + Constants.PREFS_FILE + ".properties"); } + public static HashMap parsePropertyPairs(Step step) { + HashMap pairs = new HashMap<>(); + if(step.getData() != null && !step.getData().trim().isEmpty()) { + String[] props = step.getData().split("\\|"); + for(String prop : props) { + AbstractMap.SimpleEntry pair = parsePropertyPair(step, prop); + if (pair != null) { + if(pairs.get(pair.getKey()) != null) { + log.warn("Property {} already exists, replacing [before: {}, after: {}] ", + pair.getKey(), pairs.get(pair.getKey()), pair.getValue()); + } + pairs.put(pair.getKey(), pair.getValue()); + } + } + } else { + log.error("Skipping Step '{}', Data is null or empty", step.getType()); + } + return pairs; + } + private static AbstractMap.SimpleEntry parsePropertyPair(Step step, String prop) { if(prop.contains("=")) { diff --git a/src/qz/ui/AboutDialog.java b/src/qz/ui/AboutDialog.java index e9ea8d696..eb5410fef 100644 --- a/src/qz/ui/AboutDialog.java +++ b/src/qz/ui/AboutDialog.java @@ -166,7 +166,7 @@ private LinkLabel getLinkLibrary() { // 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)); } return linkLibrary; diff --git a/src/qz/utils/ArgValue.java b/src/qz/utils/ArgValue.java index 818a4e11d..c26bb8bae 100644 --- a/src/qz/utils/ArgValue.java +++ b/src/qz/utils/ArgValue.java @@ -1,5 +1,6 @@ package qz.utils; +import org.apache.commons.lang3.StringUtils; import qz.common.Constants; import qz.ws.substitutions.Substitutions; @@ -88,6 +89,10 @@ public enum ArgValue { "security.wss.httpsonly"), SECURITY_WSS_HOST(PREFERENCES, "Influences which physical adapter to bind to by setting the host parameter for http/websocket listening", null, "0.0.0.0", "security.wss.host"), + WEBSOCKET_SECURE_PORTS(PREFERENCES, "Comma separated list of secure websocket (wss://) ports to use", null, StringUtils.join(Constants.DEFAULT_WSS_PORTS, ","), + "websocket.secure.ports"), + WEBSOCKET_INSECURE_PORTS(PREFERENCES, "Comma separated list of insecure websocket (ws://) ports to use", null, StringUtils.join(Constants.DEFAULT_WS_PORTS, ","), + "websocket.insecure.ports"), LOG_DISABLE(PREFERENCES, "Disable/enable logging features", null, false, "log.disable"), LOG_ROTATE(PREFERENCES, "Number of log files to retain when the size fills up", null, 5, diff --git a/src/qz/utils/PrefsSearch.java b/src/qz/utils/PrefsSearch.java index 21913ae38..3c03b6fc8 100644 --- a/src/qz/utils/PrefsSearch.java +++ b/src/qz/utils/PrefsSearch.java @@ -5,6 +5,8 @@ import qz.installer.certificate.CertificateManager; import qz.installer.certificate.KeyPairWrapper; +import java.util.ArrayList; +import java.util.List; import java.util.Properties; import static qz.installer.certificate.KeyPairWrapper.Type.CA; @@ -90,6 +92,25 @@ public static int getInt(ArgValue argValue, Properties ... propsArray) { return getInt(argValue, true, propsArray); } + public static List getIntegerArray(ArgValue argValue, Properties ... propsArray) { + return parseIntegerArray(getString(argValue, propsArray)); + } + + public static List parseIntegerArray(String commaSeparated) { + List parsed = new ArrayList<>(); + try { + if (commaSeparated != null && !commaSeparated.isEmpty()) { + String[] split = commaSeparated.split(","); + for(String item : split) { + parsed.add(Integer.parseInt(item)); + } + } + } catch(NumberFormatException nfe) { + log.warn("Failed parsing {} as a valid integer array", commaSeparated, nfe); + } + return parsed; + } + public static boolean getBoolean(ArgValue argValue, Properties ... propsArray) { return getBoolean(argValue, true, propsArray); } diff --git a/src/qz/ws/PrintSocketServer.java b/src/qz/ws/PrintSocketServer.java index c45c76cd3..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 final List SECURE_PORTS = Collections.unmodifiableList(Arrays.asList(Constants.WSS_PORTS)); - public static final List INSECURE_PORTS = Collections.unmodifiableList(Arrays.asList(Constants.WS_PORTS)); - - 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,6 +59,8 @@ 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()); + websocketPorts = WebsocketPorts.parseFromProperties(); + server = findAvailableSecurePort(certManager); Connector secureConnector = null; @@ -77,10 +73,10 @@ public static void runServer(CertificateManager certManager, boolean headless) t return; } - while(!running.get() && insecurePortIndex.get() < INSECURE_PORTS.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) { @@ -115,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) { @@ -128,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 @@ -155,7 +150,7 @@ private static Server findAvailableSecurePort(CertificateManager certManager) { if (certManager != null) { final AtomicBoolean runningSecure = new AtomicBoolean(false); - while(!runningSecure.get() && securePortIndex.get() < SECURE_PORTS.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()); @@ -170,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(); @@ -182,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(); } @@ -223,12 +218,9 @@ public static void main(String ... args) { App.main(args); } - public static int getSecurePortInUse() { - return SECURE_PORTS.get(securePortIndex.get()); - } - public static int getInsecurePortInUse() { - return INSECURE_PORTS.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, ","); + } +} diff --git a/test/qz/installer/provision/resources/provision.json b/test/qz/installer/provision/resources/provision.json index f596df548..374f63cf5 100644 --- a/test/qz/installer/provision/resources/provision.json +++ b/test/qz/installer/provision/resources/provision.json @@ -88,6 +88,18 @@ "os": "*", "data": "cert1.crt" }, + { + "description": "[PROPERTY] at 'certgen' (qz-tray.properties)", + "type": "property", + "os": "*", + "data": "websocket.secure.ports=9191,9292,9393,9494" + }, + { + "description": "[PROPERTY] at 'certgen' (qz-tray.properties)", + "type": "property", + "os": "*", + "data": "websocket.insecure.ports=9192,9293,9394,9495" + }, { "description": "[PROPERTY] at 'certgen' (qz-tray.properties)", "type": "property",