/*
* Hamsam - Instant Messaging API
* Copyright (C) 2003 Raghu K
*
* 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
*/
package hamsam.util.net.ssl;
import hamsam.net.ProxyInfo;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManager;
/**
* An HTTPS URL connection that uses a proxy tunnel.
*
* @author Raghu
*/
public class HamsamHttpsConnection
{
/**
* TrustManager for all SSL connections that we make.
*/
private static TrustManager[] trustManagers;
/**
* SSL context for all SSL connections.
*/
private static SSLContext sc;
/**
* URL for this connection
*/
private URL url;
/**
* The tunnelling SSL socket factory that we use to open
* all SSL sockets.
*/
private SSLTunnelSocketFactory factory;
/**
* All outgoing headers.
*/
private Map requestHeaders;
/**
* All incoming headers.
*/
private Map responseHeaders;
/**
* In case we recieve a 3xx response, this object is where we
* redirect to.
*/
private HamsamHttpsConnection redirection;
/**
* The HTTP method to be used for querying.
*/
private String method;
static
{
trustManagers = new TrustManager[1];
trustManagers[0] = new HamsamTrustManager();
try
{
sc = SSLContext.getInstance("SSL");
sc.init(null, trustManagers, new SecureRandom());
}
catch(NoSuchAlgorithmException e)
{
e.printStackTrace();
// TODO log this exception
}
catch(KeyManagementException e)
{
e.printStackTrace();
// TODO log this exception
}
}
/**
* Constructs an HTTPS connection to the specified URL.
*
* @param urlStr the URL to connect to.
*/
public HamsamHttpsConnection(String urlStr, ProxyInfo info) throws MalformedURLException
{
this(urlStr, new SSLTunnelSocketFactory(sc.getSocketFactory(), info));
}
/**
* Constructs an HTTPS connection to the specified URL using specified
* SSL socket factory.
*
* @param urlStr the URL to connect to.
* @param factory factory for creating SSL sockets
*/
private HamsamHttpsConnection(String urlStr, SSLTunnelSocketFactory factory) throws MalformedURLException
{
this.url = new URL(urlStr);
String protocol = url.getProtocol();
if(protocol == null || !protocol.toLowerCase().equals("https"))
throw new MalformedURLException("Invalid HTTPS URL: " + urlStr);
this.factory = factory;
this.requestHeaders = new HashMap();
setMethod("GET");
setHeaderField("Host", url.getHost());
setHeaderField("Connection", "Keep-Alive");
setHeaderField("Cache-Control", "no-cache");
}
public void doRequest() throws UnknownHostException, IOException
{
// Open a socket
int port = url.getPort();
port = port == -1 ? url.getDefaultPort() : port;
SSLSocket sock = (SSLSocket) factory.createSocket(url.getHost(), port);
sock.startHandshake();
// Now send the request
PrintStream out = new PrintStream(sock.getOutputStream());
out.println(getMethod() + " " + url.getPath() + " HTTP/1.1");
for(Iterator i = requestHeaders.keySet().iterator(); i.hasNext();)
{
String key = (String) i.next();
String val = (String) requestHeaders.get(key);
out.println(key + ": " + val);
}
out.println();
out.flush();
// Read the response
this.responseHeaders = new HashMap();
BufferedReader in = new BufferedReader(new InputStreamReader(sock.getInputStream()));
// Status line
String line = in.readLine();
if(line != null)
responseHeaders.put(null, line);
// Check for 3xx status lines
boolean redirected = false;
if(line.startsWith("HTTP/1.1 30"))
redirected = true;
// Response Headers
line = in.readLine();
while(line != null)
{
if(line.equals(""))
break;
int pos = line.indexOf(":");
String key = line.substring(0, pos).trim();
String val = line.substring(pos + 1).trim();
responseHeaders.put(key, val);
line = in.readLine();
}
// TODO read body
in.close();
out.close();
if(redirected)
{
redirection = new HamsamHttpsConnection(getHeaderField("Location"), factory);
redirection.requestHeaders = this.requestHeaders;
redirection.doRequest();
}
}
/**
* Returns a header field from the HTTP response.
*
* @param key the header key
* @return corresponding header value, or <code>null</code> if none found.
* @throws IllegalStateException if the HTTP request is not already sent.
*/
public String getHeaderField(String key)
{
if(this.responseHeaders == null)
throw new IllegalStateException("Cannot get headers before sending request.");
else if(this.redirection != null)
return this.redirection.getHeaderField(key);
else
return (String) this.responseHeaders.get(key);
}
/**
* Set an HTTP header to be sent while sending the request.
*
* @param key the header key
* @param val the header value
*/
public void setHeaderField(String key, String val)
{
this.requestHeaders.put(key, val);
}
/**
* Returns the HTTP method that is used while sending a request.
*
* @return the HTTP method.
*/
public String getMethod()
{
return method;
}
/**
* Sets the HTTP method to be used while sending a request.
*
* @param method the HTTP method
*/
public void setMethod(String method)
{
this.method = method;
}
}