/*******************************************************************************
* Copyright (c) 2011 Subgraph.
* 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:
* Subgraph - initial API and implementation
******************************************************************************/
package com.subgraph.vega.internal.http.proxy;
import java.io.IOException;
import java.net.Socket;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import org.apache.http.ConnectionReuseStrategy;
import org.apache.http.HttpEntity;
import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpResponseFactory;
import org.apache.http.HttpStatus;
import org.apache.http.ProtocolVersion;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpProcessor;
import org.apache.http.protocol.HttpRequestHandlerResolver;
import org.apache.http.protocol.HttpService;
import org.apache.http.util.EncodingUtils;
import com.subgraph.vega.internal.http.proxy.ssl.SSLContextRepository;
/**
* Specialized HttpService which handles SSL connections.
*/
public class VegaHttpService {
private final static int DEFAULT_SSL_PORT = 443;
private final HttpResponseFactory responseFactory;
private final HttpProcessor processor;
private final HttpService delegatedHttpService;
private final SSLContextRepository sslContextRepository;
private final boolean sslEnabled;
public VegaHttpService(HttpProcessor proc, ConnectionReuseStrategy connStrategy, HttpResponseFactory responseFactory, HttpRequestHandlerResolver handlerResolver, HttpParams params, SSLContextRepository sslContextRepository) {
this.delegatedHttpService = new HttpService(proc, connStrategy, responseFactory, handlerResolver, params);
this.sslContextRepository = sslContextRepository;
this.sslEnabled = (sslContextRepository != null);
this.responseFactory = responseFactory;
this.processor = proc;
}
public void handleRequest(final VegaHttpServerConnection conn, final HttpContext context) throws IOException, HttpException {
if(!sslEnabled) {
delegatedHttpService.handleRequest(conn, context);
return;
}
final HttpRequest peekRequest = conn.peekRequestHeader();
if(isCertificateDownload(peekRequest)) {
conn.dropCachedPeekRequest();
sendCertificateDownloadResponse(conn, context);
} else if(isConnectMethodRequest(peekRequest)) {
conn.dropCachedPeekRequest();
handleConnect(conn, peekRequest, context);
} else {
delegatedHttpService.handleRequest(conn, context);
}
}
private void handleConnect(VegaHttpServerConnection conn, HttpRequest request, HttpContext context) throws IOException, HttpException {
final HttpHost host = createHostForConnectUri(request.getRequestLine().getUri());
final SSLSocket sslSocket = createSSLSocketForHost(host, conn.getSocket());
sendResponseOk(conn, context);
conn.rebindWithSSL(sslSocket, host);
try {
sslSocket.startHandshake();
} catch (SSLHandshakeException e) {
conn.close();
return;
}
delegatedHttpService.handleRequest(conn, context);
}
private HttpHost createHostForConnectUri(String uri) {
final String[] parts = uri.split(":");
final String hostname = parts[0].toLowerCase();
final int port = (parts.length < 2) ? (DEFAULT_SSL_PORT) : (stringToSSLPort(parts[1]));
return new HttpHost(hostname, port, "https");
}
private int stringToSSLPort(String s) {
if(s == null || s.isEmpty())
return DEFAULT_SSL_PORT;
try {
return Integer.parseInt(s);
} catch (NumberFormatException e) {
return DEFAULT_SSL_PORT;
}
}
private void sendCertificateDownloadResponse(VegaHttpServerConnection connection, HttpContext context) throws HttpException, IOException {
final String pem = sslContextRepository.getCaCertificatePem();
final byte[] body = EncodingUtils.getAsciiBytes(pem);
ByteArrayEntity entity = new ByteArrayEntity(body);
entity.setContentType("application/x-x509-ca-cert; charset=US-ASCII");
sendResponseOk(connection, context, entity);
}
private void sendResponseOk(VegaHttpServerConnection connection, HttpContext context) throws HttpException, IOException {
sendResponseOk(connection, context, null);
}
private void sendResponseOk(VegaHttpServerConnection connection, HttpContext context, HttpEntity entity) throws HttpException, IOException {
final ProtocolVersion version = new ProtocolVersion("HTTP", 1, 0);
final HttpResponse response = responseFactory.newHttpResponse(version, HttpStatus.SC_OK, context);
if(entity != null)
response.setEntity(entity);
processor.process(response, context);
connection.sendResponseHeader(response);
connection.sendResponseEntity(response);
connection.flush();
}
private SSLSocket createSSLSocketForHost(HttpHost host, Socket socket) throws IOException {
final SSLContext ctx = sslContextRepository.getContextForName(host.getHostName());
if(ctx == null) {
throw new IOException("Failed to create SSLContext for host "+ host.getHostName());
}
SSLSocketFactory sslSocketFactory = ctx.getSocketFactory();
SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket(socket, host.getHostName(), host.getPort(), true);
sslSocket.setUseClientMode(false);
return sslSocket;
}
private boolean isConnectMethodRequest(HttpRequest request) {
final String method = request.getRequestLine().getMethod();
return (method != null && method.equalsIgnoreCase("CONNECT"));
}
private boolean isCertificateDownload(HttpRequest request) {
return request.getRequestLine().getUri().toLowerCase().equals("http://vega/ca.crt");
}
}