note
	description: "[
				Context for HTTP client request
				This is used to hold 
					- headers
					- query_parameters
					- form parameters
					- upload_data xor upload_filename
				And in addition it has 
					- credentials_required
					- proxy
					
				Note that any value set in this context class overrides conflicting value eventually
				     set in associated HTTP_CLIENT_SESSION.
			
				Warning: for now [2012-05-31], you can have only one of the following data
						- form_parameters
						- or upload_data 
						- or upload_filename 
						If you set more than one, the priority is then upload_data, then upload_filename, then form_parameters
			]"
	date: "$Date$"
	revision: "$Revision$"

class
	HTTP_CLIENT_REQUEST_CONTEXT

create
	make,
	make_with_credentials_required

feature {NONE} -- Initialization

	make
		do
			create headers.make (2)
			create query_parameters.make (5)
			create form_parameters.make (10)
		end

	make_with_credentials_required
		do
			make
			set_credentials_required (True)
		end

feature -- Settings

	credentials_required: BOOLEAN
			-- If True, the request will precise the HTTP_AUTHORIZATION.

	proxy: detachable TUPLE [host: READABLE_STRING_8; port: INTEGER]
			-- Optional proxy, see {HTTP_CLIENT_SESSION}.proxy

feature -- Access

	headers: HASH_TABLE [READABLE_STRING_8, READABLE_STRING_8]
			-- Specific headers to use in addition to the one set in the related HTTP_CLIENT_SESSION
			--| note: the value from Current context override the one from the session in case of conflict

	query_parameters: HTTP_CLIENT_REQUEST_QUERY_PARAMETERS
			-- Query parameters to be appended to the url
			--| note: if the url already contains a query_string, the `query_parameters' will be appended to the url

	form_parameters: HTTP_CLIENT_REQUEST_FORM_PARAMETERS
			-- Form parameters

	upload_data: detachable READABLE_STRING_8
			-- Upload data
			--| Note: make sure to precise the Content-Type header

	upload_filename: detachable IMMUTABLE_STRING_32
			-- Upload data read from `upload_filename'
			--| Note: make sure to precise the Content-Type header

	write_agent: detachable PROCEDURE [READABLE_STRING_8]
			-- Use this agent to hook the write of the response.
			--| could be used to save the response directly in a file		

	output_file: detachable FILE
			-- Optional output file to get downloaded content and header

	output_content_file: detachable FILE
			-- Optional output file to get downloaded content

	http_version: detachable IMMUTABLE_STRING_8
			-- Overwrite default http version if set.

feature -- Status report

	has_form_data: BOOLEAN
			-- Has any form parameters?
			--| i.e coming from POST or PUT content.
		do
			Result := not form_parameters.is_empty
		end

	has_upload_data: BOOLEAN
			-- Has associated upload_data?
		do
			Result := attached upload_data as d and then not d.is_empty
		end

	has_upload_filename: BOOLEAN
			-- Has associated upload_filename?
		do
			Result := attached upload_filename as fn and then not fn.is_empty
		end

	has_write_option: BOOLEAN
			-- Has non default write behavior?
		do
			Result := write_agent /= Void or output_file /= Void or output_content_file /= Void
		end

feature -- Element change

	add_header (k: READABLE_STRING_8; v: READABLE_STRING_8)
			-- Add http header line `k:v'.
		do
			headers.force (v, k)
		end

	add_header_line (s: READABLE_STRING_8)
			-- Add http header line `s'.
		local
			i: INTEGER
		do
			i := s.index_of (':', 1)
			if i > 0 then
				add_header (s.substring (1, i - 1), s.substring (i + 1, s.count))
			end
		end

	add_header_lines (lst: ITERABLE [READABLE_STRING_8])
			-- Add collection of http header lines `lst'
		do
			across
				lst as c
			loop
				add_header_line (c.item)
			end
		end

	add_query_parameter (k: READABLE_STRING_GENERAL; v: READABLE_STRING_GENERAL)
			-- Add a query parameter `k=v'.
		do
			query_parameters.force (create {HTTP_CLIENT_REQUEST_STRING_PARAMETER}.make (k, v))
		end

	add_form_parameter (k: READABLE_STRING_GENERAL; v: READABLE_STRING_GENERAL)
			-- Add a form parameter `k'= `v'.
		do
			form_parameters.force (create {HTTP_CLIENT_REQUEST_STRING_PARAMETER}.make (k, v))
		end

	add_file_form_parameter (k: READABLE_STRING_GENERAL; a_location: READABLE_STRING_GENERAL; a_content_type: detachable READABLE_STRING_8)
			-- Add a form file parameter named `k`, located at `a_location`, with optional content type `a_content_type`.
		require
			has_no_upload_data_or_filename: not has_upload_data and not has_upload_filename
		local
			param: HTTP_CLIENT_REQUEST_FILE_PARAMETER
		do
			create param.make_with_path (k, create {PATH}.make_from_string (a_location))
			param.set_content_type (a_content_type)
			form_parameters.force (param)
		end

	set_credentials_required (b: BOOLEAN)
			-- If b is True, credentials are required, otherwise just optional.
		do
			credentials_required := b
		end

	set_upload_data (a_data: like upload_data)
			-- Set `upload_data' to `a_data'
			--| note: the Current context can have upload_data XOR upload_filename, but not both.
		require
			has_no_upload_filename: (a_data /= Void and then not a_data.is_empty) implies not has_upload_filename
			has_no_form_data: (a_data /= Void and then not a_data.is_empty) implies  not has_form_data
		do
			if a_data = Void or else a_data.is_empty then
				upload_data := Void
			else
				upload_data := a_data
			end
		ensure
			(a_data /= Void and then not a_data.is_empty) implies (has_upload_data and not has_upload_filename)
		end

	set_upload_filename (a_fn: detachable READABLE_STRING_GENERAL)
			-- Set `upload_filename' to `a_fn'
			--| note: the Current context can have upload_data XOR upload_filename, but not both.	
		require
			has_no_upload_data: (a_fn /= Void and then not a_fn.is_empty) implies not has_upload_data
			has_no_form_data: (a_fn /= Void and then not a_fn.is_empty) implies  not has_form_data
		do
			if a_fn = Void or else a_fn.is_empty then
				upload_filename := Void
			else
				create upload_filename.make_from_string_general (a_fn)
			end
		ensure
			(a_fn /= Void and then not a_fn.is_empty) implies (has_upload_filename and not has_upload_data)
		end

	set_write_agent (agt: like write_agent)
			-- Set `write_agent' to `agt'.
		do
			write_agent := agt
		end

	set_output_file (f: FILE)
			-- Set `output_file' to `f'.
		require
			f_is_open_write: f.is_open_write
		do
			output_file := f
		end

	set_output_content_file (f: FILE)
			-- Set `output_content_file' to `f'.
		require
			f_is_open_write: f.is_open_write
		do
			output_content_file := f
		end

	set_http_version (v: detachable READABLE_STRING_8)
		require
			valid_version: v = Void or else v.starts_with_general ("HTTP/")
		do
			if v = Void then
				http_version := Void
			else
				create http_version.make_from_string (v)
			end
		end

