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]
62 # Utility class to parse a request string and build a `HttpRequest`
64 # The main method is `parse_http_request`.
65 class HttpRequestParser
66 # The current `HttpRequest` under construction
67 private var http_request
: HttpRequest is noinit
73 private var header_fields
= new Array[String]
75 # Words of the first line
76 private var first_line
= new Array[String]
80 fun parse_http_request
(full_request
: String): nullable HttpRequest
84 var http_request
= new HttpRequest
85 self.http_request
= http_request
87 segment_http_request
(full_request
)
89 # Parse first line, looks like "GET dir/index.html?user=xymus HTTP/1.0"
90 if first_line
.length
< 3 then
91 print
"HTTP error: request first line apprears invalid: {first_line}"
94 http_request
.method
= first_line
[0]
95 http_request
.url
= first_line
[1]
96 http_request
.http_version
= first_line
[2]
99 if http_request
.url
.has
('?') then
100 http_request
.uri
= first_line
[1].substring
(0, first_line
[1].index_of
('?'))
101 http_request
.query_string
= first_line
[1].substring_from
(first_line
[1].index_of
('?')+1)
103 var parse_url
= parse_url
104 http_request
.get_args
= parse_url
105 http_request
.all_args
.recover_with parse_url
107 http_request
.uri
= first_line
[1]
111 if http_request
.method
== "POST" then
112 var lines
= body
.split_with
('&')
113 for line
in lines
do if not line
.trim
.is_empty
then
114 var parts
= line
.split_once_on
('=')
115 if parts
.length
> 1 then
116 var decoded
= parts
[1].replace
('+', " ").from_percent_encoding
117 if decoded
== null then
121 http_request
.post_args
[parts
[0]] = decoded
122 http_request
.all_args
[parts
[0]] = decoded
124 print
"POST Error: {line} format error on {line}"
130 for i
in header_fields
do
131 var temp_field
= i
.split_with
(": ")
133 if temp_field
.length
== 2 then
134 http_request
.header
[temp_field
[0]] = temp_field
[1]
139 if http_request
.header
.keys
.has
("Cookie") then
140 var cookie
= http_request
.header
["Cookie"]
141 for couple
in cookie
.split_with
(';') do
142 var words
= couple
.trim
.split_with
('=')
143 if words
.length
!= 2 then continue
144 http_request
.cookie
[words
[0]] = words
[1]
151 private fun clear_data
157 private fun segment_http_request
(http_request
: String): Bool
159 var header_end
= http_request
.search
("\r\n\r\n")
161 if header_end
== null then
162 header_fields
= http_request
.split_with
("\r\n")
164 header_fields
= http_request
.substring
(0, header_end
.from
).split_with
("\r\n")
165 body
= http_request
.substring
(header_end
.after
, http_request
.length-1
)
168 # If a line of the http_request is long it may change line, it has " " at the
169 # end to indicate this. This section turns them into 1 line.
170 if header_fields
.length
> 1 and header_fields
[0].has_suffix
(" ") then
171 var temp_req
= header_fields
[0].substring
(0, header_fields
[0].length-1
) + header_fields
[1]
173 first_line
= temp_req
.split_with
(' ')
177 if first_line
.length
!= 3 then return false
179 first_line
= header_fields
[0].split_with
(' ')
182 if first_line
.length
!= 3 then return false
185 # Cut off the header in lines
187 while pos
< header_fields
.length
do
188 if pos
< header_fields
.length-1
and header_fields
[pos
].has_suffix
(" ") then
189 header_fields
[pos
] = header_fields
[pos
].substring
(0, header_fields
[pos
].length-1
) + header_fields
[pos
+1]
190 header_fields
.remove_at
(pos
+1)
199 # Extract args from the URL
200 private fun parse_url
: HashMap[String, String]
202 var query_strings
= new HashMap[String, String]
204 if http_request
.url
.has
('?') then
205 var get_args
= http_request
.query_string
.split_with
("&")
206 for param
in get_args
do
207 var key_value
= param
.split_with
("=")
208 if key_value
.length
< 2 then continue
209 query_strings
[key_value
[0]] = key_value
[1]