note
	description: "[
				Request created from a hash_table of meta variables
		]"
	specification: "EWSGI specification https://github.com/Eiffel-World/Eiffel-Web-Framework/wiki/EWSGI-specification"
	legal: "See notice at end of class."
	status: "See notice at end of class."
	date: "$Date$"
	revision: "$Revision$"

class
	WGI_REQUEST_FROM_TABLE

inherit
	WGI_REQUEST

create
	make

feature {NONE} -- Initialization

	make (a_vars: HASH_TABLE [READABLE_STRING_8, READABLE_STRING_8]; a_input: like input; a_wgi_connector: like wgi_connector)
		require
			vars_attached: a_vars /= Void
		do
			wgi_connector := a_wgi_connector
			input := a_input
			set_meta_variables (a_vars)
			update_path_info
			update_input
		end

	update_input
			-- Update input, depending on Transfer-Encoding value.
		do
			if
				attached http_transfer_encoding as l_transfer_encoding and then
				l_transfer_encoding.same_string (once "chunked")
			then
				is_chunked_input := True
				create {WGI_CHUNKED_INPUT_STREAM} input.make (input)
			end
		end

feature -- Access: Input

	is_chunked_input: BOOLEAN
			-- Is request using chunked transfer-encoding?

	input: WGI_INPUT_STREAM
			-- Server input channel
			--| Could be a chunked input as well

feature -- EWSGI access

	wgi_version: STRING = "0.1"

	wgi_implementation: STRING = "Eiffel Web Framework 0.1"

	wgi_connector: WGI_CONNECTOR

feature -- Access: CGI meta parameters

	meta_variables: HASH_TABLE [READABLE_STRING_8, READABLE_STRING_8]
			-- CGI Environment parameters

	meta_variable (a_name: READABLE_STRING_8): detachable READABLE_STRING_8
			-- CGI meta variable related to `a_name'
		do
			Result := meta_variables.item (a_name)
		end

	meta_string_variable_or_default (a_name: READABLE_STRING_8; a_default: READABLE_STRING_8; use_default_when_empty: BOOLEAN): READABLE_STRING_8
			-- Value for meta parameter `a_name'
			-- If not found, return `a_default'
		require
			a_name_not_empty: a_name /= Void and then not a_name.is_empty
		do
			if attached meta_variable (a_name) as val then
				Result := val.string
				if use_default_when_empty and then Result.is_empty then
					Result := a_default
				end
			else
				Result := a_default
			end
		end

	set_meta_string_variable (a_name: READABLE_STRING_8; a_value: READABLE_STRING_8)
		do
			meta_variables.force (a_value, a_name)
		ensure
			param_set: attached meta_variable (a_name) as val and then val ~ a_value
		end

	unset_meta_variable (a_name: READABLE_STRING_8)
		do
			meta_variables.remove (a_name)
		ensure
			param_unset: meta_variable (a_name) = Void
		end

feature -- Access: CGI meta parameters - 1.1

	auth_type: detachable READABLE_STRING_8

	content_length: detachable READABLE_STRING_8

	content_type: detachable READABLE_STRING_8

	gateway_interface: READABLE_STRING_8
		do
			Result := meta_string_variable_or_default ({WGI_META_NAMES}.gateway_interface, "", False)
		end

	path_info: READABLE_STRING_8
			-- <Precursor/>
			--
			--| For instance, if the current script was accessed via the URL
			--| http://www.example.com/eiffel/path_info.exe/some/stuff?foo=bar, then $_SERVER['PATH_INFO'] would contain /some/stuff.
			--|
			--| Note that is the PATH_INFO variable does not exists, the `path_info' value will be empty

	path_translated: detachable READABLE_STRING_8
		do
			Result := meta_string_variable ({WGI_META_NAMES}.path_translated)
		end

	query_string: READABLE_STRING_8

	remote_addr: READABLE_STRING_8

	remote_host: READABLE_STRING_8

	remote_ident: detachable READABLE_STRING_8
		do
			Result := meta_string_variable ({WGI_META_NAMES}.remote_ident)
		end

	remote_user: detachable READABLE_STRING_8
		do
			Result := meta_string_variable ({WGI_META_NAMES}.remote_user)
		end

	request_method: READABLE_STRING_8

	script_name: READABLE_STRING_8

	server_name: READABLE_STRING_8

	server_port: INTEGER

	server_protocol: READABLE_STRING_8
		do
			Result := meta_string_variable_or_default ({WGI_META_NAMES}.server_protocol, "HTTP/1.0", True)
		end

	server_software: READABLE_STRING_8
		do
			Result := meta_string_variable_or_default ({WGI_META_NAMES}.server_software, "Unknown Server", True)
		end

