irc3/proto/
message.rs

1use std::fmt::{Debug, Display, Error as FmtError, Formatter};
2use std::result;
3use std::error;
4
5use super::escaping::{escape, unescape};
6
7pub const IRC_TRAILING: char = ':';
8pub const IRC_TAG_START: char = '@';
9pub const IRC_PREFIX_START: char = ':';
10pub const IRC_TAG_VALUE_SEP: char = '=';
11pub const IRC_TAG_VENDOR_SEP: char = '/';
12pub const IRC_TAG_END_SEP: char = ';';
13pub const IRC_PREFIX_USER_SEP: char = '!';
14pub const IRC_PREFIX_HOST_SEP: char = '@';
15
16pub type Result<T> = result::Result<T, ParseError>;
17
18// CHECK FUNCTIONS
19fn check_valid_key(ch: char) -> bool { ch.is_alphabetic() || ch.is_digit(10) || ch == '-' }
20
21// CHAR HELPER FUNCTIONS
22fn get_char_at(s: &str, ind: usize) -> char {
23	s[ind..].chars().next().unwrap()
24}
25
26pub struct ParseError {
27	message: &'static str,
28	kind: ParseErrorKind,
29}
30
31impl ParseError {
32	fn new_unexpected(msg: &'static str, ch: char) -> ParseError {
33		ParseError {
34			message: msg,
35			kind: ParseErrorKind::Unexpected(ch)
36		}
37	}
38
39	fn new_bad_syntax(msg: &'static str) -> ParseError {
40		ParseError {
41			message: msg,
42			kind: ParseErrorKind::BadSyntax
43		}
44	}
45
46	fn new_missing_command(msg: &'static str) -> ParseError {
47		ParseError {
48			message: msg,
49			kind: ParseErrorKind::BadSyntax,
50		}
51	}
52}
53
54impl error::Error for ParseError {
55
56}
57
58impl Display for ParseError {
59	fn fmt(&self, f: &mut Formatter) -> result::Result<(), FmtError> {
60		f.write_str(self.message)?;
61		f.write_str("; ")?;
62
63		match &self.kind {
64			ParseErrorKind::Unexpected(ch) => write!(f, "unexpected char '{}'", ch),
65			ParseErrorKind::BadSyntax => f.write_str("bad syntax"),
66			ParseErrorKind::MissingCommand => f.write_str("missing command"),
67		}
68	}
69}
70
71impl Debug for ParseError {
72	fn fmt(&self, f: &mut Formatter) -> result::Result<(), FmtError> {
73		<ParseError as Display>::fmt(self, f)
74	}
75}
76
77pub enum ParseErrorKind {
78	Unexpected(char),
79	BadSyntax,
80	MissingCommand,
81}
82
83/// A single IRC3 tag.
84///
85/// The [`vendor`] field of `Tag` is used to namespace tags.
86/// This allows to differentiate between two equally named tags.
87#[derive(PartialEq)]
88pub struct Tag {
89	key: String,
90	vendor: Option<String>,
91	value:  Option<String>,
92}
93
94impl Tag {
95	pub fn new(key: &str, value: Option<&str>) -> Tag {
96		Tag::new_with_vendor(key, value, None)
97	}
98
99	pub fn new_with_vendor(key: &str, value: Option<&str>, vendor: Option<&str>) -> Tag {
100		Tag {
101			key: String::from(key),
102			vendor: match vendor {
103				Some(s) => Some(String::from(s)),
104				None => None,
105			},
106			value: match value {
107				Some(s) => Some(String::from(s)),
108				None => None,
109			}
110		}
111	}
112
113	pub fn key(&self) -> &str {
114		&self.key
115	}
116
117	pub fn vendor(&self) -> Option<&str> {
118		match &self.vendor {
119			Some(s) => Some(s),
120			None => None,
121		}
122	}
123
124	pub fn value(&self) -> Option<&str> {
125		match &self.value {
126			Some(s) => Some(s),
127			None => None,
128		}
129	}
130
131	pub fn set_key(&mut self, key: &str) {
132		self.key = String::from(key);
133	}
134
135	pub fn set_vendor(&mut self, vendor: &str) {
136		self.vendor = Some(String::from(vendor));
137	}
138
139	pub fn set_value(&mut self, value: &str) {
140		self.value = Some(String::from(value));
141	}
142}
143
144impl Display for Tag {
145	fn fmt(&self, f: &mut Formatter) -> result::Result<(), FmtError> {
146		// turn the tag into the format used in the IRC 3.2 spec
147		if let Some(ven) = &self.vendor {
148			f.write_str(ven)?;
149			f.write_str("/")?;
150		}
151
152		f.write_str(&self.key)?;
153
154		if let Some(value) = &self.value {
155			f.write_str("=")?;
156			f.write_str(&escape(value))?;
157		}
158
159		Ok(())
160	}
161}
162
163impl Debug for Tag {
164	fn fmt(&self, f: &mut Formatter) -> result::Result<(), FmtError> {
165		f.write_str(&self.key)?;
166		if let Some(val) = &self.value { write!(f, "=\"{}\"", val)?; }
167		if let Some(ven) = &self.vendor { write!(f, " ({})", ven)?; }
168
169		Ok(())
170	}
171}
172
173/// A prefix of an IRC message.
174#[derive(PartialEq)]
175pub struct Prefix {
176	origin: String,
177	user: Option<String>,
178	host: Option<String>,
179}
180
181impl Prefix {
182	pub fn new(origin: &str, user: Option<&str>, host: Option<&str>) -> Prefix {
183		Prefix {
184			origin: String::from(origin),
185			user: match user {
186				Some(s) => Some(String::from(s)),
187				None => None,
188			},
189			host: match host {
190				Some(s) => Some(String::from(s)),
191				None => None,
192			}
193		}
194	}
195
196	pub fn parse(pre: &str) -> Prefix {
197		Prefix {
198			origin: match pre.find(IRC_PREFIX_USER_SEP) {
199				Some(i) => String::from(&pre[..i]),
200				None => match pre.find(IRC_PREFIX_HOST_SEP) {
201					Some(i) => String::from(&pre[..i]),
202					None => String::from(pre),
203				}
204			},
205			user: match pre.find(IRC_PREFIX_USER_SEP) {
206				Some(i) => match pre.find(IRC_PREFIX_HOST_SEP) {
207					Some(j) => Some(String::from(&pre[(i+IRC_PREFIX_USER_SEP.len_utf8())..j])),
208					None => Some(String::from(&pre[(i+IRC_PREFIX_USER_SEP.len_utf8())..])),
209				},
210				None => None,
211			},
212			host: match pre.find(IRC_PREFIX_HOST_SEP) {
213				Some(i) => Some(String::from(&pre[(i+IRC_PREFIX_HOST_SEP.len_utf8())..])),
214				None => None,
215			}
216		}
217	}
218
219	pub fn origin(&self) -> &str {
220		&self.origin
221	}
222
223	pub fn user(&self) -> Option<&str> {
224		match &self.user {
225			Some(s) => Some(s),
226			None => None,
227		}
228	}
229
230	pub fn host(&self) -> Option<&str> {
231		match &self.host {
232			Some(s) => Some(s),
233			None => None,
234		}
235	}
236
237	pub fn set_origin(&mut self, s: &str) {
238		self.origin = String::from(s);
239	}
240
241	pub fn set_user(&mut self, s: &str) {
242		self.user = Some(String::from(s));
243	}
244
245	pub fn set_host(&mut self, s: &str) {
246		self.host = Some(String::from(s));
247	}
248}
249
250impl Display for Prefix {
251	fn fmt(&self, f: &mut Formatter) -> result::Result<(), FmtError> {
252		f.write_str(&self.origin)?;
253		
254		if let Some(user) = &self.user {
255			f.write_str("!")?;
256			f.write_str(user)?;
257		}
258
259		if let Some(host) = &self.host {
260			f.write_str("@")?;
261			f.write_str(host)?;
262		}
263
264		Ok(())
265	}
266}
267
268impl Debug for Prefix {
269	fn fmt(&self, f: &mut Formatter) -> result::Result<(), FmtError> {
270		f.write_str("{")?;
271		write!(f, "origin=\"{}\"", self.origin)?;
272		if let Some(user) = &self.user {
273			write!(f, ", user=\"{}\"", user)?;
274		}
275		if let Some(host) = &self.host {
276			write!(f, ", host=\"{}\"", host)?;
277		}
278		f.write_str("}")
279	}
280}
281
282/// An iterator over the tags of a raw IRC message.
283pub struct TagsIter<'a> {
284	cursor: usize,
285	inner: &'a str,
286}
287
288impl<'a> TagsIter<'a> {
289	pub fn new(tags: &'a str) -> TagsIter<'a> {
290		TagsIter {
291			cursor: 0,
292			inner: tags,
293		}
294	}
295}
296
297impl<'a> Iterator for TagsIter<'a> {
298	type Item = Result<Tag>;
299
300	fn next(&mut self) -> Option<Self::Item> {
301		if self.cursor >= self.inner.len() {
302			return None
303		}
304
305		// start with a vendor
306		let mut vendor: Option<String> = None;
307
308		// reading metadata
309		let mut has_value = false;
310
311		// read key first
312		let mut start = self.cursor;
313		while self.cursor < self.inner.len() {
314			let ch = get_char_at(self.inner, self.cursor);
315			// check if this is a vendor string
316			if ch == IRC_TAG_VENDOR_SEP {
317				// set vendor string if it does not exist
318				if vendor.is_none() {
319					vendor = Some(String::from(&self.inner[start..self.cursor]));
320					start = self.cursor + ch.len_utf8();
321				} else {
322					return Some(Err(ParseError::new_bad_syntax(
323					"tags may only have one vendor")));
324				}
325
326				self.cursor += ch.len_utf8();
327				continue;
328			}
329
330			// check if we need to break from reading the tag
331			if ch == IRC_TAG_VALUE_SEP {
332				has_value = true;
333				break;
334			}
335
336			// check if the tag ends
337			if ch == IRC_TAG_END_SEP {
338				break;
339			}
340
341			// check if the character conforms to the standard
342			if !check_valid_key(ch) {
343				return Some(Err(ParseError::new_unexpected(
344				"tags can only contain letters, digits or hyphens", ch)));
345			}
346
347			self.cursor += ch.len_utf8();
348		}
349
350		// write key range into a tag
351		let tag = Tag{
352			key: String::from(&self.inner[start..self.cursor]),
353			vendor: vendor,
354			value: if has_value {
355				// skip over '='
356				self.cursor += IRC_TAG_VALUE_SEP.len_utf8();
357
358				// read value
359				let start = self.cursor;
360				while self.cursor < self.inner.len() {
361					let ch = get_char_at(self.inner, self.cursor);
362
363					if ch == IRC_TAG_END_SEP {
364						break;
365					}
366
367					self.cursor += ch.len_utf8();
368				}
369
370				// clone tag
371				Some(unescape(&self.inner[start..self.cursor]))
372				// the escaping of values will occur during accesses to Tag
373			} else {
374				None
375			},
376		};
377
378		// skip over semicolon
379		self.cursor += IRC_TAG_END_SEP.len_utf8();
380
381		Some(Ok(tag))
382	}
383}
384
385/// Used to iterate over the contents of a raw IRC message's params
386pub struct ParamsIter<'a> {
387	cursor: usize,
388	inner: &'a str,
389}
390
391impl<'a> ParamsIter<'a> {
392	pub fn new(params: &'a str) -> ParamsIter<'a> {
393		ParamsIter {
394			cursor: 0,
395			inner: params,
396		}
397	}
398}
399
400impl<'a> Iterator for ParamsIter<'a> {
401	type Item = &'a str;
402
403	fn next(&mut self) -> Option<Self::Item> {
404		if self.cursor >= self.inner.len() {
405			return None;
406		}
407
408		// reading metadata
409		let mut trailing = false;
410
411		let mut start = self.cursor;
412		let mut offset: usize = 0;
413		while self.cursor < self.inner.len() {
414			let ch = get_char_at(self.inner, self.cursor);
415			if ch.is_whitespace() && !trailing {
416				offset += ch.len_utf8();
417				// consume whitespace
418				while self.cursor+offset < self.inner.len() {
419					let ch = get_char_at(self.inner, self.cursor+offset);
420					if !ch.is_whitespace() {
421						break;
422					}
423
424					offset += ch.len_utf8();
425				}
426
427				break;
428			}
429
430			if start == self.cursor {
431				// start of the parameter,
432				if ch == IRC_TRAILING {
433					trailing = true;
434					start += ch.len_utf8();
435				}
436			}
437
438			self.cursor += ch.len_utf8();
439		}
440
441		// return a result
442		let slice = &self.inner[start..self.cursor];
443		self.cursor += offset;
444		Some(slice)
445	}
446}
447
448/// A full IRC message that can be reprocessed back into its encoded form.
449///
450/// An IRC message can be parsed from a raw message, or it can built using
451/// the [`new`] constructor and the builder functions [`with_tag`],
452/// [`with_tag_vendor`], [`with_prefix`] and [`with_param`].
453///
454/// # Examples
455/// ```rust
456/// use irc3::Message;
457///
458/// fn main() {
459/// 	const MSG: &'static str = ":dan!d@localhost PRIVMSG * :Hey guys, what's up?";
460///
461/// 	let built_message = Message::new("PRIVMSG")
462/// 		.with_prefix("dan", Some("d"), Some("localhost"))
463/// 		.with_param("*")
464/// 		.with_param("Hey guys, what's up?");
465/// 	
466/// 	let parsed_message = Message::parse(MSG).unwrap();
467///
468/// 	assert!(built_message == parsed_message);
469/// }
470/// ```
471#[derive(Debug, PartialEq)]
472pub struct Message {
473	tags: Vec<Tag>,
474	prefix: Option<Prefix>,
475	command: String,
476	params: Vec<String>,
477}
478
479impl Message {
480	pub fn new(command: &str) -> Message {
481		Message {
482			tags: Vec::new(),
483			prefix: None,
484			command: String::from(command),
485			params: Vec::new(),
486		}
487	}
488
489	pub fn parse<S: AsRef<str> + ?Sized>(s: &S) -> Result<Message> {
490		let mut line = s.as_ref();
491
492		if line.is_empty() {
493			return Err(ParseError::new_missing_command("missing irc command!"))
494		}
495
496		Ok(Message {
497			// check if the tags are present
498			tags: if line.starts_with(IRC_TAG_START) {
499				// tags are present, strip line of them!
500				let mut split = line.splitn(2, char::is_whitespace);
501
502				let tags = split.next().unwrap();
503				line = match split.next() {
504					Some(line) => line,
505					None => return Err(ParseError::new_missing_command("missing irc command!")),
506				};
507
508				TagsIter::new(&tags[IRC_TAG_START.len_utf8()..]).filter_map(|r| match r {
509					Ok(t) => Some(t),
510					Err(_) => None,
511				}).collect()
512			} else {
513				Vec::new()
514			},
515			// check if the prefix is present
516			prefix: if line.starts_with(IRC_PREFIX_START) {
517				// prefix is present!
518				let mut split = line.splitn(2, char::is_whitespace);
519
520				let prefix = split.next().unwrap();
521				line = match split.next() {
522					Some(line) => line,
523					None => return Err(ParseError::new_missing_command("missing irc command!")),
524				};
525
526				Some(Prefix::parse(&prefix[IRC_PREFIX_START.len_utf8()..]))
527			} else {
528				None
529			},
530			// get the command (it must be present)
531			command: {
532				String::from(line.split(char::is_whitespace).next().unwrap())
533			},
534			params: {
535				match line.splitn(2, char::is_whitespace).skip(1).next() {
536					Some(line) => ParamsIter::new(line).map(|s| String::from(s)).collect(),
537					None => Vec::new(),
538				}
539			}
540		})
541	}
542
543	pub fn command(&self) -> &str {
544		&self.command
545	}
546
547	pub fn has_prefix(&self) -> bool {
548		self.prefix.is_some()
549	}
550
551	pub fn origin(&self) -> Option<&str> {
552		match &self.prefix {
553			Some(p) => Some(p.origin()),
554			None => None,
555		}
556	}
557
558	pub fn user(&self) -> Option<&str> {
559		match &self.prefix {
560			Some(p) => p.user(),
561			None => None,
562		}
563	}
564
565	pub fn host(&self) -> Option<&str> {
566		match &self.prefix {
567			Some(p) => p.host(),
568			None => None,
569		}
570	}
571
572	pub fn params(&self) -> std::slice::Iter<String> {
573		self.params.iter()
574	}
575
576	pub fn param(&self, ind: usize) -> Option<&str> {
577		match self.params.iter().skip(ind).next() {
578			Some(s) => Some(s),
579			None => None,
580		}
581	}
582
583	pub fn tags(&self) -> std::slice::Iter<Tag> {
584		self.tags.iter()
585	}
586
587	pub fn tag(&self, key: &str) -> Option<&Tag> {
588		self.tags.iter().filter(|t| t.key() == key).next()
589	}
590
591	// Builder things
592	pub fn with_prefix(mut self, origin: &str, user: Option<&str>, host: Option<&str>) -> Message {
593		self.prefix = Some(Prefix::new(origin, user, host));
594		self
595	}
596
597	pub fn with_tag(self, key: &str, value: Option<&str>) -> Message {
598		self.with_tag_vendor(key, value, None)
599	}
600
601	pub fn with_tag_vendor(mut self, key: &str, value: Option<&str>, vendor: Option<&str>) -> Message {
602		self.tags.push(Tag::new_with_vendor(key, value, vendor));
603		self
604	}
605
606	pub fn with_param(mut self, param: &str) -> Message {
607		self.params.push(String::from(param));
608		self
609	}
610}
611
612impl Display for Message {
613	fn fmt(&self, f: &mut Formatter) -> result::Result<(), FmtError> {
614		if self.tags.len() > 0 {
615			f.write_str("@")?;
616			for (i, t) in self.tags.iter().enumerate() {
617				if i > 0 {
618					f.write_str(";")?;
619				}
620
621				write!(f, "{}", t)?;
622			}
623			f.write_str(" ")?;
624		}
625
626		if let Some(prefix) = &self.prefix {
627			write!(f, "{}", prefix)?;
628			f.write_str(" ")?;
629		}
630
631		f.write_str(&self.command)?;
632
633		if self.params.len() > 0 {
634			f.write_str(" ")?;
635
636			for (i, p) in self.params.iter().enumerate() {
637				if i > 0 {
638					f.write_str(" ")?;
639				}
640
641				// if this is the last parameter, do trailing
642				if i >= self.params.len() - 1 {
643					// try to write a trailing
644					if p.split(char::is_whitespace).count() > 1 {
645						f.write_str(":")?;
646						f.write_str(p)?;
647					} else {
648						f.write_str(p)?;
649					}
650				} else {
651					f.write_str(p)?;
652				}
653			}
654		}
655
656		Ok(())
657	}
658}