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