1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Copyright 2013 Frederic Sevillano
4 # Copyright 2013 Jean-Philippe Caissy <jpcaissy@piji.ca>
5 # Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
7 # Licensed under the Apache License, Version 2.0 (the "License");
8 # you may not use this file except in compliance with the License.
9 # You may obtain a copy of the License at
11 # http://www.apache.org/licenses/LICENSE-2.0
13 # Unless required by applicable law or agreed to in writing, software
14 # distributed under the License is distributed on an "AS IS" BASIS,
15 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 # See the License for the specific language governing permissions and
17 # limitations under the License.
19 # Provides the `HttpRequest` class and services to create it
24 # A request received over HTTP, is build by `HttpRequestParser`
28 # HTTP protocol version
29 var http_version
: String
31 # Method of this request (GET or POST)
34 # The host targetter by this request (usually the server)
37 # The full URL requested by the client (including the `query_string`)
40 # The resource requested by the client (only the page, not the `query_string`)
43 # The string following `?` in the requested URL
46 # The header of this request
47 var header
= new HashMap[String, String]
49 # The content of the cookie of this request
50 var cookie
= new HashMap[String, String]
52 # The arguments passed with the GET method,
53 var get_args
= new HashMap[String, String]
55 # The arguments passed with the POST method
56 var post_args
= new HashMap[String, String]
58 # The arguments passed with the POST or GET method (with a priority on POST)
59 var all_args
= new HashMap[String, String]
61 # Returns argument `arg_name` in the request as a String
62 # or null if it was not found.
63 # Also cleans the String by trimming it.
64 # If the Strings happens to be empty after trimming,
65 # the method will return `null`
67 # NOTE: Prioritizes POST before GET
68 fun string_arg
(arg_name
: String): nullable String do
69 if not all_args
.has_key
(arg_name
) then return null
70 var s
= all_args
[arg_name
].trim
71 if s
.is_empty
then return null
75 # Returns argument `arg_name` as an Int or `null` if not found or not a number.
77 # NOTE: Prioritizes POST before GET
78 fun int_arg
(arg_name
: String): nullable Int do
79 if not all_args
.has_key
(arg_name
) then return null
80 var i
= all_args
[arg_name
]
81 if not i
.is_numeric
then return null
85 # Returns argument `arg_name` as a Bool or `null` if not found or not a boolean.
87 # NOTE: Prioritizes POST before GET
88 fun bool_arg
(arg_name
: String): nullable Bool do
89 if not all_args
.has_key
(arg_name
) then return null
90 var i
= all_args
[arg_name
]
91 if i
== "true" then return true
92 if i
== "false" then return false
97 # Utility class to parse a request string and build a `HttpRequest`
99 # The main method is `parse_http_request`.
100 class HttpRequestParser
101 # The current `HttpRequest` under construction
102 private var http_request
: HttpRequest is noinit
105 private var body
= ""
107 # Lines of the header
108 private var header_fields
= new Array[String]
110 # Words of the first line
111 private var first_line
= new Array[String]
115 fun parse_http_request
(full_request
: String): nullable HttpRequest
119 var http_request
= new HttpRequest
120 self.http_request
= http_request
122 segment_http_request
(full_request
)
124 # Parse first line, looks like "GET dir/index.html?user=xymus HTTP/1.0"
125 if first_line
.length
< 3 then
126 print
"HTTP error: request first line apprears invalid: {first_line}"
129 http_request
.method
= first_line
[0]
130 http_request
.url
= first_line
[1]
131 http_request
.http_version
= first_line
[2]
134 if http_request
.url
.has
('?') then
135 http_request
.uri
= first_line
[1].substring
(0, first_line
[1].index_of
('?'))
136 http_request
.query_string
= first_line
[1].substring_from
(first_line
[1].index_of
('?')+1)
138 var parse_url
= parse_url
139 http_request
.get_args
= parse_url
140 http_request
.all_args
.recover_with parse_url
142 http_request
.uri
= first_line
[1]
146 if http_request
.method
== "POST" then
147 var lines
= body
.split_with
('&')
148 for line
in lines
do if not line
.trim
.is_empty
then
149 var parts
= line
.split_once_on
('=')
150 if parts
.length
> 1 then
151 var decoded
= parts
[1].replace
('+', " ").from_percent_encoding
152 if decoded
== null then
156 http_request
.post_args
[parts
[0]] = decoded
157 http_request
.all_args
[parts
[0]] = decoded
159 print
"POST Error: {line} format error on {line}"
165 for i
in header_fields
do
166 var temp_field
= i
.split_with
(": ")
168 if temp_field
.length
== 2 then
169 http_request
.header
[temp_field
[0]] = temp_field
[1]
174 if http_request
.header
.keys
.has
("Cookie") then
175 var cookie
= http_request
.header
["Cookie"]
176 for couple
in cookie
.split_with
(';') do
177 var words
= couple
.trim
.split_with
('=')
178 if words
.length
!= 2 then continue
179 http_request
.cookie
[words
[0]] = words
[1]
186 private fun clear_data
192 private fun segment_http_request
(http_request
: String): Bool
194 var header_end
= http_request
.search
("\r\n\r\n")
196 if header_end
== null then
197 header_fields
= http_request
.split_with
("\r\n")
199 header_fields
= http_request
.substring
(0, header_end
.from
).split_with
("\r\n")
200 body
= http_request
.substring
(header_end
.after
, http_request
.length-1
)
203 # If a line of the http_request is long it may change line, it has " " at the
204 # end to indicate this. This section turns them into 1 line.
205 if header_fields
.length
> 1 and header_fields
[0].has_suffix
(" ") then
206 var temp_req
= header_fields
[0].substring
(0, header_fields
[0].length-1
) + header_fields
[1]
208 first_line
= temp_req
.split_with
(' ')
212 if first_line
.length
!= 3 then return false
214 first_line
= header_fields
[0].split_with
(' ')
217 if first_line
.length
!= 3 then return false
220 # Cut off the header in lines
222 while pos
< header_fields
.length
do
223 if pos
< header_fields
.length-1
and header_fields
[pos
].has_suffix
(" ") then
224 header_fields
[pos
] = header_fields
[pos
].substring
(0, header_fields
[pos
].length-1
) + header_fields
[pos
+1]
225 header_fields
.remove_at
(pos
+1)
234 # Extract args from the URL
235 private fun parse_url
: HashMap[String, String]
237 var query_strings
= new HashMap[String, String]
239 if http_request
.url
.has
('?') then
240 var get_args
= http_request
.query_string
.split_with
("&")
241 for param
in get_args
do
242 var key_value
= param
.split_with
("=")
243 if key_value
.length
< 2 then continue
244 query_strings
[key_value
[0]] = key_value
[1]