package org.appwork.utils.net.httpconnection;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PushbackInputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.URL;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.zip.GZIPInputStream;
import org.appwork.utils.LowerCaseHashMap;
import org.appwork.utils.Regex;
import org.appwork.utils.net.ChunkedInputStream;
import org.appwork.utils.net.CountingOutputStream;
public class HTTPConnectionImpl implements HTTPConnection {
protected LinkedHashMap<String, String> requestProperties = null;
protected long[] ranges;
protected String customcharset = null;
protected Socket httpSocket = null;
protected URL httpURL = null;
protected HTTPProxy proxy = null;
protected String httpPath = null;
protected RequestMethod httpMethod = RequestMethod.GET;
protected LowerCaseHashMap<List<String>> headers = null;
protected int httpResponseCode = -1;
protected String httpResponseMessage = "";
protected int readTimeout = 30000;
protected int connectTimeout = 30000;
protected long requestTime = -1;
protected OutputStream outputStream = null;
protected InputStream inputStream = null;
protected InputStream convertedInputStream = null;
protected boolean inputStreamConnected = false;
protected String httpHeader = null;
protected boolean outputClosed = false;
private boolean contentDecoded = true;
protected long postTodoLength = -1;
public HTTPConnectionImpl(final URL url) {
this(url, null);
}
public HTTPConnectionImpl(final URL url, final HTTPProxy p) {
this.httpURL = url;
this.proxy = p;
this.requestProperties = new LinkedHashMap<String, String>();
this.addHostHeader();
this.headers = new LowerCaseHashMap<List<String>>();
}
/* this will add Host header at the beginning */
protected void addHostHeader() {
final int defaultPort = this.httpURL.getDefaultPort();
final int usedPort = this.httpURL.getPort();
String port = "";
if (usedPort != -1 && defaultPort != -1 && usedPort != defaultPort) {
port = ":" + usedPort;
}
this.requestProperties.put("Host", this.httpURL.getHost() + port);
}
public void connect() throws IOException {
if (this.isConnected()) { return;/* oder fehler */
}
InetAddress hosts[] = null;
try {
/* resolv all possible ip's */
hosts = InetAddress.getAllByName(this.httpURL.getHost());
} catch (final UnknownHostException e) {
throw e;
}
/* try all different ip's until one is valid and connectable */
IOException ee = null;
for (final InetAddress host : hosts) {
if (this.httpURL.getProtocol().startsWith("https")) {
this.httpSocket = TrustALLSSLFactory.getSSLFactoryTrustALL().createSocket();
} else {
this.httpSocket = new Socket();
}
this.httpSocket.setSoTimeout(this.readTimeout);
this.httpResponseCode = -1;
int port = this.httpURL.getPort();
if (port == -1) {
port = this.httpURL.getDefaultPort();
}
final long startTime = System.currentTimeMillis();
if (this.proxy != null && !this.proxy.getType().equals(HTTPProxy.TYPE.DIRECT)) {
throw new RuntimeException("Invalid Direct Proxy");
} else {
if (this.proxy != null) {
/* bind socket to given interface */
try {
if (this.proxy.getLocalIP() == null) { throw new IOException("Invalid localIP"); }
this.httpSocket.bind(new InetSocketAddress(this.proxy.getLocalIP(), 0));
} catch (final IOException e) {
this.proxy.setStatus(HTTPProxy.STATUS.OFFLINE);
throw new ProxyConnectException(e.getMessage());
}
}
try {
/* try to connect to given host now */
this.httpSocket.connect(new InetSocketAddress(host, port), this.connectTimeout);
this.requestTime = System.currentTimeMillis() - startTime;
ee = null;
break;
} catch (final IOException e) {
try {
this.httpSocket.close();
} catch (final Throwable nothing) {
}
ee = e;
}
}
}
if (ee != null) { throw ee; }
this.httpPath = new org.appwork.utils.Regex(this.httpURL.toString(), "https?://.*?(/.+)").getMatch(0);
if (this.httpPath == null) {
this.httpPath = "/";
}
/* now send Request */
final StringBuilder sb = new StringBuilder();
sb.append(this.httpMethod.name()).append(' ').append(this.httpPath).append(" HTTP/1.1\r\n");
for (final String key : this.requestProperties.keySet()) {
if (this.requestProperties.get(key) == null) {
continue;
}
if ("Content-Length".equalsIgnoreCase(key)) {
this.postTodoLength = Long.parseLong(this.requestProperties.get(key));
}
sb.append(key).append(": ").append(this.requestProperties.get(key)).append("\r\n");
}
sb.append("\r\n");
this.httpSocket.getOutputStream().write(sb.toString().getBytes("UTF-8"));
this.httpSocket.getOutputStream().flush();
if (this.httpMethod != RequestMethod.POST) {
this.outputStream = this.httpSocket.getOutputStream();
this.outputClosed = true;
this.connectInputStream();
} else {
this.outputStream = new CountingOutputStream(this.httpSocket.getOutputStream());
}
}
protected synchronized void connectInputStream() throws IOException {
if (this.httpMethod == RequestMethod.POST) {
final long done = ((CountingOutputStream) this.outputStream).transferedBytes();
if (done != this.postTodoLength) { throw new IOException("Content-Length" + this.postTodoLength + " does not match send " + done + " bytes"); }
}
if (this.inputStreamConnected) { return; }
if (this.httpMethod == RequestMethod.POST) {
/* flush outputstream in case some buffers are not flushed yet */
this.outputStream.flush();
}
this.inputStreamConnected = true;
/* first read http header */
ByteBuffer header = HTTPConnectionUtils.readheader(this.httpSocket.getInputStream(), true);
byte[] bytes = new byte[header.limit()];
header.get(bytes);
this.httpHeader = new String(bytes, "ISO-8859-1").trim();
/* parse response code/message */
if (this.httpHeader.startsWith("HTTP")) {
final String code = new Regex(this.httpHeader, "HTTP.*? (\\d+)").getMatch(0);
if (code != null) {
this.httpResponseCode = Integer.parseInt(code);
}
this.httpResponseMessage = new Regex(this.httpHeader, "HTTP.*? \\d+ (.+)").getMatch(0);
if (this.httpResponseMessage == null) {
this.httpResponseMessage = "";
}
} else {
this.httpHeader = "unknown HTTP response";
this.httpResponseCode = 200;
this.httpResponseMessage = "unknown HTTP response";
this.inputStream = new PushbackInputStream(this.httpSocket.getInputStream(), bytes.length);
/*
* push back the data that got read because no http header exists
*/
((PushbackInputStream) this.inputStream).unread(bytes);
return;
}
/* read rest of http headers */
header = HTTPConnectionUtils.readheader(this.httpSocket.getInputStream(), false);
bytes = new byte[header.limit()];
header.get(bytes);
String temp = new String(bytes, "UTF-8");
/* split header into single strings, use RN or N(buggy fucking non rfc) */
String[] headerStrings = temp.split("(\r\n)|(\n)");
temp = null;
for (final String line : headerStrings) {
String key = null;
String value = null;
int index = 0;
if ((index = line.indexOf(": ")) > 0) {
key = line.substring(0, index);
value = line.substring(index + 2);
} else if ((index = line.indexOf(":")) > 0) {
/* buggy servers that don't have :space ARG */
key = line.substring(0, index);
value = line.substring(index + 1);
} else {
key = null;
value = line;
}
List<String> list = this.headers.get(key);
if (list == null) {
list = new ArrayList<String>();
this.headers.put(key, list);
}
list.add(value);
}
headerStrings = null;
final List<String> chunked = this.headers.get("Transfer-Encoding");
if (chunked != null && chunked.size() > 0 && "chunked".equalsIgnoreCase(chunked.get(0))) {
this.inputStream = new ChunkedInputStream(this.httpSocket.getInputStream());
} else {
this.inputStream = this.httpSocket.getInputStream();
}
}
public void disconnect() {
if (this.isConnected()) {
try {
this.httpSocket.close();
} catch (final Throwable e) {
e.printStackTrace();
}
}
}
public String getCharset() {
int i;
if (this.customcharset != null) { return this.customcharset; }
return this.getContentType() != null && (i = this.getContentType().toLowerCase().indexOf("charset=")) > 0 ? this.getContentType().substring(i + 8).trim() : null;
}
@Override
public long getCompleteContentLength() {
this.getRange();
if (this.ranges != null) { return this.ranges[2]; }
return this.getContentLength();
}
public long getContentLength() {
final String length = this.getHeaderField("Content-Length");
if (length != null) { return Long.parseLong(length); }
return -1;
}
public String getContentType() {
final String type = this.getHeaderField("Content-Type");
if (type == null) { return "unknown"; }
return type;
}
public String getHeaderField(final String string) {
final List<String> ret = this.headers.get(string);
if (ret == null || ret.size() == 0) { return null; }
return ret.get(0);
}
public Map<String, List<String>> getHeaderFields() {
return this.headers;
}
public List<String> getHeaderFields(final String string) {
final List<String> ret = this.headers.get(string);
if (ret == null || ret.size() == 0) { return null; }
return ret;
}
public InputStream getInputStream() throws IOException {
this.connect();
this.connectInputStream();
final int code = this.getResponseCode();
if (code >= 200 && code <= 400 || code == 404 || code == 403) {
if (this.convertedInputStream != null) { return this.convertedInputStream; }
if (this.contentDecoded) {
/* we convert different content-encodings to normal inputstream */
final String encoding = this.getHeaderField("Content-Encoding");
if (encoding == null || encoding.length() == 0) {
/* no encoding */
this.convertedInputStream = this.inputStream;
} else if ("gzip".equalsIgnoreCase(encoding)) {
/* gzip encoding */
this.convertedInputStream = new GZIPInputStream(this.inputStream);
} else if ("deflate".equalsIgnoreCase(encoding)) {
/* deflate encoding */
this.convertedInputStream = new java.util.zip.DeflaterInputStream(this.inputStream);
} else {
/* unsupported */
throw new UnsupportedOperationException("Encoding " + encoding + " not supported!");
}
} else {
/* use original inputstream */
this.convertedInputStream = this.inputStream;
}
return this.convertedInputStream;
} else {
throw new IOException(this.getResponseCode() + " " + this.getResponseMessage());
}
}
public OutputStream getOutputStream() throws IOException {
this.connect();
if (this.outputClosed) { throw new IOException("OutputStream no longer available"); }
return this.outputStream;
}
public long[] getRange() {
String range;
if (this.ranges != null) { return this.ranges; }
if ((range = this.getHeaderField("Content-Range")) == null) { return null; }
// bytes 174239-735270911/735270912
final String[] ranges = new Regex(range, ".*?(\\d+).*?-.*?(\\d+).*?/.*?(\\d+)").getRow(0);
if (ranges == null) {
System.err.print(this + "");
return null;
}
this.ranges = new long[] { Long.parseLong(ranges[0]), Long.parseLong(ranges[1]), Long.parseLong(ranges[2]) };
return this.ranges;
}
protected String getRequestInfo() {
final StringBuilder sb = new StringBuilder();
sb.append("-->").append(this.getURL()).append("\r\n");
sb.append("----------------Request------------------\r\n");
sb.append(this.httpMethod.toString()).append(' ').append(this.getURL().getPath()).append((this.getURL().getQuery() != null ? "?" + this.getURL().getQuery() : "")).append(" HTTP/1.1\r\n");
for (final String key : this.getRequestProperties().keySet()) {
final String v = this.getRequestProperties().get(key);
if (v == null) {
continue;
}
sb.append(key);
sb.append(": ");
sb.append(v);
sb.append("\r\n");
}
sb.append("\r\n");
return sb.toString();
}
public RequestMethod getRequestMethod() {
return this.httpMethod;
}
public Map<String, String> getRequestProperties() {
return this.requestProperties;
}
public String getRequestProperty(final String string) {
return this.requestProperties.get(string);
}
public long getRequestTime() {
return this.requestTime;
}
public int getResponseCode() {
return this.httpResponseCode;
}
protected String getResponseInfo() {
final StringBuilder sb = new StringBuilder();
sb.append("----------------Response------------------\r\n");
try {
if (this.isConnected()) {
this.connectInputStream();
}
} catch (final IOException nothing) {
sb.append("----no InputStream available----");
}
sb.append(this.httpHeader).append("\r\n");
for (final Entry<String, List<String>> next : this.getHeaderFields().entrySet()) {
// Achtung cookie reihenfolge ist wichtig!!!
for (int i = next.getValue().size() - 1; i >= 0; i--) {
if (next.getKey() == null) {
sb.append(next.getValue().get(i));
sb.append("\r\n");
} else {
sb.append(next.getKey());
sb.append(": ");
sb.append(next.getValue().get(i));
sb.append("\r\n");
}
}
}
sb.append("\r\n");
return sb.toString();
}
public String getResponseMessage() {
return this.httpResponseMessage;
}
public URL getURL() {
return this.httpURL;
}
public boolean isConnected() {
if (this.httpSocket != null && this.httpSocket.isConnected()) { return true; }
return false;
}
@Override
public boolean isContentDecoded() {
return this.contentDecoded;
}
public boolean isContentDisposition() {
return this.getHeaderField("Content-Disposition") != null;
}
public boolean isOK() {
if (this.getResponseCode() > -2 && this.getResponseCode() < 400) { return true; }
return false;
}
public void setCharset(final String Charset) {
this.customcharset = Charset;
}
public void setConnectTimeout(final int connectTimeout) {
this.connectTimeout = connectTimeout;
}
@Override
public void setContentDecoded(final boolean b) {
if (this.convertedInputStream != null) { throw new IllegalStateException("InputStream already in use!"); }
this.contentDecoded = b;
}
public void setReadTimeout(final int readTimeout) {
try {
if (this.isConnected()) {
this.httpSocket.setSoTimeout(readTimeout);
}
this.readTimeout = readTimeout;
} catch (final Throwable e) {
e.printStackTrace();
}
}
public void setRequestMethod(final RequestMethod method) {
this.httpMethod = method;
}
public void setRequestProperty(final String key, final String value) {
this.requestProperties.put(key, value);
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append(this.getRequestInfo());
sb.append(this.getResponseInfo());
return sb.toString();
}
}