package com.btr.proxy.selector.pac; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.Proxy; import java.net.URISyntaxException; import java.net.URL; import com.btr.proxy.util.Logger; import com.btr.proxy.util.Logger.LogLevel; import com.btr.proxy.util.MiscUtil; /***************************************************************************** * Script source that will load the content of a PAC file from an webserver. * The script content is cached once it was downloaded. * * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009 ****************************************************************************/ public class UrlPacScriptSource implements PacScriptSource { private static final int DEFAULT_CONNECT_TIMEOUT = 1 * 1000; // seconds private static final int DEFAULT_READ_TIMEOUT = 2 * 1000; // seconds public static final String OVERRIDE_CONNECT_TIMEOUT = "com.btr.proxy.url.connectTimeout"; public static final String OVERRIDE_READ_TIMEOUT = "com.btr.proxy.url.readTimeout"; private final String scriptUrl; private String scriptContent; private long expireAtMillis; /************************************************************************* * Constructor * @param url the URL to download the script from. ************************************************************************/ public UrlPacScriptSource(String url) { super(); this.expireAtMillis = 0; this.scriptUrl = url; } /************************************************************************* * getScriptContent * @see com.btr.proxy.selector.pac.PacScriptSource#getScriptContent() ************************************************************************/ public synchronized String getScriptContent() throws IOException { if (this.scriptContent == null || (this.expireAtMillis > 0 && this.expireAtMillis > System.currentTimeMillis())) { try { if (this.scriptUrl.startsWith("file:/") || this.scriptUrl.indexOf(":/") == -1) { this.scriptContent = readPacFileContent(this.scriptUrl); } else { this.scriptContent = downloadPacContent(this.scriptUrl); } } catch (IOException e) { Logger.log(getClass(), LogLevel.ERROR, "Loading script failed from: {0} with error {1}", this.scriptUrl, e); this.scriptContent = ""; throw e; } } return this.scriptContent; } /************************************************************************* * Reads a PAC script from a local file. * @param scriptUrl * @return the content of the script file. * @throws IOException * @throws URISyntaxException ************************************************************************/ private String readPacFileContent(String scriptUrl) throws IOException { try { File file = null; if (scriptUrl.indexOf(":/") == -1) { file = new File(scriptUrl); } else { file = new File(new URL(scriptUrl).toURI()); } BufferedReader r = new BufferedReader(new FileReader(file)); StringBuilder result = new StringBuilder(); try { String line; while ((line = r.readLine()) != null) { result.append(line).append("\n"); } } finally { r.close(); } return result.toString(); } catch (Exception e) { Logger.log(getClass(), LogLevel.ERROR, "File reading error.", e); throw new IOException(e.getMessage()); } } /************************************************************************* * Downloads the script from a webserver. * @param url the URL to the script file. * @return the script content. * @throws IOException on read error. ************************************************************************/ private String downloadPacContent(String url) throws IOException { if (url == null) { throw new IOException("Invalid PAC script URL: null"); } setPacProxySelectorEnabled(false); HttpURLConnection con = null; try { con = setupHTTPConnection(url); if (con.getResponseCode() != 200) { throw new IOException("Server returned: "+con.getResponseCode()+" "+con.getResponseMessage()); } // Read expire date. this.expireAtMillis = con.getExpiration(); BufferedReader r = getReader(con); String result = readAllContent(r); r.close(); return result; } finally { setPacProxySelectorEnabled(true); if (con != null) { con.disconnect(); } } } /************************************************************************* * Enables/disables the PAC proxy selector while we download to prevent recursion. * See issue: 26 in the change tracker. ************************************************************************/ private void setPacProxySelectorEnabled(boolean enable) { PacProxySelector.setEnabled(enable); } /************************************************************************* * Reads the whole content available into a String. * @param r to read from. * @return the complete PAC file content. * @throws IOException ************************************************************************/ private String readAllContent(BufferedReader r) throws IOException { StringBuilder result = new StringBuilder(); String line; while ((line = r.readLine()) != null) { result.append(line).append("\n"); } return result.toString(); } /************************************************************************* * Build a BufferedReader around the open HTTP connection. * @param con to read from * @return the BufferedReader. * @throws UnsupportedEncodingException * @throws IOException ************************************************************************/ private BufferedReader getReader(HttpURLConnection con) throws UnsupportedEncodingException, IOException { String charsetName = parseCharsetFromHeader(con.getContentType()); BufferedReader r = new BufferedReader(new InputStreamReader(con.getInputStream(), charsetName)); return r; } /************************************************************************* * Configure the connection to download from. * @param url to get the pac file content from * @return a HTTPUrlConnecion to this url. * @throws IOException * @throws MalformedURLException ************************************************************************/ private HttpURLConnection setupHTTPConnection(String url) throws IOException, MalformedURLException { HttpURLConnection con = (HttpURLConnection) new URL(url).openConnection(Proxy.NO_PROXY); con.setConnectTimeout(getTimeOut(OVERRIDE_CONNECT_TIMEOUT, DEFAULT_CONNECT_TIMEOUT)); con.setReadTimeout(getTimeOut(OVERRIDE_READ_TIMEOUT, DEFAULT_READ_TIMEOUT)); con.setInstanceFollowRedirects(true); con.setRequestProperty("accept", "application/x-ns-proxy-autoconfig, */*;q=0.8"); return con; } /************************************************************************* * Gets the timeout value from a property or uses the given default value if * the property cannot be parsed. * @param overrideProperty the property to define the timeout value in milliseconds * @param defaultValue the default timeout value in milliseconds. * @return the value to use. ************************************************************************/ protected int getTimeOut(String overrideProperty, int defaultValue) { int timeout = defaultValue; String prop = System.getProperty(overrideProperty); if (prop != null && prop.trim().length() > 0) { try { timeout = MiscUtil.parseInt(prop.trim()); } catch (NumberFormatException e) { Logger.log(getClass(), LogLevel.DEBUG, "Invalid override property : {0}={1}", overrideProperty, prop); // In this case use the default value. } } return timeout; } /************************************************************************* * Response Content-Type could be something like this: * application/x-ns-proxy-autoconfig; charset=UTF-8 * @param contentType header field. * @return the extracted charset if set else a default charset. ************************************************************************/ String parseCharsetFromHeader(String contentType) { String result = "ISO-8859-1"; if (contentType != null) { String[] paramList = contentType.split(";"); for (String param : paramList) { if (param.toLowerCase().trim().startsWith("charset") && param.indexOf("=") != -1) { result = param.substring(param.indexOf("=")+1).trim(); } } } return result; } /*************************************************************************** * @see java.lang.Object#toString() **************************************************************************/ @Override public String toString() { return this.scriptUrl; } /************************************************************************* * isScriptValid * @see com.btr.proxy.selector.pac.PacScriptSource#isScriptValid() ************************************************************************/ public boolean isScriptValid() { try { String script = getScriptContent(); if (script == null || script.trim().length() == 0) { Logger.log(getClass(), LogLevel.DEBUG, "PAC script is empty. Skipping script!"); return false; } if (script.indexOf("FindProxyForURL") == -1) { Logger.log(getClass(), LogLevel.DEBUG, "PAC script entry point FindProxyForURL not found. Skipping script!"); return false; } return true; } catch (IOException e) { Logger.log(getClass(), LogLevel.DEBUG, "File reading error: {0}", e); return false; } } }