Package org.openqa.selenium.server.browserlaunchers

Source Code of org.openqa.selenium.server.browserlaunchers.MacProxyManager$MacNetworkSetupException

/*
* Copyright 2007 ThoughtWorks, Inc.
*
*  Licensed under the Apache License, Version 2.0 (the "License");
*  you may not use this file except in compliance with the License.
*  You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
*  Unless required by applicable law or agreed to in writing, software
*  distributed under the License is distributed on an "AS IS" BASIS,
*  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*  See the License for the specific language governing permissions and
*  limitations under the License.
*
*/
package org.openqa.selenium.server.browserlaunchers;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.prefs.Preferences;
import java.util.regex.Pattern;

import org.apache.commons.logging.Log;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.ExecTask;
import org.openqa.jetty.log.LogFactory;
import org.openqa.selenium.server.log.AntJettyLoggerBuildListener;

/**
* Class to manage the proxy server on OS X.  It uses the 'networksetup' tool to do
* its magic; it also depends on 'scutil' to read some settings we need to interact
* with 'networksetup.'
*
* <p>'networksetup' seems to come in a great many varieties depending on different
* versions of OS X (and different architectures: PPC vs Intel), so we've taken
* some care to write this class very defensively.</p>
* @author Dan Fabulich
*
*/
public class MacProxyManager {
    static Log log = LogFactory.getLog(MacProxyManager.class);
   
    private static final Pattern SCUTIL_LINE = Pattern.compile("^  (\\S+) : (.*)$");
    private static final Pattern NETWORKSETUP_LISTORDER_LINE = Pattern.compile("\\(Hardware Port: ([^,]*), Device: ([^\\)]*)\\)");
    private static final Pattern NETWORKSETUP_LINE = Pattern.compile("^([^:]+): (.*)$");

    private static final String BACKUP_READY = "backupready";
   
    private String sessionId;
    private File customProxyPACDir; // TODO evict this?
    private int port;
    // DGF used to be static/final, but that made it harder to mock out
    private Preferences prefs = Preferences.userNodeForPackage(MacProxyManager.class);
    /** The user defined name of the network service, used as an
     * argument to 'networksetup', e.g. "Built-in Ethernet"
     * or "AirPort". */
    private String networkService;

    public MacProxyManager(String sessionId, int port) {
        this.sessionId = sessionId;
        this.port = port;
        prefs = Preferences.userNodeForPackage(MacProxyManager.class);
    }
   
    public File getCustomProxyPACDir() {
        return customProxyPACDir;
    }   
   
    private boolean prefNodeExists(String key) {
        return null != prefs.get(key, null);
    }
   
    /** change the network settings to enable use of our proxy */
    @SuppressWarnings("unused")
    public void changeNetworkSettings() throws IOException {
        if (networkService == null) {
            getCurrentNetworkSettings();
        }
        customProxyPACDir = LauncherUtils.createCustomProfileDir(sessionId);
        if (customProxyPACDir.exists()) {
            LauncherUtils.recursivelyDeleteDir(customProxyPACDir);
        }
        customProxyPACDir.mkdir();
        log.info("Modifying OS X global network settings...");
        // TODO Disable proxy PAC URL (or, even better, use one!) SRC-364
       
        runNetworkSetup("-setwebproxy", networkService, "localhost", ""+port);

        runNetworkSetup("-setproxybypassdomains", networkService, "Empty");
    }
   
