/***********************************************************************
 *
 * $CVSHeader$
 *
 * This file is part of WebScarab, an Open Web Application Security
 * Project utility. For details, please see http://www.owasp.org/
 *
 * Copyright (c) 2002 - 2004 Rogan Dawes
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * Getting Source
 * ==============
 *
 * Source for this application is maintained at Sourceforge.net, a
 * repository for free software projects.
 * 
 * For details, please see http://www.sourceforge.net/projects/owasp
 *
 */

/*
 * Request.java
 *
 * Created on May 12, 2003, 11:12 PM
 */

package org.owasp.webscarab.model;

import java.io.InputStream;
import java.io.OutputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.SocketTimeoutException;

import java.text.ParseException;

/** This class represents a request that can be sent to an HTTP server.
 * @author rdawes
 */
public class Request extends Message {
    
    private String _method = "GET";
    private HttpUrl _url = null;
    private String _version = "HTTP/1.0";
    
    /** Creates a new instance of Request */
    public Request() {
    }
    
    /**
     * Creates a new Request, which is a copy of the supplied Request
     * @param req the request to copy
     */    
    public Request(Request req) {
        _method = req.getMethod();
        _url = req.getURL();
        _version = req.getVersion();
        setHeaders(req.getHeaders());
        setContent(req.getContent());
    }
    
    /**
     * initialises the Request from the supplied InputStream
     * @param is the InputStream to read from
     * @throws IOException propagates any exceptions thrown by the InputStream
     */
    public void read(InputStream is) throws IOException {
        read(is, null);
    }
    
    /**
     * initialises the Request from the supplied InputStream, using the supplied Url
     * as a base. This will generally be useful where we are acting as a web server,
     * and reading a line like "GET / HTTP/1.0". The request Url is then created
     * relative to the supplied Url.
     * @param is the InputStream to read from
     * @param base the base Url to use for relative Urls
     * @throws IOException propagates any IOExceptions thrown by the InputStream
     */    
    public void read(InputStream is, HttpUrl base) throws IOException {
        String line = null;
        _logger.finer("Base: " + base);
        try {
            line = readLine(is);
            _logger.finest("Request: " + line);
        } catch (SocketTimeoutException ste) {
            // System.err.println("Read timed out. Closing connection");
            return;
        }
        if (line == null || line.equals("")) {
            // System.err.println("Client closed connection!");
            return;
        }
        String[] parts = line.split(" ");
        if (parts.length == 2 || parts.length == 3) {
            setMethod(parts[0]);
            if (getMethod().equalsIgnoreCase("CONNECT")) {
                setURL(new HttpUrl("https://" + parts[1] + "/"));
            } else {
                // supports creating an absolute url from a relative one
                setURL(new HttpUrl(base, parts[1]));
            }
        } else {
            throw new IOException("Invalid request line reading from the InputStream '"+line+"'");
        }
        if (parts.length == 3) {
            setVersion(parts[2]);
        } else {
            setVersion("HTTP/0.9");
        }
        // Read the rest of the message headers and to the start of the body
        super.read(is);
        if (_method.equals("CONNECT") || _method.equals("GET") || _method.equals("HEAD") || _method.equals("TRACE")) {
            // These methods cannot include a message body
            setNoBody();
        }
    }
    
    /**
     * parses a string representation of a request
     * @param string the string representing the request
     * @throws ParseException if there are any errors parsing the request
     */    
    public void parse(String string) throws ParseException {
        parse(new StringBuffer(string));
    }
    
    /**
     * parses a string representation of a request
     * @param buff a StringBuffer containing the request. Note that the contents of the StringBuffer are consumed during parsing.
     * @throws ParseException if there are any errors parsing the request
     */    
    public void parse(StringBuffer buff) throws ParseException {
        String line = null;
        line = getLine(buff);
        String[] parts = line.split(" ");
        if (parts.length == 2 || parts.length == 3) {
            setMethod(parts[0]);
            try {
                if (getMethod().equalsIgnoreCase("CONNECT")) {
                    setURL(new HttpUrl("https://" + parts[1] + "/"));
                } else {
                    setURL(new HttpUrl(parts[1]));
                }
            } catch (MalformedURLException mue) {
                throw new ParseException("Malformed URL '" + parts[1] + "' : " + mue, parts[0].length()+1);
            }
        } else {
            throw new ParseException("Invalid request line '" + line + "'", 0);
        }
        if (parts.length == 3) {
            setVersion(parts[2]);
        } else {
            setVersion("HTTP/0.9");
        }
        // Read the rest of the message headers and body
        super.parse(buff);
        if (_method.equals("CONNECT") || _method.equals("GET") || _method.equals("HEAD") || _method.equals("TRACE")) {
            // These methods cannot include a message body
            setNoBody();
        }
    }
    
