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]
113 fun parse_http_request
(full_request
: String): nullable HttpRequest
117 var http_request
= new HttpRequest
118 self.http_request
= http_request
120 segment_http_request
(full_request
)
122 # Parse first line, looks like "GET dir/index.html?user=xymus HTTP/1.0"
123 if first_line
.length
< 3 then
124 print
"HTTP error: request first line apprears invalid: {first_line}"
127 http_request
.method
= first_line
[0]
128 http_request
.url
= first_line
[1]
129 http_request
.http_version
= first_line
[2]
132 if http_request
.url
.has
('?') then
133 http_request
.uri
= first_line
[1].substring
(0, first_line
[1].index_of
('?'))
134 http_request
.query_string
= first_line
[1].substring_from
(first_line
[1].index_of
('?')+1)
136 var parse_url
= parse_url
137 http_request
.get_args
= parse_url
138 http_request
.all_args
.recover_with parse_url
140 http_request
.uri
= first_line
[1]
144 if http_request
.method
== "POST" then
145 var lines
= body
.split_with
('&')
146 for line
in lines
do if not line
.trim
.is_empty
then
147 var parts
= line
.split_once_on
('=')
148 if parts
.length
> 1 then
149 var decoded
= parts
[1].replace
('+', " ").from_percent_encoding
150 http_request
.post_args
[parts
[0]] = decoded
151 http_request
.all_args
[parts
[0]] = decoded
153 print
"POST Error: {line} format error on {line}"
159 for i
in header_fields
do
160 var temp_field
= i
.split_with
(": ")
162 if temp_field
.length
== 2 then
163 http_request
.header
[temp_field
[0]] = temp_field
[1]
168 if http_request
.header
.keys
.has
("Cookie") then
169 var cookie
= http_request
.header
["Cookie"]
170 for couple
in cookie
.split_with
(';') do
171 var words
= couple
.trim
.split_with
('=')
172 if words
.length
!= 2 then continue
173 http_request
.cookie
[words
[0]] = words
[1]
180 private fun clear_data
186 private fun segment_http_request
(http_request
: String): Bool
188 var header_end
= http_request
.search
("\r\n\r\n")
190 if header_end
== null then
191 header_fields
= http_request
.split_with
("\r\n")
193 header_fields
= http_request
.substring
(0, header_end
.from
).split_with
("\r\n")
194 body
= http_request
.substring
(header_end
.after
, http_request
.length-1
)
197 # If a line of the http_request is long it may change line, it has " " at the
198 # end to indicate this. This section turns them into 1 line.
199 if header_fields
.length
> 1 and header_fields
[0].has_suffix
(" ") then
200 var temp_req
= header_fields
[0].substring
(0, header_fields
[0].length-1
) + header_fields
[1]
202 first_line
= temp_req
.split_with
(' ')
206 if first_line
.length
!= 3 then return false
208 first_line
= header_fields
[0].split_with
(' ')
211 if first_line
.length
!= 3 then return false
214 # Cut off the header in lines
216 while pos
< header_fields
.length
do
217 if pos
< header_fields
.length-1
and header_fields
[pos
].has_suffix
(" ") then
218 header_fields
[pos
] = header_fields
[pos
].substring
(0, header_fields
[pos
].length-1
) + header_fields
[pos
+1]
219 header_fields
.remove_at
(pos
+1)
228 # Extract args from the URL
229 private fun parse_url
: HashMap[String, String]
231 var query_strings
= new HashMap[String, String]
233 if http_request
.url
.has
('?') then
234 var get_args
= http_request
.query_string
.split_with
("&")
235 for param
in get_args
do
236 var key_value
= param
.split_with
("=")
237 if key_value
.length
< 2 then continue
238 query_strings
[key_value
[0]] = key_value
[1]