indexing
	description: "Objects that represent CGI servlet request information"
	project: "Project Goanna <http://sourceforge.net/projects/goanna>"
	library: "CGI servlets"
	date: "$Date$"
	revision: "$Revision$"
	author: "Glenn Maughan <glennmaughan@users.sourceforge.net>"
	copyright: "Copyright (c) 2001 Glenn Maughan and others"
	license: "Eiffel Forum License v2 (see forum.txt)."

class GOA_CGI_SERVLET_REQUEST

inherit

	GOA_HTTP_SERVLET_REQUEST

	GOA_SHARED_HTTP_SESSION_MANAGER
		export
			{NONE} all
		end

	GOA_CGI_VARIABLES
		export
			{NONE} all
		end

	GOA_HTTP_UTILITY_FUNCTIONS
		export
			{NONE} all
		end

	GOA_STRING_MANIPULATION
		export
			{NONE} all
		end

	KL_SHARED_EXECUTION_ENVIRONMENT
		export
			{NONE} all
		end

	KL_SHARED_STANDARD_FILES
		export
			{NONE} all
		end

create

	make

feature {NONE} -- Initialisation

	make (resp: GOA_CGI_SERVLET_RESPONSE) is
			-- Create a new cgi servlet request wrapper
		require
			response_exists: resp /= Void
		do
			internal_response := resp
			create parameters.make (5)
			parse_parameters
		end

feature -- Access

	get_parameter (name: STRING): STRING is
			-- Returns the value of a request parameter
		do
			Result := clone (parameters.item (name).last)
		end

	get_multiple_parameter (name: STRING): DS_LINKED_LIST [STRING] is
			-- Returns the value of a request parameter
		do
			Result := clone (parameters.item (name))
		end

	get_parameter_names: DS_LINEAR [STRING] is
			-- Return all parameter names
		local
			cursor: DS_HASH_TABLE_CURSOR [DS_LINKED_LIST [STRING], STRING]
			array_list: DS_ARRAYED_LIST [STRING]
		do
			create array_list.make (parameters.count)
			cursor := parameters.new_cursor
			from
				cursor.start
			until
				cursor.off
			loop
				array_list.force_last (clone (cursor.key))
				cursor.forth
			end
			Result := array_list
		end

	get_parameter_values: DS_LINEAR [STRING] is
			-- Return all parameter values
		local
			cursor: DS_HASH_TABLE_CURSOR [DS_LINKED_LIST [STRING], STRING]
			ll_cursor: DS_LINKED_LIST_CURSOR [STRING]
			array_list: DS_ARRAYED_LIST [STRING]
		do
			create array_list.make (parameters.count + 10)
			cursor := parameters.new_cursor
			from
				cursor.start
			until
				cursor.off
			loop
				ll_cursor := cursor.item.new_cursor
				from
					ll_cursor.start
				until
					ll_cursor.after
				loop
					array_list.force_last (clone (ll_cursor.item))
					ll_cursor.forth
				end
				cursor.forth
			end
			Result := array_list
		end

	get_header (name: STRING): STRING is
			-- Get the value of the specified request header.
		do
			Result := clone (Execution_environment.variable_value (name))
		end

	get_headers (name: STRING): DS_LINEAR [STRING] is
			-- Get all values of the specified request header. If the
			-- header has comma-separated values they are separated and added to the
			-- result. If only one value exists, it is added as the sole entry in the
			-- list.
		local
			tokenizer: GOA_STRING_TOKENIZER
			list: DS_LINKED_LIST [STRING]
		do
			create list.make
			create tokenizer.make (get_header (name), ",")
			from
				tokenizer.start
			until
				tokenizer.off
			loop
				list.force_last (tokenizer.item)
				tokenizer.forth
			end
			Result := list
		end

	get_header_names: DS_LINEAR [STRING] is
			-- Get all header names.
		local
			array_list: DS_ARRAYED_LIST [STRING]
		do
			-- create fixed list of variable names
			create array_list.make (23)
			array_list.force_last (Gateway_interface_var)
			array_list.force_last (Server_name_var)
			array_list.force_last (Server_software_var)
			array_list.force_last (Server_protocol_var)
			array_list.force_last (Server_port_var)
			array_list.force_last (Request_method_var)
			array_list.force_last (Path_info_var)
			array_list.force_last (Path_translated_var)
			array_list.force_last (Script_name_var)
			array_list.force_last (Document_root_var)
			array_list.force_last (Query_string_var)
			array_list.force_last (Remote_host_var)
			array_list.force_last (Remote_addr_var)
			array_list.force_last (Auth_type_var)
			array_list.force_last (Remote_user_var)
			array_list.force_last (Remote_ident_var)
			array_list.force_last (Content_type_var)
			array_list.force_last (Content_length_var)
			array_list.force_last (Http_from_var)
			array_list.force_last (Http_accept_var)
			array_list.force_last (Http_user_agent_var)
			array_list.force_last (Http_referer_var)
			array_list.force_last (Http_cookie_var)
			Result := array_list
		end