    /**
     * Writes the Request to the supplied OutputStream, in a format appropriate for
     * sending to an HTTP proxy. Uses the RFC CRLF string "\r\n"
     * @param os the OutputStream to write to
     * @throws IOException if the underlying stream throws any.
     */    
    public void write(OutputStream os) throws IOException {
        write(os,"\r\n");
    }
    
    /**
     * Writes the Request to the supplied OutputStream, in a format appropriate for
     * sending to an HTTP proxy. Uses the supplied string to separate lines.
     * @param os the OutputStream to write to
     * @param crlf the string to use to separate the lines (usually a CRLF pair)
     * @throws IOException if the underlying stream throws any.
     */    
    public void write(OutputStream os, String crlf) throws IOException {
        if (_method == null || _url == null || _version == null) {
            System.err.println("Uninitialised Request!");
            return;
        }
        os = new BufferedOutputStream(os);
        String requestLine = _method+" "+_url+" " + _version + crlf;
        os.write(requestLine.getBytes());
        _logger.finer("Request: " + requestLine);
        super.write(os, crlf);
        os.flush();
    }
    
    /** Writes the Request to the supplied OutputStream, in a format appropriate for
     * sending to the HTTP server itself. Uses the RFC CRLF string "\r\n"
     * @param os the OutputStream to write to
     * @throws IOException if the underlying stream throws any.
     */    
    public void writeDirect(OutputStream os) throws IOException {
        writeDirect(os, "\r\n");
    }
    
    /** Writes the Request to the supplied OutputStream, in a format appropriate for
     * sending to the HTTP server itself. Uses the supplied string to separate lines.
     * @param os the OutputStream to write to
     * @param crlf the string to use to separate the lines (usually a CRLF pair)
     * @throws IOException if the underlying stream throws any.
     */    
    public void writeDirect(OutputStream os, String crlf) throws IOException {
        if (_method == null || _url == null || _version == null) {
            System.err.println("Uninitialised Request!");
            return;
        }
        os = new BufferedOutputStream(os);
        String requestLine = _method + " " + _url.direct() + " " + _version;
        os.write((requestLine+crlf).getBytes());
        _logger.finer("Request: " + requestLine);
        super.write(os, crlf);
        os.flush();
    }
    
    /**
     * Sets the request method
     * @param method the method of the request (automatically converted to uppercase)
     */    
    public void setMethod(String method) {
        _method = method.toUpperCase();
    }
    
    /**
     * gets the Request method
     * @return the request method
     */    
    public String getMethod() {
        return _method;
    }
    
    /**
     * Sets the Request URL
     * @param url the url
     */    
    public void setURL(HttpUrl url) {
        _url = url;
    }
    
    /**
     * Gets the Request URL
     * @return the request url
     */    
    public HttpUrl getURL() {
        return _url;
    }
    /**
     * Convenience method to search on parameters
     * @param search
     * @return true if parameters contains
     */
    
    public boolean parameterSearch(String search)
    {
    	String s = getURL().getQuery();
    	if(s == null) return false;
    	return s.toLowerCase().indexOf(search) > -1;
    }
    /**
     * Sets the HTTP version supported
     * @param version the version of the request. Automatically converted to uppercase.
     */    
    public void setVersion(String version) {
        _version = version.toUpperCase();
    }
    
    /**
     * gets the HTTP version
     * @return the version of the request
     */    
    public String getVersion() {
        return _version;
    }
    
    /**
     * returns a string representation of the Request, using a CRLF of "\r\n"
     * @return a string representation of the Request, using a CRLF of "\r\n"
     */    
    public String toString() {
        return toString("\r\n");
    }
    
    /**
     * returns a string representation of the Request, using the supplied string to
     * separate lines
     * @param crlf the string to use to separate lines (usually CRLF)
     * @return a string representation of the Request
     */    
    public String toString(String crlf) {
        if (_method == null || _url == null || _version == null) {
            return "Unitialised Request!";
        }
        StringBuffer buff = new StringBuffer();
        buff.append(_method).append(" ");
        buff.append(_url).append(" ");
        buff.append(_version).append(crlf);
        buff.append(super.toString(crlf));
        return buff.toString();
    }
    
    public boolean equals(Object obj) {
        if (!(obj instanceof Request)) return false;
        Request req = (Request)obj;
        if (!getMethod().equals(req.getMethod())) return false;
        if (!getURL().equals(req.getURL())) return false;
        if (!getVersion().equals(req.getVersion())) return false;
        return super.equals(req);
    }
    
}