    private String findNetworkSetupBin() {
        String defaultPath = "/System/Library/CoreServices/RemoteManagement/ARDAgent.app/Contents/Support/networksetup";
        File defaultLocation = new File(defaultPath);
        if (defaultLocation.exists()) {
            return defaultLocation.getAbsolutePath();
        }
        File networkSetupBin = AsyncExecute.whichExec("networksetup");
        if (networkSetupBin != null) {
            return networkSetupBin.getAbsolutePath();
        }
        if (defaultLocation.getParentFile().exists()) {
            String[] files = defaultLocation.getParentFile().list();
            String guess = chooseSuitableNetworkSetup(System.getProperty("os.version"),
                    System.getProperty("os.arch"), files);
            if (guess != null) {
                File guessedLocation = new File(defaultLocation.getParentFile(), guess);
                log.warn("Couldn't find 'networksetup' in expected location; we're taking " +
                "a guess and using " + guessedLocation.getAbsolutePath() +
                " instead.  Please create a symlink called 'networksetup' to make " +
                "this warning go away.");
                return guessedLocation.getAbsolutePath();
            }
        }
        throw new MacNetworkSetupException("networksetup couldn't be found in the path!\n" +
              "Please add the directory containing 'networksetup' to your PATH environment\n" +
              "variable.");
    }
   
    /** Try to guess which 'networksetup' executable to use */
    private String chooseSuitableNetworkSetup(String osVersion, String osArch, String... files) {
        // DGF we don't technically need to know osArch, but according to comments in SRC-13,
        // sometimes Tiger on PPC looks different from Tiger on Intel, so we'll leave it in,
        // just in case
       
        Set<String> candidates = new HashSet<String>();
        for (String file : files) {
            if (file.startsWith("networksetup-")) {
                candidates.add(file);
            }
        }
        if (candidates.isEmpty()) {
            log.debug("No networksetup candidates found");
            return null;
        }
        if (candidates.size() == 1) {
            log.debug("One networksetup candidate found");
            return candidates.iterator().next();
        }
        log.debug("Multiple networksetup candidates found: " + candidates);
        // uh-oh.  There's no 'networksetup' and more than one 'networksetup-*'
        // we'll have to take a guess!
        String[] versionParts = osVersion.split("\\.");
        if (versionParts.length < 2) {
            log.debug("OS version seems to be invalid: " + osVersion);
            return null;
        }
        if (!"10".equals(versionParts[0])) {
            log.debug("OS version doesn't seem to be 10.*: " + osVersion);
            return null;
        }
        CodeName codeName;
        try {
            codeName = CodeName.minorVersion(versionParts[1]);
            String possibleCandidate = "networksetup-"+codeName.name().toLowerCase();
            if (candidates.contains(possibleCandidate)) {
                log.debug("This seems to be " + codeName + ", so we'll use " + possibleCandidate);
                return possibleCandidate;
            }
            log.debug("This seems to be " + codeName + ", but there's no " + possibleCandidate);
        } catch (IllegalArgumentException e) {
            log.debug("Couldn't find code name for OS version " + osVersion);
            return null;
        }
        // DGF when we know there's multiple candidates, but none of them match, should we just pick one?
        return null;
    }
   
    private enum CodeName {
        PUMA("1"),
        JAGUAR("2"),
        PANTHER("3"),
        TIGER("4"),
        LEOPARD("5");
       
        String minorVersion;
       
        CodeName(String minorVersion) {
            this.minorVersion = minorVersion;
        }
       
        static CodeName minorVersion(String minorVersion) {
            for (CodeName cn : values()) {
                if (cn.minorVersion.equals(minorVersion)) {
                    return cn;
                }
            }
            throw new IllegalArgumentException("No codename matches minorVersion " + minorVersion);
        }
    }
   
    private String findScutilBin() {
        String defaultPath = "/usr/sbin/scutil";
        File defaultLocation = new File(defaultPath);
        if (defaultLocation.exists()) {
            return defaultLocation.getAbsolutePath();
        }
        File scutilBin = AsyncExecute.whichExec("scutil");
        if (scutilBin != null) {
            return scutilBin.getAbsolutePath();
        }
        throw new MacNetworkSetupException("scutil couldn't be found in the path!\n" +
                "Please add the directory containing 'scutil' to your PATH environment\n" +
                "variable.");
    }
   