feature -- Access: HTTP_* CGI meta parameters - 1.1

	http_accept: detachable READABLE_STRING_8
			-- Contents of the Accept: header from the current request, if there is one.
		do
			Result := meta_string_variable ({WGI_META_NAMES}.http_accept)
		end

	http_accept_charset: detachable READABLE_STRING_8
			-- Contents of the Accept-Charset: header from the current request, if there is one.
			-- Example: 'iso-8859-1,*,utf-8'.
		do
			Result := meta_string_variable ({WGI_META_NAMES}.http_accept_charset)
		end

	http_accept_encoding: detachable READABLE_STRING_8
			-- Contents of the Accept-Encoding: header from the current request, if there is one.
			-- Example: 'gzip'.
		do
			Result := meta_string_variable ({WGI_META_NAMES}.http_accept_encoding)
		end

	http_accept_language: detachable READABLE_STRING_8
			-- Contents of the Accept-Language: header from the current request, if there is one.
			-- Example: 'en'.
		do
			Result := meta_string_variable ({WGI_META_NAMES}.http_accept_language)
		end

	http_connection: detachable READABLE_STRING_8
			-- Contents of the Connection: header from the current request, if there is one.
			-- Example: 'Keep-Alive'.
		do
			Result := meta_string_variable ({WGI_META_NAMES}.http_connection)
		end

	http_host: detachable READABLE_STRING_8
			-- Contents of the Host: header from the current request, if there is one.
		do
			Result := meta_string_variable ({WGI_META_NAMES}.http_host)
		end

	http_referer: detachable READABLE_STRING_8
			-- The address of the page (if any) which referred the user agent to the current page.
			-- This is set by the user agent.
			-- Not all user agents will set this, and some provide the ability to modify HTTP_REFERER as a feature.
			-- In short, it cannot really be trusted.
		do
			Result := meta_string_variable ({WGI_META_NAMES}.http_referer)
		end

	http_user_agent: detachable READABLE_STRING_8
			-- Contents of the User-Agent: header from the current request, if there is one.
			-- This is a string denoting the user agent being which is accessing the page.
			-- A typical example is: Mozilla/4.5 [en] (X11; U; Linux 2.2.9 i586).
			-- Among other things, you can use this value to tailor your page's
			-- output to the capabilities of the user agent.
		do
			Result := meta_string_variable ({WGI_META_NAMES}.http_user_agent)
		end

	http_authorization: detachable READABLE_STRING_8
			-- Contents of the Authorization: header from the current request, if there is one.
		do
			Result := meta_string_variable ({WGI_META_NAMES}.http_authorization)
		end

	http_transfer_encoding: detachable READABLE_STRING_8
			-- Transfer-Encoding
			-- for instance chunked
		do
			Result := meta_string_variable ({WGI_META_NAMES}.http_transfer_encoding)
		end

