note
	description : "[
			Response retrieved by the client
		]"
	date        : "$Date$"
	revision    : "$Revision$"

class
	HTTP_CLIENT_RESPONSE

create
	make

feature {NONE} -- Initialization

	make (a_url: READABLE_STRING_8)
			-- Initialize `Current'.
		do
				--| Default values
			status := 200
			url := a_url
			create {STRING_8} raw_header.make_empty
		end

feature -- Status

	error_occurred: BOOLEAN
			-- Error occurred during request

	error_message: detachable READABLE_STRING_8

feature {HTTP_CLIENT_REQUEST} -- Status setting

	set_error_occurred (b: BOOLEAN)
			-- Set `error_occurred' to `b'
		do
			error_occurred := b
		end

	set_error_message (m: READABLE_STRING_8)
			-- Set `error_message' to `m'
		do
			set_error_occurred (True)
			error_message := m
		end

feature -- Access

	url: STRING_8
			-- URL associated with Current response

	status: INTEGER assign set_status
			-- Status code of the response.

	status_line: detachable READABLE_STRING_8

	http_version: detachable READABLE_STRING_8
			-- http version associated with `status_line'.

	raw_header: READABLE_STRING_8
			-- Raw http header of the response.	

	redirections: detachable ARRAYED_LIST [TUPLE [status_line: detachable READABLE_STRING_8; raw_header: READABLE_STRING_8; body: detachable READABLE_STRING_8]]
			-- Header of previous redirection if any.

	redirections_count: INTEGER
			-- Number of redirections.
		do
			if attached redirections as lst then
				Result := lst.count
			end
		end

	header (a_name: READABLE_STRING_8): detachable READABLE_STRING_8
			-- Header entry value related to `a_name'
			-- if multiple entries, just concatenate them using comma character
			--| See: http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html
			--| Multiple message-header fields with the same field-name MAY be present in a message
			--| if and only if the entire field-value for that header field is defined as a comma-separated list [i.e., #(values)].
			--| It MUST be possible to combine the multiple header fields into one "field-name: field-value" pair,
			--| without changing the semantics of the message, by appending each subsequent field-value to the first, each separated by a comma.
			--| The order in which header fields with the same field-name are received is therefore significant
			--| to the interpretation of the combined field value, and thus a proxy MUST NOT change the order of
			--| these field values when a message is forwarded.			
		local
			s: detachable STRING_8
			k,v: READABLE_STRING_8
		do
			across
				headers as hds
			loop
				k := hds.item.name
				if k.same_string (a_name) then
					v := hds.item.value
					if s = Void then
						create s.make_from_string (v)
					else
						s.append_character (',')
						s.append (v)
					end
				end
			end
			Result := s
		end

	multiple_header (a_name: READABLE_STRING_8): detachable LIST [READABLE_STRING_8]
			-- Header multiple entries related to `a_name'
		local
			k: READABLE_STRING_8
		do
			across
				headers as hds
			loop
				k := hds.item.name
				if k.same_string (a_name) then
					if Result = Void then
						create {ARRAYED_LIST [READABLE_STRING_8]} Result.make (1)
					end
					Result.force (hds.item.value)
				end
			end
		end

	headers: LIST [TUPLE [name: READABLE_STRING_8; value: READABLE_STRING_8]]
			-- Computed table of http headers of the response.
			--| We use a LIST since one might have multiple message-header fields with the same field-name
			--| Then the user can handle those case using default or custom concatenation
			--| (note: `header' is concatenating using comma)
		local
			tb: like internal_headers
			pos, l_start, l_end, n, c: INTEGER
			h: like raw_header
			k: STRING_8
		do
			tb := internal_headers
			if tb = Void then
				create tb.make (3)
				h := raw_header
				from
					pos := 1
					n := h.count
				until
					pos = 0 or pos > n
				loop
					l_start := pos
						--| Left justify
					from until not h [l_start].is_space loop
						l_start := l_start + 1
					end
					pos := h.index_of ('%N', l_start)
					if pos > 0 then
						l_end := pos - 1
					elseif l_start < n then
						l_end := n + 1
					else
							-- Empty line
						l_end := 0
					end
					if l_end > 0 then
							--| Right justify
						from until not h [l_end].is_space loop
							l_end := l_end - 1
						end
						c := h.index_of (':', l_start)
						if c > 0 then
							k := h.substring (l_start, c - 1)
							k.right_adjust
							c := c + 1
							from until c <= n and not h[c].is_space loop
								c := c + 1
							end
							tb.force ([k, h.substring (c, l_end)])
						else
							check header_has_colon: c > 0 end
						end
					end
					pos := pos + 1
				end
				internal_headers := tb
			end
			Result := tb
		end

	body: detachable READABLE_STRING_8 assign set_body
			-- Content of the response

	response_message_source (a_include_redirection: BOOLEAN): STRING_8
			-- Full message source including redirection if any
		do
			create Result.make (1_024)
			if
				a_include_redirection and then
				attached redirections as lst
			then
				across
					lst as c
				loop
					if attached c.item.status_line as s then
						Result.append (s)
						Result.append ("%R%N")
					end
					Result.append (c.item.raw_header)
					Result.append ("%R%N")
					if attached c.item.body as l_body then
						Result.append (l_body)
					end
				end
			end
			if attached status_line as s then
				Result.append (s)
				Result.append ("%R%N")
			end
			Result.append (raw_header)
			Result.append ("%R%N")
			if attached body as l_body then
				Result.append (l_body)
			end
		end

feature -- Status report

	is_http_1_0: BOOLEAN
			-- Is response using HTTP/1.0 protocole?
			--| Note: it is relevant once the raw header are set.		
		do
			Result := attached http_version as v and then v.same_string ("1.0")
		end

	is_http_1_1: BOOLEAN
			-- Is response using HTTP/1.1 protocole?
			--| Note: it is relevant once the raw header are set.		
		do
			Result := attached http_version as v and then v.same_string ("1.1")
		end

feature -- Change

	set_http_version (v: like http_version)
			-- Set `http_version' to `v'.
		do
			http_version := v
		end

	set_status (s: INTEGER)
			-- Set response `status' code to `s'
		do
			status := s
		end

	set_status_line (a_line: detachable READABLE_STRING_8)
			-- Set status line to `a_line',
			-- and also `status' extracted from `a_line' if possible.
		local
			i,j: INTEGER
			s: READABLE_STRING_8
		do
			status_line := a_line
			http_version := Void

			if a_line /= Void then
				if a_line.starts_with ("HTTP/") then
					i := a_line.index_of (' ', 1)
					if i > 0 then
						http_version := a_line.substring (1 + 5, i - 1) -- ("HTTP/").count = 5
						i := i + 1
					end
				else
					i := 1
				end
					-- Get status code token.
				if i > 0 then
					j := a_line.index_of (' ', i)
					if j > i then
						s := a_line.substring (i, j - 1)
						if s.is_integer then
							set_status (s.to_integer)
						end
					end
				end
			end
		end

	set_response_message (a_source: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT)
			-- Parse `a_source' response message
			-- and set `status_line', `status', `header' and `body'.
			--| ctx is the context associated with the request
			--| it might be useful to deal with redirection customization...
		local
			i, j, pos: INTEGER
			l_has_location: BOOLEAN
			l_content_length: INTEGER
			s: READABLE_STRING_8
			l_status_line,h: detachable STRING_8
		do
			from
				i := 1
				j := 1
				pos := 1

				i := a_source.substring_index ("%R%N", i)
			until
				i = 0 or i > a_source.count
			loop
				s := a_source.substring (j, i - 1)
				if s.starts_with ("HTTP/") then
					--| Skip first line which is the status line
					--| ex: HTTP/1.1 200 OK%R%N
					j := i + 2
					l_status_line := s
					pos := j
				elseif s.is_empty then
					-- End of header  %R%N%R%N
					if attached raw_header as l_raw_header and then not l_raw_header.is_empty then
						add_redirection (status_line, l_raw_header, body)
					end

					h := a_source.substring (pos, i - 1)

					j := i + 2
					pos := j
					set_status_line (l_status_line)
					set_raw_header (h)

--					libcURL does not cache redirection content.
--					FIXME: check if this is customizable
--					if l_has_location then
--						if l_content_length > 0 then
--							j := pos + l_content_length - 1
--							l_body := a_source.substring (pos, j)
--							pos := j
--						else
--							l_body := Void
--						end
--						set_body (l_body)
--					end
					if not l_has_location then
						i := 0 -- exit loop
					end
					l_content_length := 0
					l_status_line := Void
					l_has_location := False
				else
					if s.starts_with ("Location:") then
						l_has_location := True
					elseif s.starts_with ("Content-Length:") then
						s := s.substring (16, s.count)
						if s.is_integer then
							l_content_length := s.to_integer
						end
					end
					j := i + 2
				end
				if i > 0 then
					i := a_source.substring_index ("%R%N", j)
				end
			end
			set_body (a_source.substring (pos, a_source.count))
		ensure
			parsed: response_message_source (True).count = a_source.count
		end

	set_raw_header (h: READABLE_STRING_8)
			-- Set http header `raw_header' to `h'
		local
			i: INTEGER
			s: STRING_8
		do
			raw_header := h
				--| Reset internal headers
			internal_headers := Void

				--| Set status line, right away.
			i := h.index_of ('%N', 1)
			if i > 0 then
				s := h.substring (1, i - 1)
				if s.starts_with ("HTTP/") then
					s.right_adjust
					set_status_line (s)
				end
			end
		end

	add_redirection (s: detachable READABLE_STRING_8; h: READABLE_STRING_8; a_body: detachable READABLE_STRING_8)
			-- Add redirection with status line `s' and raw header `h' and body `a_body' if any
		local
			lst: like redirections
		do
			lst := redirections
			if lst = Void then
				create lst.make (1)
				redirections := lst
			end
			lst.force ([s,h, a_body])
		end

	set_body (s: like body)
			-- Set `body' message to `s'
		do
			body := s
		end

feature {NONE} -- Implementation

	internal_headers: detachable ARRAYED_LIST [TUPLE [key: READABLE_STRING_8; value: READABLE_STRING_8]]
			-- Internal cached value for the headers			

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