package org.bench4q.recorder.httpcapture;

import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.ProtocolException;
import java.util.StringTokenizer;

import org.apache.log4j.Logger;

/**
 * Parses and stores a http server request. Originally posted to comp.lang.java
 * in 1996.
 * 
 * @author Sherman Janes
 */
public class HttpRequestHeader {
	private static final Logger log = Logger.getLogger(HttpRequestHeader.class);

	/**
	 * Http Request method. Such as get or post.
	 */
	public String method = new String();

	/**
	 * The requested url. The universal resource locator that hopefully uniquely
	 * describes the object or service the client is requesting.
	 */
	public String url = new String();

	/**
	 * Version of http being used. Such as HTTP/1.0
	 */
	public String version = new String();
	/**
	 * to support http 1.1
	 */
	public String host = new String();
	/**
	 * The client's browser's name.
	 */
	public String userAgent = new String();

	/**
	 * The requesting documents that contained the url link.
	 */
	public String referer = new String();

	/**
	 * A internet address date of the remote copy.
	 */
	public String ifModifiedSince = new String();

	/**
	 * A list of mime types the client can accept.
	 */
	public String accept = new String();
	public String acceptLanguage = new String();
	// public String acceptEncoding = new String();
	/**
	 * The clients authorization. Don't belive it.
	 */
	public String authorization = new String();
	/**
	 * The type of content following the request header. Normally there is no
	 * content and this is blank, however the post method usually does have a
	 * content and a content length.
	 */
	public String contentType = new String();
	/**
	 * The length of the content following the header. Usually blank.
	 */
	public int contentLength = -1;
	/**
	 * The content length of a remote copy of the requested object.
	 */
	public int oldContentLength = -1;
	/**
	 * Anything in the header that was unrecognized by this class.
	 */
	public String unrecognized = new String();

	/**
	 * indicate the request go through proxy
	 */
	public boolean fromProxy = true;
	/**
	 * Indicates that no cached versions of the requested object are to be sent.
	 * Usually used to tell proxy not to send a cached copy. This may also
	 * effect servers that are front end for data bases.
	 */
	public boolean pragmaNoCache = false;

	final static String CR = "\r\n";

	/**
	 * That from which we read.
	 */
	private InputStream input;

	/**
	 * Parses a http header from a stream.
	 * 
	 * @param in
	 *            The stream to parse.
	 */
	public HttpRequestHeader(InputStream in) throws IOException {
		input = in;
		/*
		 * Read by lines
		 */
		StringTokenizer tz = new StringTokenizer(readLine());
		/*
		 * HTTP COMMAND LINE < <METHOD==get> <URL> <HTTP_VERSION> >
		 */
		method = getToken(tz).toUpperCase();
		url = getToken(tz);
		version = getToken(tz);

		while (true) {
			String line = readLine();
			log.trace(line);
			tz = new StringTokenizer(line);
			String Token = getToken(tz);
			// look for termination of HTTP command
			if (0 == Token.length())
				break;
			if (Token.equalsIgnoreCase("Host:")) {
				// line = <Host: host description>
				host = getRemainder(tz);
			} else if (Token.equalsIgnoreCase("USER-AGENT:")) {
				// line =<User-Agent: <Agent Description>>
				userAgent = getRemainder(tz);
			} else if (Token.equalsIgnoreCase("ACCEPT:")) {
				// line=<Accept: <Type>/<Form>
				// examp: Accept image/jpeg
				accept += getRemainder(tz);
			} else if (Token
					.equalsIgnoreCase(HeaderValue.REQUEST_ACCEPT_LANGUAGE + ":")) {
				acceptLanguage += getRemainder(tz);
			} else if (Token.equalsIgnoreCase("REFERER:")) {
				// line =<Referer: <URL>>
				referer = getRemainder(tz);
			} else if (Token.equalsIgnoreCase("PRAGMA:")) {
				// Pragma: <no-cache>
				Token = getToken(tz);
				if (Token.equalsIgnoreCase("NO-CACHE"))
					pragmaNoCache = true;
				else
					unrecognized += "Pragma:" + Token + " " + getRemainder(tz)
							+ "\n";
			} else if (Token.equalsIgnoreCase("AUTHORIZATION:")) {
				// Authenticate: Basic UUENCODED
				authorization = getRemainder(tz);

			} else if (Token.equalsIgnoreCase("IF-MODIFIED-SINCE:")) {
				// line =<If-Modified-Since: <http date>
				// *** Conditional GET replaces HEAD method ***
				String str = getRemainder(tz);
				int index = str.indexOf(";");
				if (index == -1) {
					ifModifiedSince = str;
				} else {
					ifModifiedSince = str.substring(0, index);

					index = str.indexOf("=");
					if (index != -1) {
						str = str.substring(index + 1);
						oldContentLength = Integer.parseInt(str);
					}
				}
			} else if (Token.equalsIgnoreCase("CONTENT-LENGTH:")) {
				Token = getToken(tz);
				contentLength = Integer.parseInt(Token);
			} else if (Token.equalsIgnoreCase("CONTENT-TYPE:")) {
				contentType = getRemainder(tz);
			} else {
				unrecognized += Token + " " + getRemainder(tz) + CR;
			}
		}
	}

