/**
* *****************************************************************************
*
* Copyright (c) 2004-2009 Oracle Corporation.
*
* All rights reserved. This program and the accompanying materials are made
* available under the terms of the Eclipse Public License v1.0 which
* accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
*
* Kohsuke Kawaguchi, Winston Prakash
*
*
******************************************************************************
*/
package hudson;
import hudson.model.Hudson;
import hudson.model.Saveable;
import hudson.model.listeners.SaveableListener;
import hudson.util.IOException2;
import hudson.util.Scrambler;
import hudson.util.XStream2;
import java.io.File;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.URL;
import java.net.URLConnection;
import com.thoughtworks.xstream.XStream;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.util.StringTokenizer;
import org.apache.commons.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* HTTP proxy configuration.
*
* <p> Use {@link #open(URL)} to open a connection with the proxy setting. <p>
* Proxy Authorization is done via "Proxy-Authorization" HTTP header See
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html section 14.34</a>).
*
* @see Hudson#proxy
*/
public final class ProxyConfiguration implements Saveable {
public String name;
public int port;
public String noProxyFor;
private int TIME_OUT_RETRY_COUNT = 5;
private int CONNECTION_TIME_OUT_MS = 2000;
private transient Logger logger = LoggerFactory.getLogger(ProxyConfiguration.class);
/**
* Possibly null proxy user name and password. Password is base64 scrambled
* since this is persisted to disk.
*/
private String userName;
private String password;
private boolean authNeeded = false;
private File rootDir;
public ProxyConfiguration(File dir) throws IOException {
rootDir = dir;
load();
}
public ProxyConfiguration(String name, int port) {
this(name, port, null, null, null, false);
}
public ProxyConfiguration(String name, int port, String noProxyFor, String userName, String password, boolean authNeeded) {
configure(name, port, noProxyFor, userName, password, authNeeded);
}
public void configure(String name, int port, String noProxyFor, String userName, String password, boolean authNeeded) {
this.name = name;
this.port = port;
this.userName = userName;
this.password = Scrambler.scramble(password);
this.authNeeded = authNeeded;
this.noProxyFor = noProxyFor;
}
public String getName() {
return name;
}
public int getPort() {
return port;
}
public String getNoProxyFor() {
return noProxyFor;
}
public boolean isAuthNeeded() {
return authNeeded;
}
public String getUserName() {
return userName;
}
public String getPassword() {
return Scrambler.descramble(password);
}
public Proxy createProxy() {
return new Proxy(Proxy.Type.HTTP, new InetSocketAddress(name, port));
}
@Override
public void save() throws IOException {
if (BulkChange.contains(this)) {
return;
}
getXmlFile().write(this);
// Could be called oiutside of Hudson Context (Ex Initial Setup)
if (Hudson.getInstance() != null) {
SaveableListener.fireOnChange(this, getXmlFile());
}
}
public XmlFile getXmlFile() {
return new XmlFile(XSTREAM, new File(rootDir, "proxy.xml"));
}
public void load() throws IOException {
XmlFile config = getXmlFile();
try {
if (config.exists()) {
config.unmarshal(this);
}
} catch (IOException e) {
logger.error("Failed to load " + config, e);
}
}
public URLConnection openUrl(URL url) throws IOException {
if (name == null) {
URLConnection urlConnection = url.openConnection();
connect(urlConnection);
return urlConnection;
}
if (noProxyFor != null) {
StringTokenizer tokenizer = new StringTokenizer(noProxyFor, ",");
while (tokenizer.hasMoreTokens()) {
String noProxyHost = tokenizer.nextToken().trim();
if (noProxyHost.contains("*")) {
if (url.getHost().trim().contains(noProxyHost.replaceAll("\\*", ""))) {
return url.openConnection(Proxy.NO_PROXY);
}
} else if (url.getHost().trim().equals(noProxyHost)) {
return url.openConnection(Proxy.NO_PROXY);
}
}
}
URLConnection urlConnection = url.openConnection(createProxy());
if (isAuthNeeded()) {
String credentials = getUserName() + ":" + getPassword();
String encoded = new String(Base64.encodeBase64(credentials.getBytes()));
urlConnection.setRequestProperty("Proxy-Authorization", "Basic " + encoded);
}
connect(urlConnection);
return urlConnection;
}
private void connect(URLConnection urlConnection) throws IOException {
boolean connected = false;
int count = 0;
urlConnection.setConnectTimeout(CONNECTION_TIME_OUT_MS);
while (!connected) {
try {
urlConnection.connect();
connected = true;
} catch (SocketTimeoutException exc) {
logger.debug("Connection timed out. trying again {}", count);
if (++count > TIME_OUT_RETRY_COUNT) {
throw new IOException(
"Could not connect to " + urlConnection.getURL().toExternalForm() + ". Connection timed out after " + TIME_OUT_RETRY_COUNT + " tries.");
}
connected = false;
} catch (UnknownHostException exc) {
throw new IOException2(
"Could not connect to " + urlConnection.getURL().toExternalForm() + ". Check your internet connection.",
exc);
} catch (ConnectException exc) {
throw new IOException2(
"Could not connect to " + urlConnection.getURL().toExternalForm() + ". Check your internet connection.",
exc);
}
}
}
/**
* This method should be used wherever {@link URL#openConnection()} to
* internet URLs is invoked directly.
*/
public static URLConnection open(URL url) throws IOException {
Hudson hudson = Hudson.getInstance(); // this code might run on slaves
ProxyConfiguration proxyConfig = hudson != null ? hudson.proxy : null;
if (proxyConfig == null) {
return url.openConnection();
} else {
return proxyConfig.openUrl(url);
}
}
private static final XStream XSTREAM = new XStream2();
static {
XSTREAM.alias("proxy", ProxyConfiguration.class);
}
}