package org.openqa.selenium.server.browserlaunchers;
import org.apache.commons.logging.Log;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.Copy;
import org.apache.tools.ant.taskdefs.Delete;
import org.apache.tools.ant.types.FileSet;
import org.mortbay.log.LogFactory;
import org.openqa.selenium.server.SeleniumServer;
import org.openqa.selenium.server.ClassPathResource;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Various static utility functions used to launch browsers
*/
public class LauncherUtils {
static Log log = LogFactory.getLog(LauncherUtils.class);
/**
* creates an empty temp directory for managing a browser profile
*/
protected static File createCustomProfileDir(String sessionId) {
final File customProfileDir;
customProfileDir = customProfileDir(sessionId);
if (customProfileDir.exists()) {
LauncherUtils.recursivelyDeleteDir(customProfileDir);
}
customProfileDir.mkdir();
return customProfileDir;
}
/**
* Return the name of the custom profile directory for a specific seleniumm session
*
* @param sessionId Current selenium sesssion id. Cannot be null.
* @return file path of the custom profile directory for this session.
*/
public static File customProfileDir(String sessionId) {
final File tmpDir;
final String customProfileDirParent;
final File customProfileDir;
tmpDir = new File(System.getProperty("java.io.tmpdir"));
customProfileDirParent = ((tmpDir.exists() && tmpDir.isDirectory()) ? tmpDir.getAbsolutePath() : ".");
customProfileDir = new File(customProfileDirParent + "/customProfileDir" + sessionId);
return customProfileDir;
}
/**
* Delete a directory and all subdirectories
*/
public static void recursivelyDeleteDir(File customProfileDir) {
if (customProfileDir == null || !customProfileDir.exists()) {
return;
}
Delete delete = new Delete();
delete.setProject(new Project());
delete.setDir(customProfileDir);
delete.setFailOnError(true);
delete.execute();
}
/**
* Try several times to recursively delete a directory
*/
public static void deleteTryTryAgain(File dir, int tries) {
try {
recursivelyDeleteDir(dir);
} catch (BuildException e) {
if (tries > 0) {
AsyncExecute.sleepTight(2000);
deleteTryTryAgain(dir, tries - 1);
} else {
throw e;
}
}
}
/**
* Generate a proxy.pac file, configuring a dynamic proxy for URLs
* containing "/selenium-server/"
* @param avoidProxy TODO
*/
protected static File makeProxyPAC(File parentDir, int port, boolean avoidProxy) throws FileNotFoundException {
return makeProxyPAC(parentDir, port, true, avoidProxy);
}
/**
* Generate a proxy.pac file, configuring a dynamic proxy. <p/> If
* proxySeleniumTrafficOnly is true, then the proxy applies only to URLs
* containing "/selenium-server/". Otherwise the proxy applies to all URLs.
* @param avoidProxy TODO
*/
protected static File makeProxyPAC(File parentDir, int port, boolean proxySeleniumTrafficOnly, boolean avoidProxy) throws FileNotFoundException {
return makeProxyPAC(parentDir, port, proxySeleniumTrafficOnly,
System.getProperty("http.proxyHost"),
System.getProperty("http.proxyPort"),
System.getProperty("http.nonProxyHosts"), avoidProxy);
}
public static File makeProxyPAC(File parentDir, int port, boolean proxySeleniumTrafficOnly, String configuredProxy, String proxyPort, String nonProxyHosts, boolean avoidProxy) throws FileNotFoundException {
if (!avoidProxy) {
proxySeleniumTrafficOnly = false;
}
File proxyPAC = new File(parentDir, "proxy.pac");
PrintStream out = new PrintStream(new FileOutputStream(proxyPAC));
String defaultProxy = "DIRECT";
if (configuredProxy != null) {
defaultProxy = "PROXY " + configuredProxy;
if (proxyPort != null) {
defaultProxy += ":" + proxyPort;
}
}
out.println("function FindProxyForURL(url, host) {");
if (proxySeleniumTrafficOnly) {
out.println(" if(shExpMatch(url, '*/selenium-server/*')) {");
}
out.println(" return 'PROXY localhost:" + Integer.toString(port) + "; " + defaultProxy + "';");
if (proxySeleniumTrafficOnly) {
if (nonProxyHosts != null && nonProxyHosts.trim().length() > 0) {
String[] hosts = nonProxyHosts.split("\\|");
for (String host : hosts) {
out.println(" } else if (shExpMatch(host, '"+host+"')) {");
out.println(" return 'DIRECT';");
}
}
if (configuredProxy != null) {
out.println(" } else {");
out.println(" return '" + defaultProxy + "';");
}
out.println(" }");
}
out.println("}");
out.close();
return proxyPAC;
}
/**
* Strips the specified URL so it only includes a protocal, hostname and
* port
*
* @throws MalformedURLException
*/
public static String stripStartURL(String url) {
try {
URL u = new URL(url);
String path = u.getPath();
if (path != null && !"".equals(path) && !path.endsWith("/")) {
log.warn("It looks like your baseUrl ("+url+") is pointing to a file, not a directory (it doesn't end with a /). We're going to have to strip off the last part of the pathname.");
}
return u.getProtocol() + "://" + u.getAuthority();
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
}
protected static String getQueryString(String url) {
final String query;
try {
query = new URL(url).getQuery();
return query == null ? "" : query;
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
}
protected static String getDefaultHTMLSuiteUrl(String browserURL, String suiteUrl, boolean multiWindow, int serverPort) {
String url = LauncherUtils.stripStartURL(browserURL);
String resultsUrl;
if (serverPort == 0) {
resultsUrl = "../postResults";
} else {
resultsUrl = "http://localhost:" + serverPort + "/selenium-server/postResults";
}
return url + "/selenium-server/core/TestRunner.html?auto=true"
+ "&multiWindow=" + multiWindow
+ "&defaultLogLevel=info"
+ "&baseUrl=" + urlEncode(browserURL)
+ "&resultsUrl=" + resultsUrl
+ "&test=" + urlEncode(suiteUrl);
}
protected static String getDefaultRemoteSessionUrl(String startURL, String sessionId, boolean multiWindow, int serverPort, boolean browserSideLog) {
String url = LauncherUtils.stripStartURL(startURL);
url += "/selenium-server/core/RemoteRunner.html?"
+ "sessionId=" + sessionId
+ "&multiWindow=" + multiWindow
+ "&baseUrl=" + urlEncode(startURL)
+ "&debugMode=" + browserSideLog;
if (serverPort != 0) {
url += "&driverUrl=http://localhost:" + serverPort + "/selenium-server/driver/";
}
return url;
}
/** Encodes the text as an URL using UTF-8.
*
* @param text the text too encode
* @return the encoded URI string
* @see URLEncoder#encode(java.lang.String, java.lang.String)
*/
public static String urlEncode(String text) {
try {
return URLEncoder.encode(text, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
protected static File extractHTAFile(File dir, int port, String resourceFile, String outFile) {
InputStream input = getSeleniumResourceAsStream(resourceFile);
BufferedReader br = new BufferedReader(new InputStreamReader(input));
File hta = new File(dir, outFile);
try {
FileWriter fw = new FileWriter(hta);
String line = br.readLine();
fw.write(line);
fw.write('\n');
fw.write("<base href=\"http://localhost:" + port + "/selenium-server/core/\">");
while ((line = br.readLine()) != null) {
fw.write(line);
fw.write('\n');
}
fw.flush();
fw.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
return hta;
}
public static InputStream getSeleniumResourceAsStream(String resourceFile) {
Class clazz = ClassPathResource.class;
InputStream input = clazz.getResourceAsStream(resourceFile);
if (input == null) {
try {
// This is hack for the OneJar version of Selenium-Server.
// Examine the contents of the jar made by
// https://svn.openqa.org/svn/selenium-rc/trunk/selenium-server-onejar/build.xml
clazz = Class.forName("OneJar");
input = clazz.getResourceAsStream(resourceFile);
} catch (ClassNotFoundException e) {
}
}
return input;
}
public static boolean isScriptFile(File aFile) {
final char firstTwoChars[] = new char[2];
final FileReader reader;
int charsRead;
try {
reader = new FileReader(aFile);
charsRead = reader.read(firstTwoChars);
if (2 != charsRead) {
return false;
}
return (firstTwoChars[0] == '#' && firstTwoChars[1] == '!');
} catch (IOException e) {
throw new RuntimeException(e);
}
}
protected static void copySingleFile(File sourceFile, File destFile) {
copySingleFileWithOverwrite(sourceFile, destFile, false);
}
protected static void copySingleFileWithOverwrite(File sourceFile, File destFile, boolean overwrite) {
Project p = new Project();
Copy c = new Copy();
c.setProject(p);
c.setTofile(destFile);
FileSet fs = new FileSet();
fs.setProject(p);
fs.setFile(sourceFile);
c.addFileset(fs);
c.setOverwrite(overwrite);
c.execute();
}
protected static void copyDirectory(File source, File dest) {
Project p = new Project();
Copy c = new Copy();
c.setProject(p);
c.setTodir(dest);
FileSet fs = new FileSet();
fs.setDir(source);
c.addFileset(fs);
c.execute();
}
/**
* Copies all files matching the suffix to the destination directory.
*
* If no files match, and the destination directory did not already
* exist, the destination directory is still created, if possible.
*
* @param source the source directory
* @param suffix the suffix for all files to be copied.
* @param dest the destination directory
*/
protected static boolean copyDirectory(File source, String suffix, File dest) {
boolean result = false;
try {
Project p = new Project();
Copy c = new Copy();
c.setProject(p);
c.setTodir(dest);
FileSet fs = new FileSet();
fs.setDir(source);
if (null != suffix) {
fs.setIncludes("*" + suffix); // add the wildcard.
}
c.addFileset(fs);
c.execute();
// handle case where no files match; must create empty directory.
if (!dest.exists()) {
result = dest.mkdirs();
} else {
result = true;
}
} catch (SecurityException se) {
log.warn("Could not copy the specified directory files", se);
result = false;
}
return result;
}
protected enum ProxySetting { NO_PROXY, PROXY_SELENIUM_TRAFFIC_ONLY, PROXY_EVERYTHING };
protected static void generatePacAndPrefJs(File customProfileDir, int port, ProxySetting proxySetting, String homePage, boolean changeMaxConnections, int timeoutInSeconds, boolean avoidProxy) throws FileNotFoundException {
// We treat PROXY_SELENIUM_TRAFFIC_ONLY as a suggestion; if the user didn't explicitly
// allow us to proxy selenium traffic only, then we'll proxy everything
if (proxySetting == ProxySetting.PROXY_SELENIUM_TRAFFIC_ONLY && avoidProxy) {
proxySetting = ProxySetting.PROXY_EVERYTHING;
}
File prefsJS = new File(customProfileDir, "prefs.js");
PrintStream out = new PrintStream(new FileOutputStream(prefsJS, true));
// Don't ask if we want to switch default browsers
out.println("user_pref('browser.shell.checkDefaultBrowser', false);");
if (proxySetting != ProxySetting.NO_PROXY) {
boolean proxySeleniumTrafficOnly = (proxySetting == ProxySetting.PROXY_SELENIUM_TRAFFIC_ONLY);
// Configure us as the local proxy
File proxyPAC = LauncherUtils.makeProxyPAC(customProfileDir, port, proxySeleniumTrafficOnly, avoidProxy);
out.println("user_pref('network.proxy.type', 2);");
out.println("user_pref('network.proxy.autoconfig_url', '" + pathToBrowserURL(proxyPAC.getAbsolutePath()) + "');");
}
// suppress authentication confirmations
out.println("user_pref('network.http.phishy-userpass-length', 255);");
// Disable pop-up blocking
out.println("user_pref('browser.allowpopups', true);");
out.println("user_pref('dom.disable_open_during_load', false);");
// Allow scripts to run as long as the server timeout
out.println("user_pref('dom.max_script_run_time', " + timeoutInSeconds + ");");
out.println("user_pref('dom.max_chrome_script_run_time', " + timeoutInSeconds + ");");
// Open links in new windows (Firefox 2.0)
out.println("user_pref('browser.link.open_external', 2);");
out.println("user_pref('browser.link.open_newwindow', 2);");
if (homePage != null) {
out.println("user_pref('startup.homepage_override_url', '" + homePage + "');");
// for Firefox 2.0
out.println("user_pref('browser.startup.homepage', '" + homePage + "');");
out.println("user_pref('startup.homepage_welcome_url', '');");
// This handles known RC problems when the startup page is a blank page or when the previous session has been restored
out.println("user_pref('browser.startup.page', 1);");
}
// Disable security warnings
out.println("user_pref('security.warn_submit_insecure', false);");
out.println("user_pref('security.warn_submit_insecure.show_once', false);");
out.println("user_pref('security.warn_entering_secure', false);");
out.println("user_pref('security.warn_entering_secure.show_once', false);");
out.println("user_pref('security.warn_entering_weak', false);");
out.println("user_pref('security.warn_entering_weak.show_once', false);");
out.println("user_pref('security.warn_leaving_secure', false);");
out.println("user_pref('security.warn_leaving_secure.show_once', false);");
out.println("user_pref('security.warn_viewing_mixed', false);");
out.println("user_pref('security.warn_viewing_mixed.show_once', false);");
// Disable cache
out.println("user_pref('browser.cache.disk.enable', false);");
out.println("user_pref('browser.cache.memory.enable', true);");
// Disable "do you want to remember this password?"
out.println("user_pref('signon.rememberSignons', false);");
// Disable re-asking for license agreement (Firefox 3)
out.println("user_pref('browser.EULA.3.accepted', true);");
out.println("user_pref('browser.EULA.override', true);");
// Disable any of the random self-updating crap
out.println("user_pref('app.update.auto', false);");
out.println("user_pref('app.update.enabled', false);");
out.println("user_pref('extensions.update.enabled', false);");
out.println("user_pref('browser.search.update', false);");
out.println("user_pref('browser.safebrowsing.enabled', false);");
if (changeMaxConnections) {
out.println("user_pref('network.http.max-connections', 256);");
out.println("user_pref('network.http.max-connections-per-server', 256);");
out.println("user_pref('network.http.max-persistent-connections-per-proxy', 256);");
out.println("user_pref('network.http.max-persistent-connections-per-server', 256);");
}
out.close();
}
static final Pattern JAVA_STYLE_UNC_URL = Pattern.compile("^file:////([^/]+/.*)$");
/**
* Generates an URL suitable for use in browsers, unlike Java's URLs, which
* choke on UNC paths. <p/>
* <P>
* Java's URLs work in IE, but break in Mozilla. Mozilla's team snobbily
* demanded that <I>all</I> file paths must have the empty authority
* (file:///), even for UNC file paths. On Mozilla \\socrates\build is
* therefore represented as file://///socrates/build.
* </P>
* See Mozilla bug <a
* href="https://bugzilla.mozilla.org/show_bug.cgi?id=66194">66194</A>.
*
* @param path -
* the file path to convert to a browser URL
* @return a nice Mozilla-compatible file URL
*/
private static String pathToBrowserURL(String path) {
if (path == null)
return null;
String url = (new File(path)).toURI().toString();
Matcher m = JAVA_STYLE_UNC_URL.matcher(url);
if (m.find()) {
url = "file://///";
url += m.group(1);
}
return url;
}
/** Run the specified pattern on each line of the data to extract a dictionary */
public static Map<String,String> parseDictionary(String data, Pattern pattern, boolean reverse) {
Map<String,String> map = new HashMap<String, String>();
for (String line : data.split("\n")) {
Matcher m = pattern.matcher(line);
if (!m.find()) continue;
String name, value;
if (reverse) {
name = m.group(2);
value = m.group(1);
} else {
name = m.group(1);
value = m.group(2);
}
map.put(name, value);
}
return map;
}
public static Map<String,String> parseDictionary(String data, Pattern pattern) {
return parseDictionary(data, pattern, false);
}
}