package com.jpexs.proxy;
import java.io.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.*;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import javax.net.ssl.SSLHandshakeException;
class Handler implements Runnable {
static final boolean DEBUG = false;
Client client = null;
Socket socket = null;
Request request = null;
Reply reply = null;
HttpRelay http = null;
int currentLength = -1;
int contentLength = -1;
long idle = 0;
double bytesPerSecond = 0;
List<Replacement> replacements;
CatchedListener catchedListener;
List<String> catchedContentTypes;
ReplacedListener replacedListener;
static int curId = 0;
int id;
/**
* Create a Handler.
*/
Handler(Socket socket, List<Replacement> replacements, List<String> catchedContentTypes, CatchedListener catchedListener, ReplacedListener replacedListener) {
curId++;
id = curId;
this.socket = socket;
this.replacements = replacements;
this.catchedListener = catchedListener;
this.catchedContentTypes = catchedContentTypes;
this.replacedListener = replacedListener;
}
/**
* Close all connections associated with this handler.
*/
synchronized void close() {
if (client != null) {
client.close();
client = null;
}
if (http != null) {
http.close();
http = null;
}
}
/**
* Flush all data to the client.
*/
void flush() {
if (client != null) {
try {
client.getOutputStream().flush();
} catch (IOException e) {
}
}
}
public void run() {
boolean keepAlive = false;
Exception reason = null;
Thread.currentThread().setName("Handler("
+ socket.getInetAddress().getHostAddress()
+ ")");
try {
client = new Client(socket);
client.setTimeout(ProxyConfig.readTimeout);
} catch (IOException e) {
return;
}
try {
boolean secure = false;
int securePort = 443;
String secureServer = "";
do {
request = null;
reply = null;
idle = System.currentTimeMillis();
try {
request = client.read();
if (secure) {
request.addSecureHostToURL(secureServer);
}
} catch (SSLHandshakeException she) {
she.printStackTrace();
break;
} catch (IOException e) {
break;
}
if (request.getCommand().equals("CONNECT")) {
secureServer = request.getHost();
securePort = request.getPort();
if ((ProxyConfig.httpsMode == ProxyConfig.HTTPS_FILTER) || ((ProxyConfig.httpsMode == ProxyConfig.HTTPS_FILTERLIST) && (ProxyConfig.enabledHttpsServers.contains(secureServer)))) {
secure = true;
reply = new Reply();
reply.statusLine = "HTTP/1.0 200 Connection established";
reply.setHeaderField("Proxy-agent", ProxyConfig.appName);
try {
client.write(reply);
} catch (IOException ex) {
}
client.promoteToServerSSL();
keepAlive = true;
continue;
}
}
idle = 0;
try {
keepAlive = processRequest(secure, secureServer, securePort);
} catch (IOException ioe) {
reason = ioe;
keepAlive = false;
}
if (request != null && reply != null) {
// XXX insert the number of bytes read into the
// reply content-length for logging.
if (reply != null && currentLength > 0) {
reply.setHeaderField("Content-length", currentLength);
}
}
} while (keepAlive);
} finally {
}
if (reason != null && reason.getMessage().indexOf("Broken pipe") == -1) {
if (client != null && request != null) {
error(client.getOutputStream(), reason, request);
}
}
close();
}
boolean processRequest(boolean secure, String secureHost, int securePort) throws IOException {
boolean keepAlive = false;
while (reply == null) {
//boolean secure = false;
boolean uncompress = false;
if (request.getCommand().equals("CONNECT")) {
secure = true;
} else if (request.getURL().startsWith("/")) {
} else if (request.getURL().startsWith("https://") && (secure)) {
} else if (!request.getURL().startsWith("http://")) {
return false;
}
/* Client wants Keep-Alive */
if (ProxyConfig.proxyKeepAlive) {
keepAlive = (request.containsHeaderField("Proxy-Connection")
&& request.getHeaderField("Proxy-Connection").equals("Keep-Alive"));
}
/* Filter the request. */
//deleted
/* None found. Use http or https relay. */
if (secure) {
http = createHttpsRelay(secureHost, securePort);
} else {
http = createHttpRelay();
}
try {
http.sendRequest(request);
if (http instanceof Http) {
((Http) http).setTimeout(ProxyConfig.readTimeout);
}
reply = http.recvReply(request);
} catch (RetryRequestException e) {
http.close();
http = null;
continue; /* XXX */
}
/* Guess content-type if there aren't any headers.
Probably an upgraded HTTP/0.9 reply. */
if (reply.headerCount() == 0) {
String url = request.getURL();
if (url.endsWith("/")
|| url.endsWith(".html") || url.endsWith(".htm")) {
reply.setHeaderField("Content-type", "text/html");
}
}
/* Filter the reply. */
if (false) {
/* uncompress gzip encoded html so it can be filtered */
if (!ProxyConfig.dontUncompress
&& "text/html".equals(reply.getHeaderField("Content-type"))) {
String encoding = reply.getHeaderField("Content-Encoding");
if (encoding != null && encoding.indexOf("gzip") != -1) {
reply.removeHeaderField("Content-Encoding");
reply.removeHeaderField("Content-length");
uncompress = true;
}
}
//filter(reply);
}
if (request.containsHeaderField("Connection") && (request.getHeaderField("Connection").toLowerCase().equals("keep-alive")) && reply.containsHeaderField("Connection")
&& reply.getHeaderField("Connection").equals("Keep-Alive")) {
keepAlive = true;
}
reply.removeHeaderField("Proxy-Connection");
if (keepAlive && reply.containsHeaderField("Content-length")) {
reply.setHeaderField("Proxy-Connection", "Keep-Alive");
} else {
keepAlive = false;
}
currentLength = -1;
contentLength = -1;
try {
contentLength = Integer.parseInt(reply.getHeaderField("Content-length"));
} catch (NumberFormatException e) {
}
if (http instanceof HttpsThrough) {
HttpsThrough https = (HttpsThrough) http;
int timeout = ProxyConfig.readTimeout;
client.write(reply);
try {
client.setTimeout(timeout);
https.setTimeout(timeout);
Copy cp = new Copy(client.getInputStream(), https.getOutputStream());
ReusableThread thread = Server.getThread();
thread.setRunnable(cp);
flushCopy(https.getInputStream(), client.getOutputStream(), -1, true);
client.close();
} catch (InterruptedIOException iioe) {
// ignore socket timeout exceptions
}
} else if (reply.hasContent()) {
try {
processContent(uncompress);
} catch (IOException e) {
if (http instanceof Http) {
((Http) http).reallyClose();
} else {
http.close();
}
http = null;
client.close();
client = null;
throw e;
//return false; /* XXX */
}
/* Document contains no data. */
if (contentLength == 0) {
client.close();
}
} else {
client.write(reply);
}
http.close();
}
return keepAlive;
}
HttpRelay createHttpsRelay(String secureHost, int securePort) throws IOException {
HttpRelay http;
if ((ProxyConfig.httpsMode == ProxyConfig.HTTPS_FILTER) || ((ProxyConfig.httpsMode == ProxyConfig.HTTPS_FILTERLIST) && (ProxyConfig.enabledHttpsServers.contains(secureHost)))) {
if (ProxyConfig.useHTTPSProxy) {
http = Https.open(ProxyConfig.httpsProxyHost, ProxyConfig.httpsProxyPort, true);
Request connectReq = new Request(null);
connectReq.setStatusLine("CONNECT " + secureHost + ":" + securePort + " HTTP/1.1");
connectReq.setCommand("CONNECT");
connectReq.setURL(secureHost + ":" + securePort);
connectReq.setProtocol("HTTP/1.1");
try {
http.sendRequest(connectReq);
Reply rep = http.recvReply(connectReq);
} catch (RetryRequestException ex) {
}
((Https) http).promoteToClientSSL();
} else {
http = Https.open(secureHost, securePort, false);
((Https) http).promoteToClientSSL();
}
/*http = new Http(request.getHost(),request.getPort(),ProxyConfig.useHTTPSProxy);
if(ProxyConfig.useHTTPSProxy){
Request connectReq=new Request(client);
connectReq.setCommand("CONNECT");
connectReq.setURL(secureHost+":"+securePort);
connectReq.setProtocol("HTTP/1.1");
try {
http.sendRequest(connectReq);
http.recvReply(connectReq);
} catch (RetryRequestException ex) {
}
}
((Http)http).promoteToClientSSL();*/
} else {
if (ProxyConfig.useHTTPSProxy) {
http = new HttpsThrough(ProxyConfig.httpsProxyHost,
ProxyConfig.httpsProxyPort,
true);
} else {
http = new HttpsThrough(secureHost, securePort);
}
}
return http;
}
HttpRelay createHttpRelay() throws IOException {
HttpRelay http;
if (ProxyConfig.useHTTPProxy) {
http = Http.open(ProxyConfig.httpProxyHost,
ProxyConfig.httpProxyPort,
true);
} else {
http = Http.open(request.getHost(), request.getPort());
}
return http;
}
InputStream readChunkedTransfer(InputStream in) throws IOException {
ByteArrayOutputStream chunks = new ByteArrayOutputStream(8192);
int size = 0;
contentLength = 0;
while ((size = reply.getChunkSize(in)) > 0) {
contentLength += size;
copy(in, chunks, size, true);
reply.readLine(in);
}
reply.getChunkedFooter(in);
reply.removeHeaderField("Transfer-Encoding");
reply.setHeaderField("Content-length", contentLength);
return new ByteArrayInputStream(chunks.toByteArray());
}
private static DateFormat httpDateFormat() {
DateFormat httpDateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
httpDateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
return httpDateFormat;
}
void disableReplyCaching() {
reply.removeHeaderField("Cache-Control");
reply.removeHeaderField("Last-Modified");
reply.removeHeaderField("Expires");
reply.removeHeaderField("Date");
reply.removeHeaderField("ETag");
reply.removeHeaderField("Pragma");
reply.setHeaderField("Pragma", "no-cache");
reply.setHeaderField("Cache-Control", "no-cache, must-revalidate");
Calendar now = Calendar.getInstance();
reply.setHeaderField("Expires", httpDateFormat().format(now.getTime()));//"Sat, 26 Jul 1997 05:00:00 GMT");
reply.setHeaderField("Last-Modified", "Sat, 26 Jul 1997 05:00:00 GMT");
}
void processContent(boolean uncompress) throws IOException {
InputStream in;
boolean chunked = false;
if (reply.containsHeaderField("Transfer-Encoding")
&& reply.getTransferEncoding().equals("chunked")) {
in = readChunkedTransfer(reply.getContent());
chunked = true;
} else {
in = reply.getContent();
}
if (in == null) {
return;
} else if (uncompress) {
in = new GZIPInputStream(in);
}
String url = request.getURL();
boolean replaced = false;
for (Replacement r : replacements) {
if (r.matches(url)) {
r.lastAccess = Calendar.getInstance();
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
try {
FileInputStream fis = new FileInputStream(r.targetFile);
byte[] buf = new byte[4096];
int pos = 0;
while ((pos = fis.read(buf)) > 0) {
buffer.write(buf, 0, pos);
}
fis.close();
buffer.close();
} catch (IOException ex) {
}
byte[] bytes = buffer.toByteArray();
contentLength = bytes.length;
reply.setHeaderField("Content-length", contentLength);
disableReplyCaching();
client.write(reply);
copy(new ByteArrayInputStream(bytes),
client.getOutputStream(), contentLength, false);
replaced = true;
if (replacedListener != null) {
replacedListener.replaced(r, request.getURL(), reply.getContentType());
}
break;
}
}
if (!replaced) {
String contentType = reply.getHeaderField("Content-type");
if (reply.getStatusCode() == 200) {
if (contentType != null) {
for (String ct : catchedContentTypes) {
String convContentType = contentType;
if (convContentType.contains(";")) {
convContentType = convContentType.substring(0, convContentType.indexOf(";"));
}
if (ct.toLowerCase().equals(convContentType.toLowerCase())) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
copy(in, baos, contentLength, true);
byte[] data = baos.toByteArray();
if (catchedListener != null) {
byte[] newData = catchedListener.catched(ct, request.getURL(), new ByteArrayInputStream(data));
if (newData != null) {
data = newData;
contentLength = data.length;
reply.setHeaderField("Content-length", contentLength);
}
}
in = new ByteArrayInputStream(data);
disableReplyCaching();
break;
}
}
}
}
client.write(reply);
copy(in, client.getOutputStream(), contentLength, true);
}
}
/**
* Return the content length.
*/
int getTotalBytes() {
return contentLength > 0 ? contentLength : 0;
}
/**
* Return the number of bytes read so far.
*/
int getCurrentBytes() {
return currentLength > 0 ? currentLength : 0;
}
/**
* Send a error message to the client.
*
* @param out client
* @param e exception that occurred
* @param r request
*/
void error(OutputStream out, Exception e, Request r) {
StringBuffer buf = new StringBuffer();
buf.append("While trying to retrieve the URL: <a href=\"" + r.getURL() + "\">" + r.getURL() + "</a>\r\n");
buf.append("<p>\r\nThe following error was encountered:\r\n<p>\r\n");
buf.append("<ul><li>" + e.toString() + "</ul>\r\n");
String s = new HttpError(400, buf.toString()).toString();
try {
out.write(s.getBytes(), 0, s.length());
out.flush();
} catch (Exception ex) {
}
}
/**
* Copy in to out.
*
* @param in InputStream
* @param out OutputStream
* @param monitored Update the Monitor
*/
void copy(InputStream in, OutputStream out, int length, boolean monitored)
throws IOException {
if (length == 0) {
return;
}
int n;
byte[] buffer = new byte[8192];
long start = System.currentTimeMillis();
long now = 0, then = start;
bytesPerSecond = 0;
if (monitored) {
currentLength = 0;
}
for (;;) {
n = (length > 0) ? Math.min(length, buffer.length) : buffer.length;
n = in.read(buffer, 0, n);
if (n < 0) {
break;
}
out.write(buffer, 0, n);
if (monitored) {
currentLength += n;
}
now = System.currentTimeMillis();
bytesPerSecond = currentLength / ((now - start) / 1000.0);
// flush after 1 second
if (now - then > 1000) {
out.flush();
}
if (length != -1) {
length -= n;
if (length == 0) {
break;
}
}
then = now;
}
out.flush();
}
/**
* Copy in to out.
*
* @param in InputStream
* @param out OutputStream
* @param monitored Update the Monitor
*/
void flushCopy(InputStream in, OutputStream out, int length, boolean monitored)
throws IOException {
if (length == 0) {
return;
}
int n;
byte[] buffer = new byte[8192];
long start = System.currentTimeMillis();
bytesPerSecond = 0;
if (monitored) {
currentLength = 0;
}
for (;;) {
n = (length > 0) ? Math.min(length, buffer.length) : buffer.length;
n = in.read(buffer, 0, n);
if (n < 0) {
break;
}
out.write(buffer, 0, n);
out.flush();
if (monitored) {
currentLength += n;
}
bytesPerSecond = currentLength / ((System.currentTimeMillis() - start) / 1000.0);
if (length != -1) {
length -= n;
if (length == 0) {
break;
}
}
}
out.flush();
}
/**
* Return a string represenation of the hander's state.
*/
public String toString() {
StringBuffer str = new StringBuffer();
str.append("CLIENT ");
str.append(socket.getInetAddress().getHostAddress());
str.append(":");
str.append(socket.getPort());
str.append(" - ");
if (request == null) {
str.append("idle " + ((System.currentTimeMillis() - idle) / 1000.0) + " sec");
} else {
if (reply != null && currentLength > 0) {
str.append("(");
str.append(currentLength);
if (contentLength > 0) {
str.append("/");
str.append(contentLength);
}
str.append(" ");
str.append(((int) bytesPerSecond / 1024) + " kB/s");
str.append(") ");
}
str.append(request.getURL());
}
return str.toString();
}
}