feature -- Access: Extension to CGI meta parameters - 1.1

	request_uri: READABLE_STRING_8
			-- The URI which was given in order to access this page; for instance, '/index.html'.

	orig_path_info: detachable READABLE_STRING_8
			-- Original version of `path_info' before processed by Current environment

feature {NONE} -- Element change: CGI meta parameter related to PATH_INFO

	set_meta_variables (a_vars: HASH_TABLE [READABLE_STRING_8, READABLE_STRING_8])
			-- Fill with variable from `a_vars'
		local
			s: like meta_string_variable
			table: HASH_TABLE [READABLE_STRING_8, READABLE_STRING_8]
			l_query_string: like query_string
			l_request_uri: detachable STRING_32
			l_empty_string: like empty_string
		do
			create {STRING_8} l_empty_string.make_empty
			empty_string := l_empty_string

			create table.make (a_vars.count)
			table.compare_objects
			meta_variables := table
			from
				a_vars.start
			until
				a_vars.after
			loop
				table.force (a_vars.item_for_iteration, a_vars.key_for_iteration)
				a_vars.forth
			end

				--| QUERY_STRING
			l_query_string := meta_string_variable_or_default ({WGI_META_NAMES}.query_string, l_empty_string, False)
			query_string := l_query_string

				--| REQUEST_METHOD
			request_method := meta_string_variable_or_default ({WGI_META_NAMES}.request_method, l_empty_string, False)

				--| CONTENT_TYPE
			s := meta_string_variable ({WGI_META_NAMES}.content_type)
			if s /= Void and then not s.is_empty then
				content_type := s
			else
				content_type := Void
			end

				--| CONTENT_LENGTH
			content_length := meta_string_variable ({WGI_META_NAMES}.content_length)

				--| PATH_INFO
			path_info := meta_string_variable_or_default ({WGI_META_NAMES}.path_info, l_empty_string, False)

				--| SERVER_NAME
			server_name := meta_string_variable_or_default ({WGI_META_NAMES}.server_name, l_empty_string, False)

				--| SERVER_PORT
			s := meta_string_variable ({WGI_META_NAMES}.server_port)
			if s /= Void and then s.is_integer then
				server_port := s.to_integer
			else
				server_port := 80
			end

				--| SCRIPT_NAME
			script_name := meta_string_variable_or_default ({WGI_META_NAMES}.script_name, l_empty_string, False)

				--| REMOTE_ADDR
			remote_addr := meta_string_variable_or_default ({WGI_META_NAMES}.remote_addr, l_empty_string, False)

				--| REMOTE_HOST
			remote_host := meta_string_variable_or_default ({WGI_META_NAMES}.remote_host, l_empty_string, False)

				--| REQUEST_URI
			s := meta_string_variable ({WGI_META_NAMES}.request_uri)
			if s /= Void then
				l_request_uri := s
			else
					--| It might occur that REQUEST_URI is not available, so let's compute it from SCRIPT_NAME
				create l_request_uri.make_from_string (script_name)
				if not l_query_string.is_empty then
					 l_request_uri.append_character ('?')
					 l_request_uri.append (l_query_string)
				end
			end
			request_uri := single_slash_starting_string (l_request_uri)
		end

	set_orig_path_info (s: READABLE_STRING_8)
			-- Set ORIG_PATH_INFO to `s'
		require
			s_attached: s /= Void
		do
			orig_path_info := s
			set_meta_string_variable ({WGI_META_NAMES}.orig_path_info, s)
		end

	unset_orig_path_info
			-- Unset ORIG_PATH_INFO
		do
			orig_path_info := Void
			unset_meta_variable ({WGI_META_NAMES}.orig_path_info)
		ensure
			unset: meta_variable ({WGI_META_NAMES}.orig_path_info) = Void
		end

	update_path_info
			-- Fix and update PATH_INFO value if needed
		local
			l_path_info: STRING
		do
			l_path_info := path_info
			--| Warning
			--| on IIS: we might have   PATH_INFO = /sample.exe/foo/bar
			--| on apache:				PATH_INFO = /foo/bar
			--| So, we might need to check with SCRIPT_NAME and remove it on IIS
			--| store original PATH_INFO in ORIG_PATH_INFO
			if l_path_info.is_empty then
				unset_orig_path_info
			else
				set_orig_path_info (l_path_info)
				if attached script_name as l_script_name then
					if l_path_info.starts_with (l_script_name) then
						path_info := l_path_info.substring (l_script_name.count + 1 , l_path_info.count)
					end
				end
			end
		end

feature {NONE} -- Implementation: utilities	

	has_white_space (s: READABLE_STRING_8): BOOLEAN
			-- `s' has white space?
		do
			Result := s.has (' ') or else s.has ('%T')
		end

	single_slash_starting_string (s: READABLE_STRING_8): STRING_8
			-- Return the string `s' (or twin) with one and only one starting slash
		local
			i, n: INTEGER
		do
			n := s.count
			if n > 1 then
				if s[1] /= '/' then
					create Result.make (1 + n)
					Result.append_character ('/')
					Result.append (s)
				elseif s[2] = '/' then
					--| We need to remove all starting slash, except one
					from
						i := 3
					until
						i > n
					loop
						if s[i] /= '/' then
							n := 0 --| exit loop
						else
							i := i + 1
						end
					end
					n := s.count
					check i >= 2 and i <= n end
					Result := s.substring (i - 1, s.count)
				else
					--| starts with one '/' and only one		
					Result := s
				end
			elseif n = 1 then
				if s[1] = '/' then
					Result := s
				else
					create Result.make (2)
					Result.append_character ('/')
					Result.append (s)
				end
			else --| n = 0
				create Result.make_filled ('/', 1)
			end
		ensure
			one_starting_slash: Result[1] = '/' and (Result.count = 1 or else Result[2] /= '/')
		end

	empty_string: READABLE_STRING_8
			-- Reusable empty string

invariant
	empty_string_unchanged: empty_string.is_empty

note
	copyright: "2011-2012, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
	license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
	source: "[
			Eiffel Software
			5949 Hollister Ave., Goleta, CA 93117 USA
			Telephone 805-685-1006, Fax 805-685-6869
			Website http://www.eiffel.com
			Customer support http://support.eiffel.com
		]"
end
