Skip to main content

ircmsgprs/
parser.rs

1use std::fmt;
2use std::iter::Peekable;
3use std::str::Chars;
4use std::vec::Vec;
5
6#[derive(Default, Debug)]
7pub struct Message {
8    pub server: Option<String>,
9    pub nick: Option<String>,
10    pub user: Option<String>,
11    pub host: Option<String>,
12    pub command: String,
13    pub params: Vec<String>,
14}
15
16impl fmt::Display for Message {
17    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
18        if self.server.is_some() {
19            write!(f, "Server: {} ", self.server.as_ref().unwrap())?;
20        } else if self.nick.is_some() {
21            if self.user.is_some() {
22                write!(
23                    f,
24                    "User: {}!{}",
25                    self.nick.as_ref().unwrap(),
26                    self.user.as_ref().unwrap()
27                )?;
28            } else {
29                write!(f, "User: {}", self.nick.as_ref().unwrap())?;
30            }
31
32            if self.host.is_some() {
33                write!(f, "@{}, ", self.host.as_ref().unwrap())?;
34            }
35        }
36
37        write!(f, "Command: {}, Params: {:?}", self.command, self.params)
38    }
39}
40
41pub struct Parser;
42
43impl Parser {
44    pub fn new() -> Parser {
45        Parser {}
46    }
47
48    pub fn parse<T: AsRef<str>>(&mut self, line: T) -> Option<Message> {
49        let data = line.as_ref().trim_end_matches("\r\n");
50        if data.len() > 510 {
51            return None;
52        }
53
54        let mut data = data.chars().peekable();
55        let mut message = Message {
56            ..Default::default()
57        };
58
59        let chr = data.peek()?;
60        if *chr == ':' {
61            let prefix = self.parse_prefix(&mut data);
62            if prefix.find('@').is_some() {
63                let mut chunks = prefix.split('@');
64                let name = chunks.nth(0).unwrap().to_string();
65                if name.find('!').is_some() {
66                    let mut inner_chunks = name.split('!');
67                    message.nick = Some(inner_chunks.nth(0).unwrap().to_string());
68                    message.user = Some(inner_chunks.nth(0).unwrap().to_string());
69                } else {
70                    message.nick = Some(name);
71                }
72
73                message.host = Some(chunks.nth(0).unwrap().to_string());
74            } else {
75                message.server = Some(prefix);
76            }
77        }
78
79        if let Some(command) = self.parse_command(&mut data) {
80            message.command = command;
81        } else {
82            return None;
83        }
84
85        if let Some(params) = self.parse_params(&mut data) {
86            message.params = params;
87        } else {
88            return None;
89        }
90
91        Some(message)
92    }
93
94    fn parse_prefix(&mut self, data: &mut Peekable<Chars>) -> String {
95        return data
96            .by_ref()
97            .skip(1)
98            .take_while(|c| *c != ' ')
99            .collect::<String>();
100    }
101
102    fn parse_command(&mut self, data: &mut Peekable<Chars>) -> Option<String> {
103        let chr = data.peek()?;
104        if chr.is_numeric() {
105            let numeric = data
106                .by_ref()
107                .take_while(|c| c.is_numeric())
108                .collect::<String>();
109
110            // Numeric replies must be exactly 3 digits long
111            if numeric.len() == 3 {
112                return Some(numeric);
113            }
114
115            return None;
116        } else {
117            return Some(data.by_ref().take_while(|c| *c != ' ').collect::<String>());
118        }
119    }
120
121    fn parse_params(&mut self, data: &mut Peekable<Chars>) -> Option<Vec<String>> {
122        let mut params = Vec::new();
123        while let Some(chr) = data.peek() {
124            if *chr == ':' {
125                params.push(data.by_ref().skip(1).collect::<String>());
126            } else {
127                params.push(data.by_ref().take_while(|c| *c != ' ').collect::<String>());
128            }
129
130            if params.len() > 15 {
131                return None;
132            }
133        }
134
135        return Some(params);
136    }
137}