package org.appwork.utils.net.httpconnection; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PushbackInputStream; import java.io.UnsupportedEncodingException; 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.Iterator; 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.Base64InputStream; 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; private int[] allowedResponseCodes = new int[0]; private InetSocketAddress proxyInetSocketAddress = null; protected InetSocketAddress connectedInetSocketAddress = null; 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.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 */ } final InetAddress hosts[] = this.resolvHostIP(this.httpURL.getHost()); /* 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")) { /* https */ this.httpSocket = TrustALLSSLFactory.getSSLFactoryTrustALL().createSocket(); /* * http://twelve-programmers.blogspot.de/2010/01/limitations-in-jsse * -tls-implementation.html */ /* * http://stackoverflow.com/questions/6851461/java-why-does-ssl- * handshake-give-could-not-generate-dh-keypair-exception */ // final List<String> limited = new LinkedList<String>(); // for (final String suite : ((SSLSocket) // this.httpSocket).getEnabledCipherSuites()) { // if (!suite.contains("_DHE_")) { // limited.add(suite); // } // } // ((SSLSocket) // this.httpSocket).setEnabledCipherSuites(limited.toArray(new // String[limited.size()])); } else { /* http */ 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.isDirect()) { /* bind socket to given interface */ try { if (this.proxy.getLocalIP() == null) { throw new IOException("Invalid localIP"); } this.httpSocket.bind(this.proxyInetSocketAddress = new InetSocketAddress(this.proxy.getLocalIP(), 0)); } catch (final IOException e) { this.proxyInetSocketAddress = null; throw new ProxyConnectException(e, this.proxy); } } else if (this.proxy != null && this.proxy.isNone()) { /* none is also allowed here */ } else if (this.proxy != null) { throw new RuntimeException("Invalid Direct Proxy"); } try { /* try to connect to given host now */ this.httpSocket.connect(this.connectedInetSocketAddress = new InetSocketAddress(host, port), this.connectTimeout); this.requestTime = System.currentTimeMillis() - startTime; ee = null; break; } catch (final IOException e) { this.connectedInetSocketAddress = null; 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 */ this.sendRequest(); } 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"; if (bytes.length > 0) { 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); } else { /* nothing to push back */ this.inputStream = this.httpSocket.getInputStream(); } 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; } if (key != null) { key = key.trim(); } if (value != null) { value = value.trim(); } 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) { } } } /* * (non-Javadoc) * * @see * org.appwork.utils.net.httpconnection.HTTPConnection#finalizeConnect() */ @Override public void finalizeConnect() throws IOException { this.connect(); this.connectInputStream(); } @Override public int[] getAllowedResponseCodes() { return this.allowedResponseCodes; } 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.trim()); } 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 (this.isOK() || code == 404 || code == 403 || code == 416) { if (this.convertedInputStream != null) { return this.convertedInputStream; } if (this.contentDecoded) { final String encodingTransfer = this.getHeaderField("Content-Transfer-Encoding"); if ("base64".equalsIgnoreCase(encodingTransfer)) { /* base64 encoded content */ this.inputStream = new Base64InputStream(this.inputStream); } /* we convert different content-encodings to normal inputstream */ final String encoding = this.getHeaderField("Content-Encoding"); if (encoding == null || encoding.length() == 0 || "none".equalsIgnoreCase(encoding)) { /* 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() { if (this.ranges != null) { return this.ranges; } String contentRange = this.getHeaderField("Content-Range"); if ((contentRange = this.getHeaderField("Content-Range")) == null) { return null; } String[] range = null; if (contentRange != null) { if ((range = new Regex(contentRange, ".*?(\\d+).*?-.*?(\\d+).*?/.*?(\\d+)").getRow(0)) != null) { /* RFC-2616 */ /* START-STOP/SIZE */ /* Content-Range=[133333332-199999999/200000000] */ final long gotSB = Long.parseLong(range[0]); final long gotEB = Long.parseLong(range[1]); final long gotS = Long.parseLong(range[2]); this.ranges = new long[] { gotSB, gotEB, gotS }; return this.ranges; } else if ((range = new Regex(contentRange, ".*?(\\d+).*?-/.*?(\\d+)").getRow(0)) != null && this.getResponseCode() != 416) { /* only parse this when we have NO 416 (invalid range request) */ /* NON RFC-2616! STOP is missing */ /* * this happend for some stupid servers, seems to happen when * request is bytes=9500- (x till end) */ /* START-/SIZE */ /* content-range: bytes 1020054729-/1073741824 */ final long gotSB = Long.parseLong(range[0]); final long gotS = Long.parseLong(range[1]); this.ranges = new long[] { gotSB, gotS - 1, gotS }; return this.ranges; } else if (this.getResponseCode() == 416 && (range = new Regex(contentRange, ".*?\\*/.*?(\\d+)").getRow(0)) != null) { /* a 416 may respond with content-range * | content.size answer */ this.ranges = new long[] { -1, -1, Long.parseLong(range[0]) }; return this.ranges; } else { /* unknown range header format! */ System.out.println(contentRange + " format is unknown!"); } } return null; } protected String getRequestInfo() { final StringBuilder sb = new StringBuilder(); sb.append("----------------Request Information-------------\r\n"); sb.append("URL: ").append(this.getURL()).append("\r\n"); sb.append("Host: ").append(this.getURL().getHost()).append("\r\n"); if (this.connectedInetSocketAddress != null && this.connectedInetSocketAddress.getAddress() != null) { sb.append("HostIP: ").append(this.connectedInetSocketAddress.getAddress().getHostAddress()).append("\r\n"); } if (this.proxyInetSocketAddress != null && this.proxyInetSocketAddress.getAddress() != null) { sb.append("LocalIP: ").append(this.proxyInetSocketAddress.getAddress().getHostAddress()).append("\r\n"); } sb.append("Connection-Timeout: ").append(this.connectTimeout + "ms").append("\r\n"); sb.append("Read-Timeout: ").append(this.readTimeout + "ms").append("\r\n"); sb.append("----------------Request-------------------------\r\n"); if (this.isConnected()) { sb.append(this.httpMethod.toString()).append(' ').append(this.httpPath).append(" HTTP/1.1\r\n"); final Iterator<Entry<String, String>> it = this.getRequestProperties().entrySet().iterator(); while (it.hasNext()) { final Entry<String, String> next = it.next(); if (next.getValue() == null) { continue; } sb.append(next.getKey()); sb.append(": "); sb.append(next.getValue()); sb.append("\r\n"); } } else { sb.append("-------------Not Connected Yet!-----------------\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 Information------------\r\n"); try { if (this.isConnected()) { sb.append("Connection-Time: ").append(this.requestTime + "ms").append("\r\n"); sb.append("----------------Response------------------------\r\n"); this.connectInputStream(); 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"); } else { sb.append("-------------Not Connected Yet!------------------\r\n"); } } catch (final IOException nothing) { sb.append("----------No InputStream Available!--------------\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() { final int code = this.getResponseCode(); if (code >= 200 && code < 400) { return true; } if (this.isResponseCodeAllowed(code)) { return true; } return false; } protected boolean isResponseCodeAllowed(final int code) { for (final int c : this.allowedResponseCodes) { if (c == code) { return true; } } return false; } protected LinkedHashMap<String, String> putHostToTop(final LinkedHashMap<String, String> oldRequestProperties) { final LinkedHashMap<String, String> newRet = new LinkedHashMap<String, String>(); final String host = oldRequestProperties.remove("Host"); if (host != null) { newRet.put("Host", host); } newRet.putAll(oldRequestProperties); return newRet; } public InetAddress[] resolvHostIP(final String host) throws IOException { InetAddress hosts[] = null; for (int resolvTry = 0; resolvTry < 2; resolvTry++) { try { /* resolv all possible ip's */ hosts = InetAddress.getAllByName(host); return hosts; } catch (final UnknownHostException e) { try { Thread.sleep(500); } catch (final InterruptedException e1) { throw new IOException(e1.toString()); } } } throw new UnknownHostException(host); } protected void sendRequest() throws UnsupportedEncodingException, IOException { /* now send Request */ final StringBuilder sb = new StringBuilder(); sb.append(this.httpMethod.name()).append(' ').append(this.httpPath).append(" HTTP/1.1\r\n"); boolean hostSet = false; /* check if host entry does exist */ for (final String key : this.requestProperties.keySet()) { if ("Host".equalsIgnoreCase(key)) { hostSet = true; break; } } if (hostSet == false) { /* host entry does not exist,lets add it */ this.addHostHeader(); } this.requestProperties = this.putHostToTop(this.requestProperties); final Iterator<Entry<String, String>> it = this.requestProperties.entrySet().iterator(); while (it.hasNext()) { final Entry<String, String> next = it.next(); if (next.getValue() == null) { continue; } if ("Content-Length".equalsIgnoreCase(next.getKey())) { /* content length to check if we send out all data */ this.postTodoLength = Long.parseLong(next.getValue().trim()); } sb.append(next.getKey()).append(": ").append(next.getValue()).append("\r\n"); } sb.append("\r\n"); this.httpSocket.getOutputStream().write(sb.toString().getBytes("ISO-8859-1")); 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()); } } @Override public void setAllowedResponseCodes(final int[] codes) { if (codes == null) { throw new IllegalArgumentException("codes==null"); } this.allowedResponseCodes = codes; } 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(); } }