	/**
	 * We would like to use BufferedReader.readLine(), but it may read past the
	 * header while filling its buffer. Anything it over-read would thus be
	 * missed from the body. (Reading the body cannot be via a Reader- derived
	 * class because we do not want to do any character set conversion at this
	 * stage. It might be binary data!)
	 */
	String readLine() throws IOException {
		int c;
		StringBuffer sb = new StringBuffer();
		while ((c = input.read()) != '\n') {
			if (c == -1)
				throw new IOException("unterminated line in request header");
			sb.append((char) c);
		}
		return sb.toString();
	}

	public void buildRequestHeader(HttpURLConnection urlConnection) {
		if (0 == method.length())
			method = "GET";

		try {
			urlConnection.setRequestMethod(method);
		} catch (ProtocolException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		if (0 < userAgent.length())
			urlConnection.addRequestProperty("User-Agent", userAgent);

		if (0 < referer.length())
			urlConnection.addRequestProperty("Referer", referer);

		if (pragmaNoCache)
			urlConnection.addRequestProperty("Pragma", "no-cache");

		if (0 < ifModifiedSince.length())
			urlConnection.addRequestProperty("If-Modified-Since",
					ifModifiedSince);

		// ACCEPT TYPES //
		if (0 < accept.length())
			urlConnection.addRequestProperty("Accept", accept);
		else
			urlConnection.addRequestProperty("Accept", "*/" + "* ");
		if (0 < acceptLanguage.length()) {
			urlConnection.addRequestProperty(
					HeaderValue.REQUEST_ACCEPT_LANGUAGE, acceptLanguage);
		}
		if (0 < contentType.length())
			urlConnection.addRequestProperty("Content-Type", contentType);

		if (0 < contentLength)
			urlConnection.addRequestProperty("Content-Length", "contentLength");

		if (0 != authorization.length())
			urlConnection.addRequestProperty("Authorization", "authorization");

		// add flag show the request from proxy
		urlConnection.addRequestProperty("FromProxy",
				Boolean.toString(fromProxy));

		if (0 != unrecognized.length()){
			String[] unknowHeaders = unrecognized.split(CR);
			if(unknowHeaders != null){
				for(int i=0;i<unknowHeaders.length;i++){
					int keyPos = unknowHeaders[i].indexOf(":");
					if(keyPos == -1)continue;
					
					urlConnection.addRequestProperty(unknowHeaders[i].substring(0,keyPos),  unknowHeaders[i].substring(keyPos+1));
				}
			}
		}
				
	}

	/*
	 * Rebuilds the header in a string
	 * 
	 * @return The header in a string.
	 */
	public String toString(boolean sendUnknowen) {
		String Request;

		if (0 == method.length())
			method = "GET";

		Request = method + " " + url + " " + version + CR;

		if (0 < host.length()) {
			Request += "Host:" + host + CR;
		}
		if (0 < userAgent.length())
			Request += "User-Agent:" + userAgent + CR;

		if (0 < referer.length())
			Request += "Referer:" + referer + CR;

		if (pragmaNoCache)
			Request += "Pragma: no-cache" + CR;

		if (0 < ifModifiedSince.length())
			Request += "If-Modified-Since: " + ifModifiedSince + CR;

		// ACCEPT TYPES //
		if (0 < accept.length())
			Request += "Accept: " + accept + CR;
		else
			Request += "Accept: */" + "* \r\n";
		if (0 < acceptLanguage.length()) {
			Request += HeaderValue.REQUEST_ACCEPT_LANGUAGE + ": "
					+ acceptLanguage + CR;
		}
		if (0 < contentType.length())
			Request += "Content-Type: " + contentType + CR;

		if (0 < contentLength)
			Request += "Content-Length: " + contentLength + CR;

		if (0 != authorization.length())
			Request += "Authorization: " + authorization + CR;

		// add flag show the request from proxy
		Request += "FromProxy:" + fromProxy + CR;

		if (sendUnknowen) {
			if (0 != unrecognized.length())
				Request += unrecognized;
		}

		Request += CR;

		return Request;
	}

	/**
	 * (Re)builds the header in a string.
	 * 
	 * @return The header in a string.
	 */
	public String toString() {
		return toString(true);
	}

	/**
	 * Returns the next token in a string
	 * 
	 * @param tk
	 *            String that is partially tokenized.
	 * @return The remainder
	 */
	String getToken(StringTokenizer tk) {
		String str = "";
		if (tk.hasMoreTokens())
			str = tk.nextToken();
		return str;
	}

	/**
	 * Returns the remainder of a tokenized string
	 * 
	 * @param tk
	 *            String that is partially tokenized.
	 * @return The remainder
	 */
	String getRemainder(StringTokenizer tk) {
		String str = "";
		if (tk.hasMoreTokens())
			str = tk.nextToken();
		while (tk.hasMoreTokens()) {
			str += " " + tk.nextToken();
		}
		return str;
	}

}