    /** Acquire current network settings using scutil/networksetup */
    private MacNetworkSettings getCurrentNetworkSettings() {
        getPrimaryNetworkServiceName();
        String output = runNetworkSetup("-getwebproxy", networkService);
        log.debug(output);
        Map<String,String> dictionary = LauncherUtils.parseDictionary(output.toString(), NETWORKSETUP_LINE);
        String strEnabled = verifyKey("Enabled", dictionary, "networksetup", output);
        boolean enabled = isTrueOrSomething(strEnabled);
        String server = verifyKey("Server", dictionary, "networksetup", output);
        String strPort = verifyKey("Port", dictionary, "networksetup", output);
        int port1;
        try {
            port1 = Integer.parseInt(strPort);
        } catch (NumberFormatException e) {
            throw new MacNetworkSetupException("Port didn't look right: " + output, e);
        }
        String strAuth = verifyKey("Authenticated Proxy Enabled", dictionary, "networksetup", output);
        boolean auth = isTrueOrSomething(strAuth);
        String[] bypassDomains = getCurrentProxyBypassDomains();
        MacNetworkSettings networkSettings = new MacNetworkSettings(networkService, enabled, server, port1, auth, bypassDomains);
        return networkSettings;
    }
   
    private String[] getCurrentProxyBypassDomains() {
        String output = runNetworkSetup("-getproxybypassdomains", networkService);
        log.debug(output);
        if (output == null) {
            throw new MacNetworkSetupException("-getproxybypassdomains had no output");
        }
        String[] lines = output.split("\n");
        int i = 0;
        if (lines.length == i) {
            return new String[] {""};
        }
        if (lines[i].startsWith("cp: /Library")) { // spurious warning when you don't run as root
            i++;
        }
        if (lines.length == i) {
            return new String[] {""};
        }
        if (lines[i].startsWith("There aren't any")) {
            return new String[0];
        }
        if (i == 0) return lines;
        String[] domains = new String[lines.length-i];
        System.arraycopy(lines, i, domains, 0, lines.length-i);
        return domains;
    }
   
    private boolean isTrueOrSomething(String value) {
        // networksetup sometimes uses one of these; we don't really care which!
        String[] matches = {"yes", "1", "true", "on"};
        for (String match : matches) {
            if (match.equalsIgnoreCase(value)) return true;
        }
        return false;
    }
   
    private String verifyKey(String key, Map<String,String> dictionary, String executable, String output) {
        if (!dictionary.containsKey(key)) {
            throw new MacNetworkSetupException("Couldn't find " + key + " in " + executable + "; output: " + output);
        }
        return dictionary.get(key);
    }
   
    private String getPrimaryNetworkServiceName() {
        // TODO This would be faster (but harder to test?) if we just launched scutil once
        // and communicated with it line-by-line using stdin/stdout
        String output = runScutil("show State:/Network/Global/IPv4");
        log.debug(output);
        Map<String,String> dictionary = LauncherUtils.parseDictionary(output.toString(), SCUTIL_LINE);
        String primaryInterface = verifyKey("PrimaryInterface", dictionary, "scutil", output);
        output = runNetworkSetup("-listnetworkserviceorder");
        log.debug(output);
        dictionary = LauncherUtils.parseDictionary(output.toString(), NETWORKSETUP_LISTORDER_LINE, true);
        String userDefinedName = verifyKey(primaryInterface, dictionary, "networksetup -listnetworksetuporder", output);
        networkService = userDefinedName;
        return userDefinedName;
       
    }
   
    /** Execute scutil and quit, returning the output */
    protected String runScutil(String arg) {
        Project p = new Project();
        p.addBuildListener(new AntJettyLoggerBuildListener(log));
        ExecTask exec = new ExecTask();
        exec.setProject(p);
        exec.setTaskType("scutil");
        exec.setExecutable(findScutilBin());
        exec.setFailonerror(false);
        exec.setResultProperty("result");
        exec.setOutputproperty("output");
        exec.setInputString(arg + "\nquit\n");
        exec.execute();
        String output = p.getProperty("output");
        String result = p.getProperty("result");
        if (!"0".equals(result)) {
            throw new RuntimeException("exec return code " + result + ": " + output);
        }
        return output;
    }
   
