package com.planet_ink.coffee_mud.Common; import com.planet_ink.coffee_mud.core.interfaces.*; import com.planet_ink.coffee_mud.core.*; import com.planet_ink.coffee_mud.core.collections.*; import com.planet_ink.coffee_mud.core.exceptions.CMException; import com.planet_ink.coffee_mud.core.exceptions.CoffeeMudException; import com.planet_ink.coffee_mud.Abilities.interfaces.*; import com.planet_ink.coffee_mud.Areas.interfaces.*; import com.planet_ink.coffee_mud.Behaviors.interfaces.*; import com.planet_ink.coffee_mud.CharClasses.interfaces.*; import com.planet_ink.coffee_mud.Commands.interfaces.*; import com.planet_ink.coffee_mud.Common.interfaces.*; import com.planet_ink.coffee_mud.Exits.interfaces.*; import com.planet_ink.coffee_mud.Items.interfaces.*; import com.planet_ink.coffee_mud.Libraries.interfaces.*; import com.planet_ink.coffee_mud.Locales.interfaces.*; import com.planet_ink.coffee_mud.MOBS.interfaces.*; import com.planet_ink.coffee_mud.Races.interfaces.*; import java.net.*; import java.io.*; import java.util.*; /* Copyright 2013-2016 Bo Zimmerman Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ public class DefaultHttpClient implements HttpClient, Cloneable { @Override public String ID(){return "DefaultHttpClient";} @Override public String name() { return ID();} private static enum HState { PREHEAD, INHEAD, INBODY, PRECHUNK, INCHUNK, POSTINCHUNK, POSTCHUNK } private volatile int tickStatus=Tickable.STATUS_NOT; protected Map<String,String> reqHeaders=new CaselessTreeMap<String>(); protected Map<String,List<String>> respHeaders=new CaselessTreeMap<List<String>>(); protected Socket sock = null; protected OutputStream out = null; protected InputStream in = null; protected Method meth = Method.GET; protected int connectTimeout=10000; protected int readTimeout=10000; protected int maxReadBytes=0; protected byte[] outBody=null; protected Integer respStatus=null; @Override public CMObject newInstance() { try { return getClass().newInstance(); } catch(final Exception e) { return new DefaultHttpClient(); } } @Override public void initializeClass(){} @Override public HttpClient header(String key, String value) { reqHeaders.put(key, value); return this; } @Override public HttpClient method(Method meth) { if(meth!=null) { this.meth=meth; } return this; } @Override public HttpClient body(String body) { if(body!=null) { this.outBody=body.getBytes(); } return this; } @Override public HttpClient body(byte[] body) { if(body!=null) { this.outBody=body; } return this; } @Override public HttpClient reset() { reqHeaders.clear(); respHeaders.clear(); respStatus=null; return this; } @Override public HttpClient connectTimeout(int ms) { this.connectTimeout=ms; return this; } @Override public HttpClient readTimeout(int ms) { this.readTimeout=ms; return this; } @Override public HttpClient maxReadBytes(int bytes) { this.maxReadBytes=bytes; return this; } protected void conditionalHeader(String key, String value, List<String> clearSet) { if(!reqHeaders.containsKey(key)) { reqHeaders.put(key, value); clearSet.add(key); } } @Override public int getResponseCode() { if(this.respStatus!=null) return this.respStatus.intValue(); return -1; } @Override public Map<String,List<String>> getResponseHeaders() { return this.respHeaders; } @Override public HttpClient doRequest(String url) throws IOException { respHeaders.clear(); respStatus=null; outBody=null; if(url == null) throw new IOException("Bad url"); final boolean ssl=url.toLowerCase().startsWith("https"); if(ssl) throw new IOException("Unsupported: ssl"); int x=url.indexOf("://"); if(x>=0) url=url.substring(x+3); String host; String rest="/"; x=url.indexOf('/'); if(x<0) x=url.indexOf('?'); if(x>=0) { host=url.substring(0,x); rest=url.substring(x); } else host=url; int port=ssl?443:80; x=host.indexOf(':'); if(x>=0) { port=CMath.s_int(host.substring(x+1),80); host=host.substring(0,x); } final List<String> onesToClear=new Vector<String>(); conditionalHeader("Host",host,onesToClear); conditionalHeader("Connection","Keep-Alive",onesToClear); conditionalHeader("Accept","*/*",onesToClear); final int len=(outBody!=null)?outBody.length:0; conditionalHeader("Content-Length",""+len,onesToClear); if(sock == null) { sock=new Socket(); sock.connect(new InetSocketAddress(host,port), this.connectTimeout); in=sock.getInputStream(); sock.setSoTimeout(10); out=sock.getOutputStream(); } final IOException cleanException=new IOException("Connection closed by remote host"); try { while(in.read()!=-1){} /* clear the stream */ throw cleanException; } catch(final IOException e) { if(e==cleanException) throw e; } out.write((meth.toString()+" "+rest+" HTTP/1.1\r\n").getBytes()); for(final String key : reqHeaders.keySet()) out.write((key+": "+reqHeaders.get(key)+"\r\n").getBytes()); for(final String key : onesToClear) reqHeaders.remove(key); out.write("\r\n".getBytes()); if(outBody!=null) out.write(outBody); long nextReadTimeout=(this.readTimeout>0)?(System.currentTimeMillis()+this.readTimeout):Long.MAX_VALUE; int lastC=-1; HState state=HState.PREHEAD; final ByteArrayOutputStream bodyBuilder=new ByteArrayOutputStream(); final StringBuilder headBuilder=new StringBuilder(); int c=0; int maxBytes=this.maxReadBytes; int chunkSize=0; while(c!=-1) { try { lastC=c; c=in.read(); if(this.readTimeout>0) nextReadTimeout=(System.currentTimeMillis()+this.readTimeout); } catch(final IOException e) { if(e instanceof java.net.SocketTimeoutException) { if(System.currentTimeMillis()>nextReadTimeout) throw e; continue; } else throw e; } switch(state) { case PREHEAD: { if((c=='\n')&&(lastC=='\r')) { state=HState.INHEAD; final String s=headBuilder.toString(); headBuilder.setLength(0); final String[] parts=s.split(" ", 3); if(CMath.isInteger(parts[1])) respStatus=Integer.valueOf(CMath.s_int(parts[1])); else respStatus=Integer.valueOf(-1); } else if((c!='\n')&&(c!='\r')) headBuilder.append((char)c); break; } case INHEAD: { if((c=='\n')&&(lastC=='\r')) { if(headBuilder.length()==0) { if(respHeaders.containsKey("Transfer-Encoding") &&(respHeaders.get("Transfer-Encoding").contains("chunked") ||respHeaders.get("Transfer-Encoding").contains("Chunked") ||respHeaders.get("Transfer-Encoding").contains("CHUNKED"))) { maxBytes=Integer.MAX_VALUE; state=HState.PRECHUNK; } else if(respHeaders.containsKey("Content-Length")) { final List<String> l=respHeaders.get("Content-Length"); for(final String s : l) if(CMath.isInteger(s)) { final int possMax=CMath.s_int(s); if((maxBytes==0)||(possMax<maxBytes)) maxBytes=possMax; } state=HState.INBODY; } else { c=-1; break; } } else { final String s=headBuilder.toString(); x=s.indexOf(':'); if(x>0) { final String key=s.substring(0,x).trim(); final String value=s.substring(x+1).trim(); List<String> list; if(respHeaders.containsKey(key)) list=respHeaders.get(key); else { list=new ArrayList<String>(); respHeaders.put(key, list); } list.add(value); } } headBuilder.setLength(0); } else if((c!='\r')&&(c!='\n')) headBuilder.append((char)c); break; } case INBODY: { bodyBuilder.write(c); if((maxBytes==0)||(bodyBuilder.size()>=maxBytes)) c=-1; break; } case PRECHUNK: { if((c=='\n')&&(lastC=='\r')) { state=HState.INCHUNK; String szStr=headBuilder.toString().trim(); x=szStr.indexOf(';'); if(x>=0) szStr=szStr.substring(0,x).trim(); x=0; while((x<szStr.length())&&(szStr.charAt(x)=='0')) x++; if(x<szStr.length()) { chunkSize = Integer.parseInt(szStr.substring(x).trim(),16); if(chunkSize==0) state=HState.POSTCHUNK; } else state=HState.POSTCHUNK; headBuilder.setLength(0); } else if((c!='\r')&&(c!='\n')) { headBuilder.append((char)c); } break; } case INCHUNK: { bodyBuilder.write(c); if((--chunkSize)<=0) state=HState.POSTINCHUNK; break; } case POSTINCHUNK: { if((c=='\n')&&(lastC=='\r')) state=HState.PRECHUNK; break; } case POSTCHUNK: { if((c=='\n')&&(lastC=='\r')) { if(headBuilder.length()==0) c=-1; else { final String s=headBuilder.toString(); x=s.indexOf(':'); if(x>0) { final String key=s.substring(0,x).trim(); final String value=s.substring(x+1).trim(); List<String> list; if(respHeaders.containsKey(key)) list=respHeaders.get(key); else { list=new ArrayList<String>(); respHeaders.put(key, list); } list.add(value); } } headBuilder.setLength(0); } else if((c!='\r')&&(c!='\n')) headBuilder.append((char)c); break; } } } this.outBody=bodyBuilder.toByteArray(); return this; } @Override public byte[] getRawUrl(final String urlStr, String cookieStr) { return getRawUrl(urlStr, cookieStr, 1024*1024*10, 10000); } @Override public byte[] getRawUrl(final String urlStr) { return getRawUrl(urlStr, null, 1024*1024*10, 10000); } @Override public byte[] getRawUrl(final String urlStr, final int maxLength, final int readTimeout) { return getRawUrl(urlStr, null, maxLength, readTimeout); } @Override public int getResponseContentLength() { if(this.outBody!=null) return this.outBody.length; return 0; } @Override public InputStream getResponseBody() { if(this.outBody!=null) return new ByteArrayInputStream(this.outBody); return new ByteArrayInputStream(new byte[0]); } @Override public HttpClient doGet(String url) throws IOException { return this.method(Method.GET).doRequest(url); } @Override public HttpClient doHead(String url) throws IOException { return this.method(Method.HEAD).doRequest(url); } @Override public byte[] getRawUrl(final String urlStr, String cookieStr, final int maxLength, final int readTimeout) { HttpClient h=null; try { h=this.readTimeout(readTimeout).connectTimeout(readTimeout).method(Method.GET); if((cookieStr!=null)&&(cookieStr.length()>0)) h=h.header("Cookie", cookieStr); h.doRequest(urlStr); if (h.getResponseCode() == 302) { final InputStream in=h.getResponseBody(); int len=h.getResponseContentLength(); if((len > 0)&&((maxLength==0)||(len<=maxLength))) { final byte[] buffer = new byte[1024]; final ByteArrayOutputStream bout=new ByteArrayOutputStream(); while ((len = in.read(buffer)) != -1) { bout.write(buffer, 0, len); } return bout.toByteArray(); } } if (h.getResponseCode() == 200) { final InputStream in=h.getResponseBody(); int len=h.getResponseContentLength(); if((len > 0)&&((maxLength==0)||(len<=maxLength))) { final byte[] buffer = new byte[1024]; final ByteArrayOutputStream bout=new ByteArrayOutputStream(); while ((len = in.read(buffer)) != -1) { bout.write(buffer, 0, len); } return bout.toByteArray(); } } } catch (final Exception e) { if(e.getMessage()==null) Log.errOut("HttpClient",e); else Log.errOut("HttpClient: "+e.getMessage()+"("+urlStr+")"); return null; } finally { if(h!=null) h.finished(); } return null; } @Override public void finished() { if(sock!=null) { try { sock.shutdownInput(); sock.shutdownOutput(); sock.close(); } catch(final Exception e) {} finally { sock=null; in=null; out=null; } } } @Override public Map<String,List<String>> getHeaders(final String urlStr) { HttpClient h=null; try { h=this.readTimeout(3000).connectTimeout(3000).method(Method.GET); h.doRequest(urlStr); return h.getResponseHeaders(); } catch (final Exception e) { if(e.getMessage()==null) Log.errOut("HttpClient",e); else Log.errOut("HttpClient: "+e.getMessage()+"("+urlStr+")"); return null; } finally { if(h!=null) h.finished(); } } @Override public int getTickStatus() { return tickStatus; } @Override public boolean tick(Tickable ticking, int tickID) { return false; } @Override public CMObject copyOf() { try { return (CMObject)this.clone(); } catch (final CloneNotSupportedException e) { return this; } } @Override public int compareTo(CMObject o) { final int comp=CMClass.classID(this).compareToIgnoreCase(CMClass.classID(o)); return (comp==0)?((this==o)?0:1):comp; } }