feature -- Status setting

	set_proxy (a_host: detachable READABLE_STRING_8; a_port: INTEGER)
			-- Set proxy to `a_host:a_port'.
			--| this can be used for instance with "http://fiddler2.com/" web debugging proxy.
		do
			if a_host = Void then
				proxy := Void
			else
				proxy := [a_host, a_port]
			end
		end

feature -- URL helpers

	append_query_parameters_to_url (a_url: STRING)
			-- Append parameters `a_parameters' to `a_url'
		require
			a_url_attached: a_url /= Void
		local
			l_first_param: BOOLEAN
		do
			if
				attached query_parameters as l_query_parameters and then
				not l_query_parameters.is_empty
			then
				if a_url.index_of ('?', 1) > 0 then
					l_first_param := False
				elseif a_url.index_of ('&', 1) > 0 then
					l_first_param := False
				else
					l_first_param := True
				end

				across
					query_parameters as ic
				loop
					if l_first_param then
						a_url.append_character ('?')
					else
						a_url.append_character ('&')
					end
					l_first_param := False
					uri_percent_encoder.append_query_name_encoded_string_to (ic.item.name, a_url)
					a_url.append_character ('=')
					ic.item.append_query_value_encoded_to (a_url)
				end
			end
		end

feature -- Conversion helpers

	query_parameters_to_url_encoded_string: STRING_8
			-- `query_parameters' as url-encoded string.
		do
			Result := parameters_to_uri_percent_encoded_string (query_parameters)
		end

	form_parameters_to_x_www_form_url_encoded_string: STRING_8
			-- `form_parameters' as x-www-form-urlencoded string.	
		do
			Result := parameters_to_x_www_form_urlencoded_string (form_parameters)
		end

	form_parameters_to_url_encoded_string: STRING_8
			-- `form_parameters' as url-encoded string.	
		obsolete "Use form_parameters_to_x_www_form_url_encoded_string [2017-05-31]"
		do
			Result := form_parameters_to_x_www_form_url_encoded_string
		end

feature {NONE} -- Implementation

	parameters_to_url_encoded_string (ht: HASH_TABLE [READABLE_STRING_32, READABLE_STRING_32]): STRING_8
			-- Build url encoded string using parameters from `ht'.
		do
			create Result.make (64)
			from
				ht.start
			until
				ht.after
			loop
				if not Result.is_empty then
					Result.append_character ('&')
				end
				Result.append (url_encoder.encoded_string (ht.key_for_iteration))
				Result.append_character ('=')
				Result.append (url_encoder.encoded_string (ht.item_for_iteration))
				ht.forth
			end
		end

	parameters_to_uri_percent_encoded_string (a_params: HTTP_CLIENT_REQUEST_PARAMETERS [HTTP_CLIENT_REQUEST_PARAMETER]): STRING_8
			-- Build query urlencoded string using parameters from `a_params'.
		do
			create Result.make (64)
			across
				a_params as ic
			loop
				if not Result.is_empty then
					Result.append_character ('&')
				end
				uri_percent_encoder.append_query_name_encoded_string_to (ic.item.name, Result)
				Result.append_character ('=')
				ic.item.append_query_value_encoded_to (Result)
			end
		end

	parameters_to_x_www_form_urlencoded_string (a_params: HTTP_CLIENT_REQUEST_PARAMETERS [HTTP_CLIENT_REQUEST_PARAMETER]): STRING_8
			-- Build x-www-form-urlencoded string using parameters from `a_params'.
		do
			create Result.make (64)
			across
				a_params as ic
			loop
				if not Result.is_empty then
					Result.append_character ('&')
				end
				x_www_form_url_encoder.append_percent_encoded_string_to (ic.item.name, Result)
				Result.append_character ('=')
				ic.item.append_form_url_encoded_to (Result)
			end
		end

	x_www_form_url_encoder: X_WWW_FORM_URL_ENCODER
			-- Shared x-www-form-urlencoded encoder.
		once
			create Result
		end

	uri_percent_encoder: URI_PERCENT_ENCODER
		once
			create Result
		end

	url_encoder: URL_ENCODER
			-- Shared URL encoder.
		once
			create Result
		end

note
	copyright: "2011-2017, 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
