1 /* Copyright (C) 2019 Open Information Security Foundation
2 *
3 * You can copy, redistribute or modify this Program under the terms of
4 * the GNU General Public License version 2 as published by the Free
5 * Software Foundation.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * version 2 along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
15 * 02110-1301, USA.
16 */
17
18 // written by Giuseppe Longo <giuseppe@glono.it>
19
20 use nom::*;
21 use nom::IResult;
22 use nom::character::{is_alphabetic, is_alphanumeric, is_space};
23 use nom::character::streaming::crlf;
24 use std;
25 use std::collections::HashMap;
26
27 #[derive(Debug)]
28 pub struct Header {
29 pub name: String,
30 pub value: String,
31 }
32
33 #[derive(Debug)]
34 pub struct Request {
35 pub method: String,
36 pub path: String,
37 pub version: String,
38 pub headers: HashMap<String, String>,
39 }
40
41 #[derive(Debug)]
42 pub struct Response {
43 pub version: String,
44 pub code: String,
45 pub reason: String,
46 }
47
48 #[derive(PartialEq, Debug, Clone)]
49 pub enum Method {
50 Register,
51 Custom(String),
52 }
53
54 #[inline]
is_token_char(b: u8) -> bool55 fn is_token_char(b: u8) -> bool {
56 is_alphanumeric(b) || b"!%'*+-._`".contains(&b)
57 }
58
59 #[inline]
is_method_char(b: u8) -> bool60 fn is_method_char(b: u8) -> bool {
61 is_alphabetic(b)
62 }
63
64 #[inline]
is_request_uri_char(b: u8) -> bool65 fn is_request_uri_char(b: u8) -> bool {
66 is_alphanumeric(b) || is_token_char(b) || b"~#@:".contains(&b)
67 }
68
69 #[inline]
is_version_char(b: u8) -> bool70 fn is_version_char(b: u8) -> bool {
71 is_alphanumeric(b) || b"./".contains(&b)
72 }
73
74 #[inline]
is_reason_phrase(b: u8) -> bool75 fn is_reason_phrase(b: u8) -> bool {
76 is_alphanumeric(b) || is_token_char(b) || b"$&(),/:;=?@[\\]^ ".contains(&b)
77 }
78
is_header_name(b: u8) -> bool79 fn is_header_name(b: u8) -> bool {
80 is_alphanumeric(b) || is_token_char(b)
81 }
82
is_header_value(b: u8) -> bool83 fn is_header_value(b: u8) -> bool {
84 is_alphanumeric(b) || is_token_char(b) || b"\"#$&(),/;:<=>?@[]{}()^|~\\\t\n\r ".contains(&b)
85 }
86
87 named!(pub sip_parse_request<&[u8], Request>,
88 do_parse!(
89 method: parse_method >> char!(' ') >>
90 path: parse_request_uri >> char!(' ') >>
91 version: parse_version >> crlf >>
92 headers: parse_headers >>
93 crlf >>
94 (Request { method: method.into(), path: path.into(), version: version.into(), headers: headers})
95 )
96 );
97
98 named!(pub sip_parse_response<&[u8], Response>,
99 do_parse!(
100 version: parse_version >> char!(' ') >>
101 code: parse_code >> char!(' ') >>
102 reason: parse_reason >> crlf >>
103 (Response { version: version.into(), code: code.into(), reason: reason.into() })
104 )
105 );
106
107 named!(#[inline], parse_method<&[u8], &str>,
108 map_res!(take_while!(is_method_char), std::str::from_utf8)
109 );
110
111 named!(#[inline], parse_request_uri<&[u8], &str>,
112 map_res!(take_while1!(is_request_uri_char), std::str::from_utf8)
113 );
114
115 named!(#[inline], parse_version<&[u8], &str>,
116 map_res!(take_while1!(is_version_char), std::str::from_utf8)
117 );
118
119 named!(#[inline], parse_code<&[u8], &str>,
120 map_res!(take!(3), std::str::from_utf8)
121 );
122
123 named!(#[inline], parse_reason<&[u8], &str>,
124 map_res!(take_while!(is_reason_phrase), std::str::from_utf8)
125 );
126
127 named!(#[inline], header_name<&[u8], &str>,
128 map_res!(take_while!(is_header_name), std::str::from_utf8)
129 );
130
131 named!(#[inline], header_value<&[u8], &str>,
132 map_res!(parse_header_value, std::str::from_utf8)
133 );
134
135 named!(
136 hcolon<char>,
137 delimited!(take_while!(is_space), char!(':'), take_while!(is_space))
138 );
139
140 named!(
141 message_header<Header>,
142 do_parse!(
143 n: header_name
144 >> hcolon
145 >> v: header_value
146 >> crlf
147 >> (Header {
148 name: String::from(n),
149 value: String::from(v)
150 })
151 )
152 );
153
154 named!(pub sip_take_line<&[u8], Option<String> >,
155 do_parse!(
156 line: map_res!(take_while1!(is_reason_phrase), std::str::from_utf8) >>
157 (Some(line.into()))
158 )
159 );
160
parse_headers(mut input: &[u8]) -> IResult<&[u8], HashMap<String, String>>161 pub fn parse_headers(mut input: &[u8]) -> IResult<&[u8], HashMap<String, String>> {
162 let mut headers_map: HashMap<String, String> = HashMap::new();
163 loop {
164 match crlf(input) as IResult<&[u8],_> {
165 Ok((_, _)) => {
166 break;
167 }
168 Err(Err::Error(_)) => {}
169 Err(Err::Failure(_)) => {}
170 Err(Err::Incomplete(e)) => return Err(Err::Incomplete(e)),
171 };
172 let (rest, header) = try_parse!(input, message_header);
173 headers_map.insert(header.name, header.value);
174 input = rest;
175 }
176
177 Ok((input, headers_map))
178 }
179
parse_header_value(buf: &[u8]) -> IResult<&[u8], &[u8]>180 fn parse_header_value(buf: &[u8]) -> IResult<&[u8], &[u8]> {
181 let mut end_pos = 0;
182 let mut trail_spaces = 0;
183 let mut idx = 0;
184 while idx < buf.len() {
185 match buf[idx] {
186 b'\n' => {
187 idx += 1;
188 if idx >= buf.len() {
189 return Err(Err::Incomplete(Needed::Size(1)));
190 }
191 match buf[idx] {
192 b' ' | b'\t' => {
193 idx += 1;
194 continue;
195 }
196 _ => {
197 return Ok((&buf[(end_pos + trail_spaces)..], &buf[..end_pos]));
198 }
199 }
200 }
201 b' ' | b'\t' => {
202 trail_spaces += 1;
203 }
204 b'\r' => {}
205 b => {
206 trail_spaces = 0;
207 if !is_header_value(b) {
208 return Err(Err::Incomplete(Needed::Size(1)));
209 }
210 end_pos = idx + 1;
211 }
212 }
213 idx += 1;
214 }
215 Ok((&b""[..], buf))
216 }
217
218 #[cfg(test)]
219 mod tests {
220
221 use crate::sip::parser::*;
222
223 #[test]
test_parse_request()224 fn test_parse_request() {
225 let buf: &[u8] = "REGISTER sip:sip.cybercity.dk SIP/2.0\r\n\
226 From: <sip:voi18063@sip.cybercity.dk>;tag=903df0a\r\n\
227 To: <sip:voi18063@sip.cybercity.dk>\r\n\
228 Content-Length: 0\r\n\
229 \r\n"
230 .as_bytes();
231
232 match sip_parse_request(buf) {
233 Ok((_, req)) => {
234 assert_eq!(req.method, "REGISTER");
235 assert_eq!(req.path, "sip:sip.cybercity.dk");
236 assert_eq!(req.version, "SIP/2.0");
237 assert_eq!(req.headers["Content-Length"], "0");
238 }
239 _ => {
240 assert!(false);
241 }
242 }
243 }
244
245 #[test]
test_parse_request_trail_space_header()246 fn test_parse_request_trail_space_header() {
247 let buf: &[u8] = "REGISTER sip:sip.cybercity.dk SIP/2.0\r\n\
248 From: <sip:voi18063@sip.cybercity.dk>;tag=903df0a\r\n\
249 To: <sip:voi18063@sip.cybercity.dk>\r\n\
250 Content-Length: 0 \r\n\
251 \r\n"
252 .as_bytes();
253
254 match sip_parse_request(buf) {
255 Ok((_, req)) => {
256 assert_eq!(req.method, "REGISTER");
257 assert_eq!(req.path, "sip:sip.cybercity.dk");
258 assert_eq!(req.version, "SIP/2.0");
259 assert_eq!(req.headers["Content-Length"], "0");
260 }
261 _ => {
262 assert!(false);
263 }
264 }
265 }
266
267 #[test]
test_parse_response()268 fn test_parse_response() {
269 let buf: &[u8] = "SIP/2.0 401 Unauthorized\r\n\
270 \r\n"
271 .as_bytes();
272
273 match sip_parse_response(buf) {
274 Ok((_, resp)) => {
275 assert_eq!(resp.version, "SIP/2.0");
276 assert_eq!(resp.code, "401");
277 assert_eq!(resp.reason, "Unauthorized");
278 }
279 _ => {
280 assert!(false);
281 }
282 }
283 }
284 }
285