/*
* Copyright (c) xlightweb.org, 2008 - 2009. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Please refer to the LGPL license at: http://www.gnu.org/copyleft/lesser.txt
* The latest copy of this software may be found on http://www.xlightweb.org/
*/
package org.xlightweb.client;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.BufferUnderflowException;
import java.nio.channels.ClosedChannelException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.net.ssl.SSLContext;
import org.xlightweb.HttpRequest;
import org.xlightweb.HttpRequestHeaderWrapper;
import org.xlightweb.HttpUtils;
import org.xlightweb.IHttpExchange;
import org.xlightweb.IHttpRequest;
import org.xlightweb.IHttpRequestHandler;
import org.xlightweb.IHttpRequestHeader;
import org.xlightweb.IHttpResponse;
import org.xlightweb.IHttpResponseHandler;
import org.xsocket.Execution;
import org.xsocket.MaxReadSizeExceededException;
import org.xsocket.connection.IDataHandler;
import org.xsocket.connection.INonBlockingConnection;
import org.xsocket.connection.NonBlockingConnection;
/**
* Proxy handler
*
* @author grro@xlightweb.org
*/
final class ProxyHandler implements IHttpRequestHandler {
private static final Logger LOG = Logger.getLogger(ProxyHandler.class.getName());
private String proxyHost;
private int proxyPort = -1;
private String securedProxyHost;
private int securedProxyPort = -1;
private String proxyUser;
private String proxyPassword;
private String proxyUserPassword;
private SSLContext sslContext;
public void setSSLContext(SSLContext sslContext) {
this.sslContext = sslContext;
}
public void setProxyPort(int proxyPort) {
this.proxyPort = proxyPort;
}
public void setSecuredProxyHost(String host) {
if ((host != null) && (host.length() == 0)) {
host = null;
}
this.securedProxyHost = host;
}
public void setSecuredProxyPort(int proxyPort) {
this.securedProxyPort = proxyPort;
}
public void setProxyUser(String proxyUser) {
this.proxyUser = proxyUser;
if (proxyPassword != null) {
try {
proxyUserPassword = new String(HttpUtils.encodeBase64((proxyUser + ":" + proxyPassword).getBytes()));
} catch (IOException ioe) {
throw new RuntimeException(ioe.toString());
}
}
}
public void setProxyPassword(String proxyPassword) {
this.proxyPassword = proxyPassword;
if (proxyUser != null) {
try {
proxyUserPassword = new String(HttpUtils.encodeBase64((proxyUser + ":" + proxyPassword).getBytes()));
} catch (IOException ioe) {
throw new RuntimeException(ioe.toString());
}
}
}
public void setProxyHost(String host) {
if ((host != null) && (host.length() == 0)) {
host = null;
}
this.proxyHost = host;
}
/**
* {@inheritDoc}
*/
@Execution(Execution.NONTHREADED)
public void onRequest(IHttpExchange exchange) throws IOException {
IHttpRequest request = exchange.getRequest();
if (!hasProxytoUse(request)) {
exchange.forward(request);
return;
}
if (request.isSecure()) {
forwardSSL(exchange);
} else {
forwardNonSSL(exchange);
}
}
private boolean hasProxytoUse(IHttpRequest request) {
if (!request.isSecure() && (proxyHost == null)) {
return false;
}
if (request.isSecure() && (securedProxyHost == null)) {
return false;
}
return true;
}
private void forwardNonSSL(IHttpExchange exchange) throws IOException {
IHttpRequest request = exchange.getRequest();
if (proxyUser != null) {
if (proxyUserPassword != null) {
request.addHeader("Proxy-Authorization", "Basic " + proxyUserPassword);
} else {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("proxy password is not send send error");
}
exchange.sendError(new IOException("proxy user password is not set (hint: usage <HttpClient>.setProxyPassword(...)"));
return;
}
}
IHttpRequest wrappedRequest = null;
if (request.hasBody()) {
wrappedRequest = new HttpRequest(new NonSSLRequestHeaderWrapper(request.getRequestHeader()), request.getNonBlockingBody());
} else {
wrappedRequest = new HttpRequest(new NonSSLRequestHeaderWrapper(request.getRequestHeader()));
}
exchange.forward(wrappedRequest);
}
private void forwardSSL(IHttpExchange exchange) throws IOException {
String host = exchange.getRequest().getHeader("Host");
String forwardHost = host;
int forwardPort = 443;
int idx = host.lastIndexOf(":");
if (idx != -1) {
forwardPort = Integer.parseInt(host.substring(idx + 1, host.length()));
forwardHost = host.substring(0, idx);
}
INonBlockingConnection con = new NonBlockingConnection(securedProxyHost, securedProxyPort, sslContext, false);
con.setHandler(new DataHandler(exchange));
StringBuilder sb = new StringBuilder();
sb.append("CONNECT " + forwardHost + ":" + forwardPort + " HTTP/1.1\r\n" +
"Host: " + host + "\r\n" +
"User-Agent: xLightweb/" + HttpUtils.getImplementationVersion() + "\r\n");
if (proxyUser != null) {
if (proxyUserPassword != null) {
sb.append("Proxy-Authorization: Basic " + proxyUserPassword + "\r\n");
} else {
exchange.sendError(401, "proxy user password is not set (hint: usage <HttpClient>.setProxyPassword(...)");
return;
}
}
sb.append("Proxy-Connection: keep-alive\r\n" +
"\r\n");
con.write(sb.toString());
}
private static final class DataHandler implements IDataHandler {
private IHttpExchange exchange = null;
public DataHandler(IHttpExchange exchange) {
this.exchange = exchange;
}
public boolean onData(INonBlockingConnection connection) throws IOException, BufferUnderflowException, ClosedChannelException, MaxReadSizeExceededException {
String header = connection.readStringByDelimiter("\r\n\r\n");
String[] lines = header.split("\r\n");
int idx = lines[0].indexOf(" ");
String statusAndReason = lines[0].substring(idx, lines[0].length()).trim();
if (!statusAndReason.startsWith("200")) {
exchange.sendError(new IOException("could not set up tunnel to " + exchange.getRequest().getHeader("Host") + " got " + statusAndReason));
connection.close();
return true;
}
IHttpResponseHandler respHdl = new IHttpResponseHandler() {
public void onResponse(IHttpResponse response) throws IOException {
exchange.send(response);
}
public void onException(IOException ioe) throws IOException {
exchange.sendError(ioe);
}
};
connection.activateSecuredMode();
new HttpClientConnection(connection).send(exchange.getRequest(), respHdl);
return true;
}
}
private final class NonSSLRequestHeaderWrapper extends HttpRequestHeaderWrapper {
public NonSSLRequestHeaderWrapper(IHttpRequestHeader delegee) {
super(delegee);
}
public URL getRequestUrl() {
try {
URL orgURL = getWrappedRequestHeader().getRequestUrl();
int port = proxyPort;
if (port == -1) {
port = 80;
}
URL url = new URL("http", proxyHost, port, orgURL.getFile());
return url;
} catch (MalformedURLException murl) {
throw new RuntimeException(murl.toString());
}
}
@Override
public String toString() {
String s = getWrappedRequestHeader().toString();
int idx = s.indexOf("\r\n");
StringBuilder sb = new StringBuilder(getMethod() + " http://" + getHost() + getRequestURI());
if (getQueryString() != null) {
sb.append("?");
sb.append(getQueryString());
}
sb.append(" ");
sb.append(getProtocol());
sb.append("\r\n");
sb.append(s.substring(idx + 2, s.length()));
return sb.toString();
}
}
}