feature -- Status report

	has_parameter (name: STRING): BOOLEAN is
			-- Does this request have a parameter named 'name'?
		do
			Result := parameters.has (name)
		end

	content_length: INTEGER is
			-- The length in bytes of the request body or -1 if the length is
			-- not known.
		local
			str: STRING
		do
			Result := 0
			if has_header (Content_length_var) then
				str := get_header (Content_length_var)
				if str /= Void and then str.is_integer then
					Result := str.to_integer
				end
			end
		end

	content_type: STRING is
			-- The MIME type of the body of the request, or Void if the type is
			-- not known.
		do
			if has_header (Content_type_var) then
				Result := get_header (Content_type_var)
			else
				Result := ""
			end
		end

	protocol: STRING is
			-- The name and version of the protocol the request uses in the form
			-- 'protocol/majorVersion.minorVrsion', for example, HTTP/1.1.
		do
			if has_header (Server_protocol_var) then
				Result := get_header (Server_protocol_var)
			else
				Result := ""
			end
		end

	scheme: STRING is
			-- The name of the scheme used to make this request. Such as, http, https
			-- or ftp.
		do
			Result := "http"
			-- TODO: may need to change this when we support https	
		end

	server_name: STRING is
			-- The host name of the server that received the request.
		do
			if has_header (Server_name_var) then
				Result := get_header (Server_name_var)
			else
				Result := ""
			end
		end

	server_port: STRING is
			-- The port number on which this request was received.
		do
			if has_header (Server_port_var) then
				Result := get_header (Server_port_var)
			else
				Result := ""
			end
		end

	remote_address: STRING is
			-- The internet protocol (IP) address of the client that sent the request.
		do
			if has_header (Remote_addr_var) then
				Result := get_header (Remote_addr_var)
			else
				Result := ""
			end

		end

	remote_host: STRING is
			-- The fully qualified name of the client that sent the request, or the
			-- IP address of the client if the name cannot be determined.
		do
			if has_header (Remote_host_var) then
				Result := get_header (Remote_host_var)
			else
				Result := ""
			end
		end

	is_secure: BOOLEAN is
			-- Was this request made using a secure channel, such as HTTPS?
		do
			if has_header (Https_var) and then equal (get_header (Https_var), "on") then
				Result := True
			end
		end

	has_header (name: STRING): BOOLEAN is
			-- Does this request contain a header named 'name'?
		do
			Result := Execution_environment.variable_value (name) /= Void
		end

	auth_type: STRING is
			-- The name of the authentication scheme used to protect the servlet,
			-- for example, "BASIC" or "SSL" or Void if the servlet was not protected.
		do
			if has_header (Auth_type_var) then
				Result := get_header (Auth_type_var)
			else
				Result := ""
			end
		end

	cookies: DS_LINKED_LIST [GOA_COOKIE] is
			-- Cookies sent with this request.
		do
			if internal_cookies = Void then
				-- iterate across cookie header and collect all cookie values
				-- TODO: handle multiple cookie headers
				parse_cookie_header
			end
			Result := internal_cookies
		end

	session: GOA_HTTP_SESSION is
			-- Return the session associated with this request. Create a new session
			-- if one does not already exist.
		do
			if session_id = Void then
				Session_manager.bind_session (Current, internal_response)
				session_id := Session_manager.last_session_id
			end
			Result := Session_manager.get_session (session_id)
		end

	method: STRING is
			-- The name of the HTTP method with which this request was made, for
			-- example, GET, POST, or HEAD.
		do
			if has_header (Request_method_var) then
				Result := get_header (Request_method_var)
			else
				Result := ""
			end
		end

	path_info: STRING is
			-- Any extra path information associated with the URL the client sent
			-- when it made the request.
		do
			if has_header (Path_info_var) then
				Result := get_header (Path_info_var)
			else
				Result := ""
			end
		end

	path_translated: STRING is
			-- Any extra path information after the servlet name but before
			-- the query string translated to a real path.
		do
			if has_header (Path_translated_var) then
				Result := get_header (Path_translated_var)
			else
				Result := ""
			end
		end

	query_string: STRING is
			-- The query string that is contained in the request URL after the path.
			-- Returns Void if no query string is sent.
		do
			if has_header (Query_string_var) then
				Result := get_header (Query_string_var)
			else
				Result := ""
			end
		end

	remote_user: STRING is
			-- The login of the user making this request, if the user has been
			-- authenticated, or Void if the user has not been authenticated.
		do
			if has_header (Remote_user_var) then
				Result := get_header (Remote_user_var)
			else
				Result := ""
			end
		end

	servlet_path: STRING is
			-- The part of this request's URL that calls the servlet. This includes
			-- either the servlet name or a path to the servlet, but does not include
			-- any extra path information or a query string.
		do
			if has_header (Script_name_var) then
				Result := get_header (Script_name_var)
			else
				Result := ""
			end
		end

	content: STRING is
			-- Content data
		do
			debug ("CGI servlet request")
				print ("Content entered%N")
			end
			if has_header (Content_length_var) then
				debug ("CGI servlet request")
					print ("Found content length header%N")
				end
				if internal_content = Void then
					if content_length > 0 then
						debug ("CGI servlet request")
							print ("Content length > 0%N")
						end
						-- TODO: check for errors
						std.input.read_string (content_length)
						internal_content := std.input.last_string
						debug ("CGI servlet request")
							print ("Internal content is: " + internal_content + "%N")
						end
					else
						debug ("CGI servlet request")
							print ("No internal content 1%N")
						end
						internal_content := ""
					end
				end
			else
				debug ("CGI servlet request")
					print ("No internal content 2%N")
				end
				internal_content := ""
			end
			if internal_content /= Void then
				Result := internal_content
			else
				Result := ""
			end

		end

