syslog_loose/lib.rs
1#![deny(clippy::all)]
2#![deny(clippy::cargo)]
3extern crate nom;
4
5mod error;
6mod message;
7mod parsers;
8mod pri;
9mod procid;
10mod rfc3164;
11mod rfc5424;
12mod structured_data;
13mod timestamp;
14
15use chrono::prelude::*;
16use nom::{IResult, Parser as _, branch::alt};
17
18pub use message::{Message, Protocol};
19pub use pri::{SyslogFacility, SyslogSeverity, decompose_pri};
20pub use procid::ProcId;
21pub use structured_data::StructuredElement;
22pub use timestamp::IncompleteDate;
23
24/// Used to specify which variant of the RFC message we are expecting.
25#[derive(Clone, Copy, Debug)]
26pub enum Variant {
27    /// Either variant. First attempt to parse as RFC5424, if that fails try RFC3164.
28    Either,
29    /// Parse as [RFC3164](https://www.rfc-editor.org/rfc/rfc3164)
30    RFC3164,
31    /// Parse as [RFC5424](https://www.rfc-editor.org/rfc/rfc5424)
32    RFC5424,
33}
34
35/// Attempt to parse 5424 first, if this fails move on to 3164.
36fn parse<F, Tz: TimeZone + Copy>(
37    input: &str,
38    get_year: F,
39    tz: Option<Tz>,
40    variant: Variant,
41) -> IResult<&str, Message<&str>>
42where
43    F: FnOnce(IncompleteDate) -> i32 + Copy,
44{
45    match variant {
46        Variant::Either => {
47            alt((rfc5424::parse, |input| rfc3164::parse(input, get_year, tz))).parse(input.trim())
48        }
49        Variant::RFC3164 => rfc3164::parse(input.trim(), get_year, tz),
50        Variant::RFC5424 => rfc5424::parse(input.trim()),
51    }
52}
53
54///
55/// Parse the message.
56///
57/// # Arguments
58///
59/// * input - the string containing the message.
60/// * tz - a default timezone to use if the parsed timestamp does not specify one
61/// * get_year - a function that is called if the parsed message contains a date with no year.
62///   the function takes a (month, date, hour, minute, second) tuple and should return the year to use.
63/// * variant - the variant of message we are expecting to receive.
64///
65pub fn parse_message_with_year_tz<F, Tz: TimeZone + Copy>(
66    input: &str,
67    get_year: F,
68    tz: Option<Tz>,
69    variant: Variant,
70) -> Message<&str>
71where
72    F: FnOnce(IncompleteDate) -> i32 + Copy,
73    DateTime<FixedOffset>: From<DateTime<Tz>>,
74{
75    parse(input, get_year, tz, variant)
76        .map(|(_, result)| result)
77        .unwrap_or(
78            // If we fail to parse, the entire input becomes the message
79            // the rest of the fields are empty.
80            Message {
81                facility: None,
82                severity: None,
83                timestamp: None,
84                hostname: None,
85                appname: None,
86                procid: None,
87                msgid: None,
88                protocol: Protocol::RFC3164,
89                structured_data: vec![],
90                msg: input,
91            },
92        )
93}
94
95///
96/// Parse the message.
97///
98/// # Arguments
99///
100/// * input - the string containing the message.
101/// * get_year - a function that is called if the parsed message contains a date with no year.
102///   the function takes a (month, date, hour, minute, second) tuple and should return the year to use.
103/// * variant - the variant of message we are expecting to receive.
104///
105pub fn parse_message_with_year<F>(input: &str, get_year: F, variant: Variant) -> Message<&str>
106where
107    F: FnOnce(IncompleteDate) -> i32 + Copy,
108{
109    parse_message_with_year_tz::<_, Local>(input, get_year, None, variant)
110}
111
112/// Parses the message.
113/// For messages where the timestamp doesn't specify a year it just
114/// takes the current year.
115///
116/// # Arguments
117///
118/// * input - the string containing the message.
119/// * variant - the variant of message we are expecting to receive.
120///
121pub fn parse_message(input: &str, variant: Variant) -> Message<&str> {
122    parse_message_with_year(input, |_| Local::now().year(), variant)
123}
124
125///
126/// Parse the message exactly. If it can't be parsed, an Error is returned.
127/// Note, since it is hard to locate exactly what is causing the error due to the parser trying
128/// so many different combinations, a simple hardcoded string is returned as the error message.
129///
130/// # Arguments
131///
132/// * input - the string containing the message.
133/// * get_year - a function that is called if the parsed message contains a date with no year.
134///   the function takes a (month, date, hour, minute, second) tuple and should return the year to use.
135/// * variant - the variant of message we are expecting to receive.
136///
137pub fn parse_message_with_year_exact<F>(
138    input: &str,
139    get_year: F,
140    variant: Variant,
141) -> Result<Message<&str>, String>
142where
143    F: FnOnce(IncompleteDate) -> i32 + Copy,
144{
145    parse::<_, Local>(input, get_year, None, variant)
146        .map(|(_, result)| result)
147        .map_err(|_| "unable to parse input as valid syslog message".to_string())
148}
149
150///
151/// Parse the message exactly. If it can't be parsed, an Error is returned.
152/// Note, since it is hard to locate exactly what is causing the error due to the parser trying
153/// so many different combinations, a simple hardcoded string is returned as the error message.
154///
155/// # Arguments
156///
157/// * input - the string containing the message.
158/// * tz - a default timezone to use if the parsed timestamp does not specify one
159/// * get_year - a function that is called if the parsed message contains a date with no year.
160///   the function takes a (month, date, hour, minute, second) tuple and should return the year to use.
161/// * variant - the variant of message we are expecting to receive.
162///
163pub fn parse_message_with_year_exact_tz<F, Tz: TimeZone + Copy>(
164    input: &str,
165    get_year: F,
166    tz: Option<Tz>,
167    variant: Variant,
168) -> Result<Message<&str>, String>
169where
170    F: FnOnce(IncompleteDate) -> i32 + Copy,
171{
172    parse(input, get_year, tz, variant)
173        .map(|(_, result)| result)
174        .map_err(|_| "unable to parse input as valid syslog message".to_string())
175}