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>
6 # Copyright 2014 Alexandre Terrasa <alexandre@moz-code.org>
8 # Licensed under the Apache License, Version 2.0 (the "License");
9 # you may not use this file except in compliance with the License.
10 # You may obtain a copy of the License at
12 # http://www.apache.org/licenses/LICENSE-2.0
14 # Unless required by applicable law or agreed to in writing, software
15 # distributed under the License is distributed on an "AS IS" BASIS,
16 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 # See the License for the specific language governing permissions and
18 # limitations under the License.
20 # Provides the `HttpRequest` class and services to create it
26 # A request received over HTTP, is build by `HttpRequestParser`
30 private init is old_style_init
do end
32 # HTTP protocol version
33 var http_version
: String
35 # Method of this request (GET or POST)
38 # The full URL requested by the client (including the `query_string`)
41 # The resource requested by the client (only the page, not the `query_string`)
44 # The string following `?` in the requested URL
47 # The header of this request
48 var header
= new HashMap[String, String]
50 # The raw body of the request.
53 # The content of the cookie of this request
54 var cookie
= new HashMap[String, String]
56 # The arguments passed with the GET method,
57 var get_args
= new HashMap[String, String]
59 # The arguments passed with the POST method
60 var post_args
= new HashMap[String, String]
62 # The arguments passed with the POST or GET method (with a priority on POST)
63 var all_args
= new HashMap[String, String]
65 # Returns argument `arg_name` in the request as a String
66 # or null if it was not found.
67 # Also cleans the String by trimming it.
68 # If the Strings happens to be empty after trimming,
69 # the method will return `null`
71 # NOTE: Prioritizes POST before GET
72 fun string_arg
(arg_name
: String): nullable String do
73 if not all_args
.has_key
(arg_name
) then return null
74 var s
= all_args
[arg_name
].trim
75 if s
.is_empty
then return null
79 # Returns argument `arg_name` as an Int or `null` if not found or not an integer.
81 # NOTE: Prioritizes POST before GET
82 fun int_arg
(arg_name
: String): nullable Int do
83 if not all_args
.has_key
(arg_name
) then return null
84 var i
= all_args
[arg_name
]
85 if not i
.is_int
then return null
89 # Returns argument `arg_name` as a Bool or `null` if not found or not a boolean.
91 # NOTE: Prioritizes POST before GET
92 fun bool_arg
(arg_name
: String): nullable Bool do
93 if not all_args
.has_key
(arg_name
) then return null
94 var i
= all_args
[arg_name
]
95 if i
== "true" then return true
96 if i
== "false" then return false
101 # Utility class to parse a request string and build a `HttpRequest`
103 # The main method is `parse_http_request`.
104 class HttpRequestParser
105 # The current `HttpRequest` under construction
106 private var http_request
: HttpRequest is noinit
109 private var body
= ""
111 # Lines of the header
112 private var header_fields
= new Array[String]
114 # Words of the first line
115 private var first_line
= new Array[String]
117 # Parse the `first_line`, `header_fields` and `body` of `full_request`.
118 fun parse_http_request
(full_request
: String): nullable HttpRequest
122 var http_request
= new HttpRequest
123 self.http_request
= http_request
125 segment_http_request
(full_request
)
127 # Parse first line, looks like "GET dir/index.html?user=xymus HTTP/1.0"
128 if first_line
.length
< 3 then
129 print
"HTTP error: request first line apprears invalid: {first_line}"
132 http_request
.method
= first_line
[0]
133 http_request
.url
= first_line
[1]
134 http_request
.http_version
= first_line
[2]
137 if http_request
.url
.has
('?') then
138 http_request
.uri
= first_line
[1].substring
(0, first_line
[1].index_of
('?'))
139 http_request
.query_string
= first_line
[1].substring_from
(first_line
[1].index_of
('?')+1)
141 var parse_url
= parse_url
142 http_request
.get_args
= parse_url
143 http_request
.all_args
.add_all parse_url
145 http_request
.uri
= first_line
[1]
149 if http_request
.method
== "POST" or http_request
.method
== "PUT" then
150 http_request
.body
= body
151 var lines
= body
.split_with
('&')
152 for line
in lines
do if not line
.trim
.is_empty
then
153 var parts
= line
.split_once_on
('=')
154 if parts
.length
> 1 then
155 var decoded
= parts
[1].replace
('+', " ").from_percent_encoding
156 http_request
.post_args
[parts
[0]] = decoded
157 http_request
.all_args
[parts
[0]] = decoded
163 for i
in header_fields
do
164 var temp_field
= i
.split_with
(": ")
166 if temp_field
.length
== 2 then
167 http_request
.header
[temp_field
[0]] = temp_field
[1]
172 if http_request
.header
.keys
.has
("Cookie") then
173 var cookie
= http_request
.header
["Cookie"]
174 for couple
in cookie
.split_with
(';') do
175 var words
= couple
.trim
.split_with
('=')
176 if words
.length
!= 2 then continue
177 http_request
.cookie
[words
[0]] = words
[1]
184 private fun clear_data
190 private fun segment_http_request
(http_request
: String): Bool
192 var header_end
= http_request
.search
("\r\n\r\n")
194 if header_end
== null then
195 header_fields
= http_request
.split_with
("\r\n")
197 header_fields
= http_request
.substring
(0, header_end
.from
).split_with
("\r\n")
198 body
= http_request
.substring
(header_end
.after
, http_request
.length-1
)
201 # If a line of the http_request is long it may change line, it has " " at the
202 # end to indicate this. This section turns them into 1 line.
203 if header_fields
.length
> 1 and header_fields
[0].has_suffix
(" ") then
204 var temp_req
= header_fields
[0].substring
(0, header_fields
[0].length-1
) + header_fields
[1]
206 first_line
= temp_req
.split_with
(' ')
210 if first_line
.length
!= 3 then return false
212 first_line
= header_fields
[0].split_with
(' ')
215 if first_line
.length
!= 3 then return false
218 # Cut off the header in lines
220 while pos
< header_fields
.length
do
221 if pos
< header_fields
.length-1
and header_fields
[pos
].has_suffix
(" ") then
222 header_fields
[pos
] = header_fields
[pos
].substring
(0, header_fields
[pos
].length-1
) + header_fields
[pos
+1]
223 header_fields
.remove_at
(pos
+1)
232 # Extract args from the URL
233 private fun parse_url
: HashMap[String, String]
235 var query_strings
= new HashMap[String, String]
237 if http_request
.url
.has
('?') then
238 var get_args
= http_request
.query_string
.split_with
("&")
239 for param
in get_args
do
240 var key_value
= param
.split_with
("=")
241 if key_value
.length
< 2 then continue
243 var key
= key_value
[0].from_percent_encoding
244 var value
= key_value
[1].from_percent_encoding
245 query_strings
[key
] = value