feature {CGI_SERVLET_REQUEST} -- Implementation

	internal_content: STRING
			-- Content read from stdin

	internal_response: GOA_CGI_SERVLET_RESPONSE
		-- Response object held so that session cookie can be set.

	session_id: STRING
		-- Id of session. Void until session is bound by first call to get_session.

	internal_cookies: DS_LINKED_LIST [GOA_COOKIE]
		-- Cached collection of request cookies

	parameters: DS_HASH_TABLE [DS_LINKED_LIST [STRING], STRING]
			-- Table of parameter values with support for multiple values per parameter name

feature {NONE} -- Implementation

	parse_parameters is
			-- Parse the query string or stdin data for parameters and
			-- store in params structure.			
		local
			string_to_process: STRING
		do
			-- If the request is a GET then the parameters are stored in the query
			-- string. Otherwise, the parameters are in the stdin data.
			if method.is_equal ("GET") then
				string_to_process := clone (query_string)
			elseif method.is_equal ("POST") then
				debug ("CGI servlet request")
					print ("POST - parse content%N")
				end
				string_to_process := clone (content)
			else
				-- not sure where the parameters will be for other request methods.
				-- Need to experiment.
			end
			if string_to_process /= Void and then not string_to_process.is_empty then
				parse_parameter_string (string_to_process)
			end
		end

	parse_parameter_string (str: STRING) is
			-- Parse the parameter string 'str' and build parameter structure.
			-- Parameters are of the form 'name=value' separated by '&' with all
			-- spaces and special characters encoded. An exception is an image map
			-- coordinate pair that is of the form 'value,value'. Any amount of
			-- whitespace may separate each token.
		require
			valid_str: str /= Void
		local
			e: INTEGER
			pair, name, value: STRING
			tokenizer: GOA_STRING_TOKENIZER
		do
			-- parameters can appear more than once. Add a parameter value for each instance.
			debug ("query_string_parsing")
				print (generator + ".parse_parameter_string str = " + quoted_eiffel_string_out (str) + "%R%N")
			end
			create tokenizer.make (str, "&")
			from
				tokenizer.start
			until
				tokenizer.off
			loop
				-- get the parameter pair token
				pair := tokenizer.item
				-- find equal character
				e := index_of_char (pair, '=', 1)
				if e > 0 then
					name := pair.substring (1, e - 1)
					value := pair.substring (e + 1, pair.count)
					-- add the parameter
					add_parameter (decode_url(name), decode_url(value))
				else
					-- TODO: check for a coordinate pair
				end
				tokenizer.forth
			end
		end

	add_parameter (name, value: STRING) is
			-- Set decoded 'value' for the parameter 'name' to the parameters structure.
			-- Append to the list if the name already exists in the hashtable
		do
			debug ("query_string_parsing")
				print (generator + ".add_parameter name = "
					+ quoted_eiffel_string_out (name)
					+ " value = " + quoted_eiffel_string_out (value) + "%R%N")
			end
			if not parameters.has (name) then
				parameters.force (create {DS_LINKED_LIST [STRING]}.make, name)
			end
			parameters.item (name).put_last (value)
		end

	parse_cookie_header is
			-- Parse the cookie header, if it exists and construct the 'internal_cookies'
			-- collection.
			-- This routine parsed the cookies using version 0 of the cookie spec (RFC 2109).
			-- See also http://www.netscape.com/newsref/std/cookie_spec.html
		local
			tokenizer: GOA_STRING_TOKENIZER
			comparator: GOA_COOKIE_NAME_EQUALITY_TESTER
			pair, name, value: STRING
			new_cookie: GOA_COOKIE
			i: INTEGER
		do
			-- not very efficient but we don't know if we will get any bogus cookies
			-- along the way
			create internal_cookies.make
			create comparator
			internal_cookies.set_equality_tester (comparator)
			if has_header (Http_cookie_var) then
				from
					create tokenizer.make (get_header (Http_cookie_var), ";")
					debug ("cookie_parsing")
						print (generator + ".parse_cookie_header str = "
							 + quoted_eiffel_string_out (get_header (Http_cookie_var)) + "%R%N")
					end
					tokenizer.start
				until
					tokenizer.off
				loop
					pair := tokenizer.item
					if pair.is_empty then
						i := 0
					else
						i := index_of_char (pair, '=', 1)
					end
					if i > 1 then
						name := pair.substring (1, i - 1)
						name.left_adjust
						name.right_adjust
						if i = pair.count then
							value := ""
						else
							value := pair.substring (i + 1, pair.count)
							value.left_adjust
							value.right_adjust
						end
						-- remove double quotes if they wrap the value
						if value.item (1) = '%"' then
							value := value.substring (2, value.count - 1)
						end
						if not tester.is_reserved_cookie_word (name) then
							create new_cookie.make (name, value)
						end
						debug ("cookie_parsing")
							print (generator + ".parse_cookie_header new_cookie = "
								+ quoted_eiffel_string_out (new_cookie.header_string) + "%R%N")
						end
						internal_cookies.force_last (new_cookie)
					else
						-- bad cookie, ignore it
					end
					tokenizer.forth
				end
			end
		end

	tester: GOA_COOKIE is
			-- Used to test validity of values
		once
			create result.make ("test", "test")
		end



end -- class GOA_CGI_SERVLET_REQUEST