mirror of
https://git.huckle.dev/Huckles-Minecraft-Archive/jpexs-decompiler.git
synced 2026-05-23 11:25:54 +00:00
666 lines
22 KiB
Java
666 lines
22 KiB
Java
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();
|
|
}
|
|
}
|