    /** Execute networksetup, returning the output */
    protected String runNetworkSetup(String... args) {
        Project p = new Project();
        p.addBuildListener(new AntJettyLoggerBuildListener(log));
        ExecTask exec = new ExecTask();
        exec.setProject(p);
        exec.setTaskType("networksetup");
        exec.setExecutable(findNetworkSetupBin());
        exec.setFailonerror(false);
        exec.setResultProperty("result");
        exec.setOutputproperty("output");
        for (Object arg : args) {
            exec.createArg().setValue(String.valueOf(arg));
        }
        exec.execute();
        String output = p.getProperty("output");
        String result = p.getProperty("result");
        if (!"0".equals(result)) {
            throw new RuntimeException("exec return code " + result + ": " + output);
        }
        return output;
    }
   
    @SuppressWarnings("serial")
    static class MacNetworkSetupException extends RuntimeException {
        MacNetworkSetupException(Exception e) {
            super(generateMessage(), e);
        }
       
        private static String generateMessage() {
            return "Problem while managing OS X network settings, OS Version " +
            System.getProperty("os.version");
            // TODO more diagnostics re: networksetup?  md5sum? others?
        }
       
        MacNetworkSetupException(String message) {
            this(new RuntimeException(message));
        }
       
        MacNetworkSetupException(String message, Throwable e) {
            super(generateMessage() + ": " + message, e);
        }
    }
   
    /** Copy OS X network settings into Java's per-user persistent preference store
     * @see Preferences
     * */
    @SuppressWarnings("unused")
    public void backupNetworkSettings() throws IOException {
        // Don't clobber our old backup if we
        // never got the chance to restore for some reason
        if (backupIsReady()) return;
        log.info("Backing up OS X global network settings...");
        MacNetworkSettings networkSettings = getCurrentNetworkSettings();
        writeToPrefs(networkSettings);
        backupReady(true);
    }

    /** Restore OS X network settings back the way thay were */
    public void restoreNetworkSettings() {
        // Backup really should be ready, but if not, skip it
        if (!backupIsReady()) return;
        log.info("Restoring OS X global network settings...");
        MacNetworkSettings networkSettings = retrieveFromPrefs();
       
        runNetworkSetup("-setwebproxy", networkSettings.serviceName, networkSettings.proxyServer, ""+networkSettings.port1);
        // DGF Do we need to do anything with authentication?  Let's just leave it alone and hope it doesn't bite us
       
        if (networkSettings.bypass.length > 0) {
            String[] bypassDomainArgs = new String[networkSettings.bypass.length+2];
            bypassDomainArgs[0] = "-setproxybypassdomains";
            bypassDomainArgs[1] = networkSettings.serviceName;
            System.arraycopy(networkSettings.bypass, 0, bypassDomainArgs, 2, networkSettings.bypass.length);
            runNetworkSetup(bypassDomainArgs);
        } else {
            runNetworkSetup("-setproxybypassdomains", networkSettings.serviceName, "Empty");
        }
       
        String enabledArg = networkSettings.enabled ? "on" : "off";
       
        runNetworkSetup("-setwebproxystate", networkSettings.serviceName, enabledArg);
       
        backupReady(false);
    }

