/
com/planet_ink/coffee_mud/Abilities/Common/
com/planet_ink/coffee_mud/Abilities/Diseases/
com/planet_ink/coffee_mud/Abilities/Druid/
com/planet_ink/coffee_mud/Abilities/Fighter/
com/planet_ink/coffee_mud/Abilities/Languages/
com/planet_ink/coffee_mud/Abilities/Misc/
com/planet_ink/coffee_mud/Abilities/Prayers/
com/planet_ink/coffee_mud/Abilities/Properties/
com/planet_ink/coffee_mud/Abilities/Skills/
com/planet_ink/coffee_mud/Abilities/Songs/
com/planet_ink/coffee_mud/Abilities/Spells/
com/planet_ink/coffee_mud/Abilities/Thief/
com/planet_ink/coffee_mud/Abilities/Traps/
com/planet_ink/coffee_mud/Behaviors/
com/planet_ink/coffee_mud/CharClasses/interfaces/
com/planet_ink/coffee_mud/Commands/
com/planet_ink/coffee_mud/Commands/interfaces/
com/planet_ink/coffee_mud/Common/
com/planet_ink/coffee_mud/Common/interfaces/
com/planet_ink/coffee_mud/Exits/interfaces/
com/planet_ink/coffee_mud/Items/Armor/
com/planet_ink/coffee_mud/Items/Basic/
com/planet_ink/coffee_mud/Items/CompTech/
com/planet_ink/coffee_mud/Items/MiscMagic/
com/planet_ink/coffee_mud/Items/Weapons/
com/planet_ink/coffee_mud/Items/interfaces/
com/planet_ink/coffee_mud/Libraries/
com/planet_ink/coffee_mud/Libraries/interfaces/
com/planet_ink/coffee_mud/Locales/
com/planet_ink/coffee_mud/MOBS/
com/planet_ink/coffee_mud/Races/
com/planet_ink/coffee_mud/Races/interfaces/
com/planet_ink/coffee_mud/WebMacros/
com/planet_ink/coffee_mud/WebMacros/interfaces/
com/planet_ink/coffee_mud/core/
com/planet_ink/coffee_mud/core/collections/
com/planet_ink/coffee_mud/core/interfaces/
com/planet_ink/coffee_mud/core/intermud/
com/planet_ink/coffee_mud/core/intermud/i3/
com/planet_ink/coffee_web/server/
com/planet_ink/siplet/applet/
lib/
resources/factions/
resources/fakedb/
resources/progs/autoplayer/
resources/quests/holidays/
web/
web/admin.templates/
web/admin/grinder/
web/admin/images/
web/clan.templates/
web/pub.templates/
web/pub/images/mxp/
web/pub/sounds/
web/pub/textedit/
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;
	}
}