Property definitions

nitcorn $ HttpRequestParser :: defaultinit
# Utility class to parse a request string and build a `HttpRequest`
#
# The main method is `parse_http_request`.
class HttpRequestParser
	# The current `HttpRequest` under construction
	private var http_request: HttpRequest is noinit

	# Untreated body
	private var body = ""

	# Lines of the header
	private var header_fields = new Array[String]

	# Words of the first line
	private var first_line = new Array[String]

	# Parse the `first_line`, `header_fields` and `body` of `full_request`.
	fun parse_http_request(full_request: String): nullable HttpRequest
	do
		clear_data

		var http_request = new HttpRequest
		self.http_request = http_request

		segment_http_request(full_request)

		# Parse first line, looks like "GET dir/index.html?user=xymus HTTP/1.0"
		if first_line.length < 3 then
			print "HTTP error: request first line apprears invalid: {first_line}"
			return null
		end
		http_request.method = first_line[0]
		http_request.url = first_line[1]
		http_request.http_version = first_line[2]

		# GET args
		if http_request.url.has('?') then
			http_request.uri = first_line[1].substring(0, first_line[1].index_of('?'))
			http_request.query_string = first_line[1].substring_from(first_line[1].index_of('?')+1)

			var parse_url = parse_url
			http_request.get_args = parse_url
			http_request.all_args.add_all parse_url
		else
			http_request.uri = first_line[1]
		end

		# POST args
		if http_request.method == "POST" or http_request.method == "PUT" then
			http_request.body = body
			var lines = body.split_with('&')
			for line in lines do if not line.trim.is_empty then
				var parts = line.split_once_on('=')
				if parts.length > 1 then
					var decoded = parts[1].replace('+', " ").from_percent_encoding
					http_request.post_args[parts[0]] = decoded
					http_request.all_args[parts[0]] = decoded
				end
			end
		end

		# Headers
		for i in header_fields do
			var temp_field = i.split_with(": ")

			if temp_field.length == 2 then
				http_request.header[temp_field[0]] = temp_field[1]
			end
		end

		# Cookies
		if http_request.header.keys.has("Cookie") then
			var cookie = http_request.header["Cookie"]
			for couple in cookie.split_with(';') do
				var words = couple.trim.split_with('=')
				if words.length != 2 then continue
				http_request.cookie[words[0]] = words[1]
			end
		end

		return http_request
	end

	private fun clear_data
	do
		first_line.clear
		header_fields.clear
	end

	private fun segment_http_request(http_request: String): Bool
	do
		var header_end = http_request.search("\r\n\r\n")

		if header_end == null then
			header_fields = http_request.split_with("\r\n")
		else
			header_fields = http_request.substring(0, header_end.from).split_with("\r\n")
			body = http_request.substring(header_end.after, http_request.length-1)
		end

		# If a line of the http_request is long it may change line, it has " " at the
		# end to indicate this. This section turns them into 1 line.
		if header_fields.length > 1 and header_fields[0].has_suffix(" ") then
			var temp_req = header_fields[0].substring(0, header_fields[0].length-1) + header_fields[1]

			first_line  = temp_req.split_with(' ')
			header_fields.shift
			header_fields.shift

			if first_line.length != 3 then return false
		else
			first_line = header_fields[0].split_with(' ')
			header_fields.shift

			if first_line.length != 3 then return false
		end

		# Cut off the header in lines
		var pos = 0
		while pos < header_fields.length do
			if pos < header_fields.length-1 and header_fields[pos].has_suffix(" ") then
				header_fields[pos] = header_fields[pos].substring(0, header_fields[pos].length-1) + header_fields[pos+1]
				header_fields.remove_at(pos+1)
				pos = pos-1
			end
			pos = pos+1
		end

		return true
	end

	# Extract args from the URL
	private fun parse_url: HashMap[String, String]
	do
		var query_strings = new HashMap[String, String]

		if http_request.url.has('?') then
			var get_args = http_request.query_string.split_with("&")
			for param in get_args do
				var key_value = param.split_with("=")
				if key_value.length < 2 then continue

				var key = key_value[0].from_percent_encoding
				var value = key_value[1].from_percent_encoding
				query_strings[key] = value
			end
		end

		return query_strings
	end
end
lib/nitcorn/http_request.nit:101,1--251,3