    /** Extract network data from Java user preferences */
    private MacNetworkSettings retrieveFromPrefs() {
        String serviceName = prefsGetStringOrFail("serviceName");
        String proxyServer = prefsGetStringOrFail("proxyServer");
        String strBypass = prefsGetStringOrFail("bypass");
        String[] bypassEncodedArray, bypass;
        if ("".equals(strBypass)) {
            bypass = new String[0];
        } else {
            bypassEncodedArray = strBypass.split("\t");
            int domains;
            try {
                domains = Integer.parseInt(bypassEncodedArray[0]);
            } catch (NumberFormatException e) {
                throw new RuntimeException("BUG! Couldn't decode bypass preference: " + strBypass);
            }
            bypass = new String[domains];
            if (domains == bypassEncodedArray.length) {
                // DGF blank domain... I assume that only the last domain can be blank?
                if (domains == 1) {
                    bypass = new String[] {""};
                } else {
                    if (bypassEncodedArray.length != domains -1 ) {
                        throw new RuntimeException("BUG! Couldn't decode bypass preference: " + strBypass);
                    }
                    System.arraycopy(bypassEncodedArray, 1, bypass, 0, domains-1);
                }
            } else {
                if (bypassEncodedArray.length != domains +1 ) {
                    throw new RuntimeException("BUG! Couldn't decode bypass preference: " + strBypass);
                }
                System.arraycopy(bypassEncodedArray, 1, bypass, 0, domains);
            }
        }
       
        int port1 = prefsGetIntOrFail("port");
        boolean enabled = prefsGetBooleanOrFail("enabled");
        boolean authenticated = prefsGetBooleanOrFail("authenticated");
        return new MacNetworkSettings(serviceName, enabled, proxyServer, port1, authenticated, bypass);
    }
   
    private String prefsGetStringOrFail(String key) {
        String value = prefs.get(key, null);
        if (value == null) {
            throw new RuntimeException("BUG! pref key " + key + " should not be null");
        }
        return value;
    }
   
    private int prefsGetIntOrFail(String key) {
        prefsGetStringOrFail(key);
        return prefs.getInt(key, 0);
    }
   
    private boolean prefsGetBooleanOrFail(String key) {
        prefsGetStringOrFail(key);
        return prefs.getBoolean(key, false);
    }
   
    private void writeToPrefs(MacNetworkSettings networkSettings) {
        prefs.put("serviceName", networkSettings.serviceName);
        prefs.putBoolean("enabled", networkSettings.enabled);
        prefs.put("proxyServer", networkSettings.proxyServer);
        prefs.putInt("port", networkSettings.port1);
        prefs.putBoolean("authenticated", networkSettings.authenticated);
        prefs.put("bypass", networkSettings.bypassAsString());
    }
   
    private boolean backupIsReady() {
        if (!prefNodeExists(BACKUP_READY)) return false;
        return prefs.getBoolean(BACKUP_READY, false);
    }

    private void backupReady(boolean backupReady) {
        prefs.putBoolean(BACKUP_READY, backupReady);
    }

    /** Data class to hold network settings */
    class MacNetworkSettings {
        final String serviceName;
        final boolean enabled;
        final String proxyServer;
        final int port1;
        final boolean authenticated;
        final String[] bypass;
        public MacNetworkSettings(String serviceName, boolean enabled, String server, int port, boolean authenticated, String[] bypass) {
            this.serviceName = serviceName;
            this.enabled = enabled;
            this.proxyServer = server;
            this.port1 = port;
            this.authenticated = authenticated;
            this.bypass = bypass;
        }
       
       
        /** Return bypass domains as tab-delimited string */
        public String bypassAsString() {
            StringBuffer sb = new StringBuffer();
            sb.append(bypass.length).append('\t');
            for (String domain : bypass) {
                sb.append(domain).append('\t');
            }
            return sb.toString();
        }
       
        @Override
        public String toString() {
            StringBuffer sb = new StringBuffer("{serviceName=");
            sb.append(serviceName)
                .append(", enabled=").append(enabled)
                .append(", proxyServer=").append(proxyServer)
                .append(", port=").append(port1)
                .append(", authenticated=").append(authenticated)
                .append(", bypass=").append(Arrays.toString(bypass))
                .append("}");
                ;
            return sb.toString();
        }
    }
   
}
TOP

Related Classes of org.openqa.selenium.server.browserlaunchers.MacProxyManager$MacNetworkSetupException

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.